--- id: canon-task-wait-qualifiers type: standard title: "Task Wait Qualifiers v0.1" domain: custodian status: accepted version: "0.1" owner: the-custodian revision: "accepted-1" last_reviewed: "2026-09-28" review_interval: 6m created: "2026-09-28" updated: "2026-09-28" scope: fleet related_workplans: - CUST-WP-0074 - CUST-WP-0072 - STATE-WP-0092 - COORDINATION-WP-0005 - RAIL-FAB-WP-0029 supersedes: none --- # Task Wait Qualifiers v0.1 ## Purpose A task in `status: wait` must say what it waits for in a form the fleet can read without a person. Two kinds of wait exist and need different handling: - **External commitment** — the task waits for another workplan or task to reach a terminal state. It clears on its own; the waiting side observes. - **Human gate** — the task waits for a decision, approval, attended privileged action or operator-provisioned credential. Nothing in the fleet clears it; a person does. `wait` stays the only stored status for both. The kind is a **derived state** computed from qualifier keys, in the same way `stalled` and `needs_review` are derived labels rather than stored statuses (ADR-001 workplan convention). Founder decision 2026-09-28 (`CUST-WP-0074`): derived states, not two new stored statuses. Reason: a qualifier is additive — existing files stay valid and a consistency warning migrates them one workplan at a time — whereas new statuses force every consumer (hub enum, fix-consistency rank logic, Fabric open-task set, ralph-workplan, dashboards) to change at once. ## Shape No new task-block keys. Three keys that task blocks already carry become meaningful together: ```yaml # External commitment → derived state: waiting-external status: wait depends_on: [FLEX-WP-0020] # workplan id, or task id (KEY-WP-0035-T02) blocking_reason: "access-engine rename must land first" # Human gate → derived state: needs-human status: wait needs_human: true blocking_reason: "operator must provision NC_WEBDAV/AGE creds to OpenBao" decision_id: CCR-2026-0004 # optional, when a tracked decision exists ``` A task may carry both (derived state: both). `decision_id` is the id of a hub decision or a change request; it is optional and never a substitute for `needs_human: true`. ## Rules 1. A `wait` task carries a `depends_on` list, `needs_human: true`, or both. Neither → consistency WARN (unqualified wait), not auto-fixable. 2. `blocking_reason` stays required for `wait`. The hub API already refuses `wait` without it; the file must carry it so the hub can be rebuilt from files. 3. An external commitment is satisfied when every `depends_on` target is terminal: task targets `done` or `cancel`, workplan targets `finished` or `archived` (coordination-model v0.2 §5 commitment rule). A task still `wait` after satisfaction → consistency WARN (blocker satisfied). Targets the hub cannot resolve produce no warning. 4. A human gate is satisfied by the person's recorded action in the task text, or — when `decision_id` is set — by that decision resolving, which raises the same WARN. 5. Workplan `blocked` stays stored. The hub derives `blocked-human` when any wait task is a human gate, else `blocked-external` when any is an external commitment, else `none`. "Everything waiting on a person" is therefore one query, not a reading exercise. 6. A wait on another owner's reply, with no workplan id yet, is an external commitment qualified by `blocked_on: message-from:` on the task block — the vocabulary C-25 already watches for unread replies. The consistency check accepts it in place of `depends_on`; the hub read model reports such a task as `unqualified` until an id exists, so upgrade to a `depends_on` id as soon as the owner returns one. 7. A human-gate task is never leased to a worker. Coordination tooling treats `needs_human: true` as a stop, not a wake condition. ## Scope of `depends_on` - Workplan-frontmatter `depends_on` (C-20) is unchanged: it declares workplan-level blockers. - Task-block `depends_on` is the commitment surface this standard adds. It is indexed into the same hub dependency table with the waiting task as the from side. - `related:` is context, never a commitment. ## Derived state names | Stored | Qualifier | Derived task state | Hub field | |---|---|---|---| | `wait` | `depends_on` only | `waiting-external` | `wait_kind: external` | | `wait` | `needs_human: true` only | `needs-human` | `wait_kind: human` | | `wait` | both | both | `wait_kind: both` | | `wait` | neither | unqualified (WARN) | `wait_kind: unqualified` | Workplan: `blocked_kind` ∈ `human` \| `external` \| `none`, null unless `status: blocked`. ## Consumers - **state-hub** — fix-consistency indexes task-block `depends_on`, emits the two warnings, syncs `needs_human` / `blocking_reason` / `decision_id` from file to hub, exposes `wait_kind` and `blocked_kind` on the read model. - **coordination-engine** — `cross-owner-wait-mode` enters from task-block `depends_on`; the ontology gains commitment `kind: decision` for human gates. - **railiance-fabric** — the coordination graph colours commitment edges and human-gate nodes distinctly and offers a needs-human-only view. - **ralph-workplan** — the loop never picks up a `needs_human` task and its "all tasks done" retirement rule is unaffected. ## Migration Existing `wait` tasks without a qualifier remain valid files. The unqualified WARN drives the backfill (`CUST-WP-0074-T05`), gate roots first. Do not touch `flavor: residual` workplans. ## Review Proposal-then-review. Acceptance is recorded in `CUST-WP-0074-T01`; on acceptance set `status: accepted`, `revision: accepted-1`.