Files
roboco/alembic/versions/069_tasks_parent_task_id_idx.py
8f3f4236c0 feat(tasks): sequence is the bar — strict sibling ordering at the claim chokepoint (#452)
* feat(tasks): enforce sibling sequence order at the claim chokepoint

A task with a parent and effective sequence N (COALESCE(sequence, 0))
can no longer be claimed while any sibling with a strictly lower
effective sequence is non-terminal — assignee-blind, independent of and
stricter than dependency_ids, enforced in _validate_claim_preconditions
so both claim paths (gateway verbs and the dispatcher's raw REST claim)
cross it. Ties run parallel; cancelled siblings never block; sequence 0
and parentless tasks are unaffected. Live failure this guards: a PM
delegated revision subtasks sequenced 0..3 with no dependency edges and
seq 2 started alongside seq 0 — sequence was advisory-only.

set_sequence's contract updated accordingly. New e2e smoke case drives
the refusal and the post-completion claim through the real gateway.

* chore(scripts): skip .uv-cache and .claude in the prose scanner

Repo-local tool dirs (private uv cache, agent worktrees) carry vendored
and generated markdown that tripped make reflow-check.

* fix(tasks): wave-derived delegation sequences + claim-gate hardening

Three fixes from the adversarial review of the sequence claim gate:

Delegation no longer stamps a raw per-sibling ordinal (deterministic
merge-order bookkeeping) as sequence — under the strict gate that
serialized ALL delegated work, including fully independent cross-dev and
cross-cell siblings. Sequences are now wave-derived post-wiring
(stamp_wave_sequence: 1 + max same-parent dependency sequence, 0 when
independent), so independent siblings tie and run parallel while
colliding/ordered work ascends. The cross-cell UX wiring restamps
instead of writing relative ux+1 values (a relative write could invert
a collision-derived stamp), and the dispatch merge/lane barriers gain a
created_at tiebreak for wave-tied siblings so shared-branch merge order
stays deterministic. PM-authored sequences are never rewritten.

The guard now also fires on reclaims from needs_revision (a lower-
sequence sibling delegated after the first claim was invisible), and
tasks.parent_task_id gains an index (migration 069) — the guard's
sibling probe ran as a Seq Scan on the hottest verb.

---------

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-07-10 22:55:00 +02:00

30 lines
899 B
Python

"""Add ix_tasks_parent_task_id — sibling scans on the claim hot path.
Postgres does not auto-index FK columns, so every sibling lookup
(``get_subtasks``, the sequence claim guard's blocking-sibling probe, the
dispatch merge/lane barriers) was a Seq Scan over tasks. The sequence guard
runs on every PENDING/NEEDS_REVISION claim, so the scan sat on the hottest
verb. Plain btree; additive; no data change.
Revision ID: 069_tasks_parent_task_id_idx
Revises: 068_tasks_constraints_column
Create Date: 2026-07-10
"""
from __future__ import annotations
from alembic import op
revision = "069_tasks_parent_task_id_idx"
down_revision = "068_tasks_constraints_column"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.create_index("ix_tasks_parent_task_id", "tasks", ["parent_task_id"])
def downgrade() -> None:
op.drop_index("ix_tasks_parent_task_id", table_name="tasks")