## Process (fixed sequence — executed by the script)

`bun scripts/workflow-status.mjs` (node when bun is absent — the repository's
runtime convention) executes this whole sequence (git/forge
collection, urgency labels-only scan, roadmap + fix-index parse, dependency
closure, readiness + step-6a receipt sensing, phase progress, review-mark
currency, fix-now fold projection) and prints the fixed Envelope v2 JSON. The
numbered blocks below state the semantics the script implements; the model reads
the JSON and interprets it.

### Normalized Repository State

Read `docs/workflow/REPOSITORY_STATE.md` when present and always emit
`detail.repository_state: {status, snapshot_id, source_revision}`. Before
readiness classification, if the ledger is `draft`, `contradicted`, or
`resolved`, add a run-scoped `substrate` blocker, set the envelope state to
`BLOCKED`, leave `startable_now` empty, and set `next.recommended` to
`/discover-repository-state` for non-frozen state or
`/resolve-repository-state <contradiction-id>` for `contradicted` state.
An absent ledger emits a non-blocking substrate notice instead of a blocker.
The sensor remains read-only; it never edits or resolves the ledger. Conflicting
live evidence against a frozen ledger remains a contradiction candidate.

3. **Urgency labels (`detail.urgent`) — labels-only, presence-only, never
   decides.** Reuse the open-issue list from step 2 (`gh issue list --json
   labels` — the JSON labels array already carried by that call); no separate
   call is required. Scan the **labels object only**
   for `urgent` / `fix-next` — never the issue's title, body, or comments
   (the injection-safety invariant `triage-issue` owns: these two labels can
   only be applied by a triage+-permission actor, so presence alone is
   trustworthy). For each open issue carrying either label, emit `{number,
   title, label}`; if an issue carries **both**, report `urgent` (it strictly
   dominates — reaches the judge — so `fix-next`'s head-of-queue, no-interrupt
   path is redundant on that issue). Alongside the label list, carry the
   **in-flight unit's interruptibility facts** — current phase, dirty/clean
   tree, distance to the next commit boundary — reusing the same reconcile
   step 7 (phase progress) and the crash-recovery dirty-tree check already
   compute; do not duplicate the git calls. This sensor **reports facts only**
   — it never decides pause-vs-finish (that is the consumer's bounded judge,
   canonical in `docs/workflow/ORCHESTRATION.md`); urgency may only *inform*
   `next.recommended`, never silently override it.
4. **Roadmap + fix index.** Parse every row: id, slug, status — the
   five-state machine `idea / defined / planned / in-progress / done` (see
   `docs/features/ROADMAP.md` → Status legend) — depends-on, linked PR. A
   legacy row reading a plain `planned` with no five-state history: check its
   `SPEC.md` product half; complete (`## Design status: designed`) → treat as
   `defined`+`planned` (no redirect, per `docs/workflow/MIGRATION.md`);
   otherwise treat as `idea`. **Unknown status.** A row whose status is **not**
   one of the five states above (e.g. a non-standard `scheduled`) maps to the
   **nearest** five-state value, **defaulting to `idea`** when no nearer value
   is evident — so it safely routes to `/unit-lane` rather than skipping
   design. Worked example: `scheduled → idea` (cross-reference `#51`, which
   owns the fuller status-vocabulary reconciliation). Note the raw status
   string in `workflow_observations` so the mapping is visible, never silent.
5. **Compute the dependency tree.** For every non-merged unit, build the
   **transitive** depends-on closure and mark each edge met (dep's PR merged)
   or unmet — same rule as execute-phase's dependency gate: `done`-with-open-PR
   is NOT met. Detect inconsistencies (a "merged" row whose own deps aren't
   merged; cycles) and report them as `substrate` blockers.
6. **Classify readiness — `startable_now` requires status ≥ `defined` AND deps
   met.** For every unit in the roadmap/fix index:
   - status `idea` → list under **`design_candidates`**, next command
     `/unit-lane <slug>`. Never `startable_now`, regardless of deps.
   - status `defined` or `planned`, deps met → `startable_now`, with the next
     command: `defined` → `/unit-lane <slug>` (lane conductor),
     `planned` → `/execute-phase <NN> <phase>` (step executor).
   - deps unmet (any status ≥ `defined`) → `blocked_units` (unchanged).
6a. **Lane rows — one per open unit (P8b: spec/plan receipts retired).**
   For every unit at `defined`, `planned`, `in-progress`,
   **or `done` with a linked PR that is still open (unmerged — merge
   state lives in the forge)**, emit one `detail.pre_execution[]` row:
   `{unit, unitDir, stage: "lane", label: "current", verdict: "READY",
   boundDigest: null, observedDigest: null,
   recommended: "/unit-lane <id>" (defined/planned/done),
   reason: "unit-doc currency is checked per step by unit-lane/execute-phase"}`.
   The spec/plan receipt stages are retired (feature 61 P8b): no per-unit
   verifier spawns, no bound-digest re-derivations. Receipt currency is
   now the unit doc's triage block checked per step by `unit-lane`/`execute-phase`.
   A roadmap row is never edited here — sensing only reads.
7. **Phase progress.** For each in-progress feature, read `TASKS.md`: current
   phase, total phases, per-phase checkbox completion.
8. **Pending quality gates.** For each unit with commits: has the mandatory
   `review-change` for its current state run? The artifact is the unit's
   **durable review mark** — the `REVIEW-RAN` row of its `review-findings.md`
   fold ledger, whose shape and writer `pre-execution-review`'s `LEDGERS.md`
   owns — and it counts while the sha it names is an ancestor of the unit's head
   and no commit after it touched a bound input of that review, over the paths
   `pre-execution-review`'s `SNAPSHOT.md` binds:
   `git log <mark-sha>..HEAD -- <bound paths>` prints nothing. Equality with the
   head is not the test and cannot be: carrying the mark is a commit, and that
   commit moves the head, so a rule keyed on equality calls every review that
   finished its own turn unreviewed. The ledger's presence is never that proof: a
   review that found nothing writes no finding row, so presence would call a
   reviewed unit unreviewed and an unreviewed one reviewed. Has `audit-pr` a
   MERGE-READY bound to the PR's current head SHA (look for the audit
   comment marker on the PR)? Derive `review_pending` (no current mark) /
   `audit_pending` / `merge_ready` per unit.
9. **Fix-now fold ledger → `findings.fix_now[]`.** For each in-flight unit
   (feature or fix) that has a `review-findings.md` ledger, read only its
   `folded: no` rows and emit each as a structured item:
   `{id, file, axis, severity, class, route, suggested_tier}` (`file` = the
   ledger's `file:line` column value, verbatim). Derive `suggested_tier` from
   this fixed table — mechanical, never guessed:

   | Condition | `suggested_tier` |
   |---|---|
   | `severity == "high"` | `strong` |
   | `axis` ∈ {security, correctness, logic, architecture, design, concurrency} | `strong` |
   | anything else | `cheap` |

   Reuses `next.tier`'s `strong`/`cheap` vocabulary — `next.tier`'s own
   derivation (below) is **unchanged**, this is a separate, per-finding field.
   No ledger for a unit → that unit contributes nothing to `fix_now` (not an
   error); no unit in the run has one → `findings.fix_now: []`, same as
   today. **Read-only**: this step only projects the ledger's current
   unfolded rows — never writes, ticks `folded`, or judges.

### Envelope fields the sensor projects (versioned grammar)

Steps 1-9 print these keys and no others; each row must be a field the schema
package's envelope validator declares for that object.

```text
sensor-fields@1
# machine: envelope
object | field
unit | type
pr | merge_ready
gates | review_pending
gates | audit_pending
findings | fix_now
findings.fix_now | suggested_tier
dependencies | unmet
blockers | kind
recommendations | product_audit
```
