Draft CUST-WP-0074: qualify task wait states (external vs human gate)
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 3s

Derived-state shape chosen over new stored statuses; reuses task-block
depends_on, needs_human and blocking_reason. Blocked-workplan graph
snapshot recorded under history/.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
codex 2026-09-28 20:52:02 +02:00
parent 693f7f1962
commit 49f505615a
2 changed files with 322 additions and 0 deletions

View file

@ -0,0 +1,229 @@
---
id: CUST-WP-0074
type: workplan
title: "Qualify task wait states: external commitment versus human gate"
domain: infotech
repo: the-custodian
status: ready
owner: the-custodian
topic_slug: custodian
flavor: planning
created: "2026-09-28"
updated: "2026-09-28"
related:
- CUST-WP-0072
- STATE-WP-0092
- COORDINATION-WP-0005
- RAIL-FAB-WP-0029
- CUST-WP-0071
origin: analysis
origin_ref: the-custodian/history/20260928-blocked-workplan-graph.md
---
# Qualify task wait states: external commitment versus human gate
## Why
On 2026-09-28 the hub held 105 blocked workplans (retired slugs excluded)
with 220 `wait` tasks between them. The blocker was recorded as prose in
the task text. Only 7 dependency edges existed in `workplan_dependencies`
across all open workplans, and only 5 of the 220 wait tasks set
`needs_human`. Nothing could answer "which of these only Bernd can
unblock" or "which clear on their own when `FLEX-WP-0020` finishes"
without reading every task.
Reading them shows two different kinds of wait:
- **Case A — external commitment.** The task waits for another repo's
workplan or task to reach a terminal state. It clears by itself; the
waiting side only needs to observe. Example: five workplans wait on
the `flex-auth` → `access-engine` rename (`FLEX-WP-0020`).
- **Case B — human gate.** The task waits for a decision, an approval, an
attended privileged action, or a credential provisioned by an operator.
Nothing in the fleet clears it; a person does. Example: about 25
workplans sit behind one attended OpenBao provisioning session
(`CCR-2026-0004` and the per-lane scoped applies).
Both are `wait` today. Founder decision 2026-09-28: keep `wait` as the
stored status and qualify it, deriving two visible states from the
qualifier. This follows the existing convention that `stalled` and
`needs_review` are derived labels, not stored statuses
(`.claude/rules/workplan-convention.md`). Two new stored statuses were
rejected: every consumer (hub enum, fix-consistency rank logic, Fabric
`OPEN_TASK`, ralph-workplan, dashboards) would have to change at once
across 1465 workplans, whereas a qualifier is additive and a WARN drives
migration one workplan at a time.
Analysis, gate roots and the extracted graph:
`history/20260928-blocked-workplan-graph.md`.
## The shape
No new keys. Three keys that task blocks already carry become meaningful
together:
```yaml
# Case A — 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"
# Case B — 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
```
Rules:
1. A `wait` task carries `depends_on` (Case A), `needs_human: true`
(Case B), or both. Neither → consistency WARN.
2. `blocking_reason` stays required for `wait` (the hub API already
refuses `wait` without it).
3. Case A is satisfied when every `depends_on` target is `done`,
`finished` or `archived` — the coordination-model v0.2 commitment
rule. A task still `wait` past satisfaction → consistency WARN.
4. Case B is satisfied by a resolved decision or by the human's recorded
action in the task text; when `decision_id` is set, resolution of that
decision triggers the same WARN.
5. Workplan `blocked` stays stored. The hub derives `blocked-external`
when all its wait tasks are Case A, `blocked-human` when any is Case
B, so "everything waiting on a person" is one query.
6. A wait on a repo that has no workplan id yet stays Case A with the
repo named in `blocking_reason` and the workplan-level `blocked_on:`
field (C-25). Upgrade to a `depends_on` id as soon as the owner
returns one.
The workplan-level `depends_on` (C-20) is unchanged. Task-level
`depends_on` is the new commitment surface; `related:` stays context.
## Write the standard and update the convention
```task
id: CUST-WP-0074-T01
status: todo
priority: high
```
Owner: the-custodian.
- `canon/standards/task-wait-qualifiers_v0.1.md`: the shape above, the
six rules, the derived-state names, and the satisfaction rule cross-
referenced to `coordination-engine/spec/coordination-model-v0.2.md` §5.
- `.claude/rules/workplan-convention.md`: extend the task-block template
and the status-progression paragraph; state that `wait` without a
qualifier is a WARN once T02 ships.
- `~/ralph-workplan/workplan-spec.md`: same template extension, so the
loop's "all tasks done" check is unaffected and it never picks up a
`needs_human` task.
- Human-gated: canon goes through proposal-then-review. Commit the
proposal, then record the founder's acceptance here.
## Hand off enforcement and indexing to state-hub
```task
id: CUST-WP-0074-T02
status: todo
priority: high
```
Owner: state-hub. This task is the handoff and its acceptance; the
implementation lives in a `STATE-WP` workplan whose id gets recorded in
`depends_on` here once it exists.
Requested, by hub message to `state-hub`:
- C-20 reads task-block `depends_on` (workplan and task ids) and writes
`workplan_dependencies` rows with the waiting task as the from side —
the table has `to_task_id`; check whether a `from_task_id` column is
needed or whether `from_workplan_id` plus description suffices.
- New check (next free C-id): `wait` task with neither task-block
`depends_on` nor `needs_human: true` → WARN, not fixable.
- New check: `wait` task whose every `depends_on` target is terminal, or
whose `decision_id` is resolved → WARN "blocker satisfied", not fixable.
- `needs_human`, `blocking_reason` and `decision_id` written from file to
hub on sync (columns exist; `decision_id` may need one).
- `GET /tasks/` and `GET /workplans/` expose a derived `wait_kind` /
`blocked_kind` field per rule 5. Read model only — no new write route.
Acceptance: the four checks run green against this repo, and the derived
fields answer for `CUST-WP-0074-T05` below.
## Hand off the ontology addendum to coordination-engine
```task
id: CUST-WP-0074-T03
status: todo
priority: medium
```
Owner: coordination-engine.
- `spec/coordination-model-v0.2.md` §5 gains commitment `kind: decision`
for Case B, obligor = the named human or decision id, satisfaction =
decision resolved. Case A is the existing `kind: completion`.
- `spec/cross-owner-wait-mode-v0.1.md` entry condition 1 changes from
"names another repo, owner, or workplan id" in prose to task-block
`depends_on`; the prose form remains a fallback until the T05 backfill
is complete.
- Human-gate cases must never be leased to a worker; make that explicit
in the Act section.
## Hand off the view to railiance-fabric
```task
id: CUST-WP-0074-T04
status: todo
priority: medium
```
Owner: railiance-fabric, under `RAIL-FAB-WP-0029`.
- `coordination_graph.py` reads task-block `depends_on` for edges and the
derived `wait_kind` for colour: external commitment versus human gate.
- Chokepoint sizing already uses in-degree; add a filter for
`needs-human` only, so the operator's queue is one view.
- Fabric remains a read of State Hub. No authoring.
## Backfill the open wait tasks
```task
id: CUST-WP-0074-T05
status: wait
priority: high
needs_human: false
depends_on: [CUST-WP-0074-T01, CUST-WP-0074-T02]
blocking_reason: "Standard must be accepted (T01) and the unqualified-wait WARN must exist (T02) before touching foreign workplan files."
```
Owner: the-custodian, following the `CUST-WP-0072` backfill pattern:
edit files in each repo, commit, run fix-consistency, never patch the hub
directly.
- Start with the gate roots in the history record (R1–R8): qualifying
those roughly 60 tasks turns the largest fan-outs into real edges.
- Case B tasks: set `needs_human: true`; attach `decision_id` where a
CCR or hub decision exists (`CCR-2026-0004` covers the offsite-backup
lane).
- Case A tasks: convert the referenced id in the prose to `depends_on`.
- Leave `flavor: residual` workplans alone.
- Done when the unqualified-wait WARN count for open workplans is zero.
## Correct the mis-blocked workplans
```task
id: CUST-WP-0074-T06
status: todo
priority: low
```
Owner: the-custodian coordinates; each repo owner edits.
Six blocked workplans have no `wait` task at all (`fluid-wp-0008`,
`fluid-wp-0009`, `ft-wp-0001`, `glas-wp-0015`, `key-wp-0013`,
`three-phoenix-ha-cluster`); the `FEP-WP-0002/0003/0004/0006/0008` chain
is self-ordering inside one repo; `testdrive-jsui-publication` waits on
a submodule init. Message each owner with the finding and the
qualifier rule. Do not edit their files or reassign their tasks. Record
the outcome per workplan here.