Skip to content

fix(status): opt-in stage-report-presence guard on status advance (#235)#236

Open
gcko wants to merge 1 commit into
spacedock-dev:mainfrom
gcko:fix/issue-235-confirm-mapping-stage-report-guard
Open

fix(status): opt-in stage-report-presence guard on status advance (#235)#236
gcko wants to merge 1 commit into
spacedock-dev:mainfrom
gcko:fix/issue-235-confirm-mapping-stage-report-guard

Conversation

@gcko
Copy link
Copy Markdown
Contributor

@gcko gcko commented May 26, 2026

Closes #235.

Summary

Add an opt-in mechanical guard against the failure mode reported in #235: an FO that advances entity status past a stage which emits stage-side YAML approval evidence (e.g. semantic-mapping.yml with review.status: approved) without appending the corresponding ## Stage Report: <stage> section to the entity body. The downstream consumer ends up with an entity where the YAML records "approved" but the prose audit log is missing.

When a workflow declares stages.defaults.require-stage-report: true in its README, the status binary refuses:

status --set <slug> status=<next>

unless the entity body contains ## Stage Report: <prior_status> (with optional (cycle N) suffix for feedback rounds). --force bypasses for explicit manual recovery.

Why opt-in

The default worktree: false / concurrency: 2 test fixture (and several user workflows) use a pure-state initial stage like backlog with no worker — there is no prior report to require for the first advancement. Making the guard always-on would break those workflows. Workflows where every stage produces an audit log section can opt in with a single line.

The initial stage is exempt regardless of the opt-in (it has no prior to require a report for).

What changed

  • skills/commission/bin/status — new guard block in the --set handler. Reads stages.defaults.require-stage-report from the workflow README; when true and the update changes status to a different non-empty value, scans the entity body for ^## Stage Report: <prior>(\s*\(cycle N\))?$. Exits 1 with a clear error naming the missing section if absent (and not --force'd).
  • tests/test_status_script.pyTestStageReportGuard (8 tests): refusal, allowed transitions with the report, initial-stage exemption, idempotent --set (no transition), --force bypass, opt-in absence (backward compatibility), (cycle N) suffix matching, error message contract.
  • skills/first-officer/references/first-officer-shared-core.md — new "Stage-report-presence enforcement" paragraph in ## Completion and Gates describing the opt-in and the --force discipline.
  • skills/ensign/references/ensign-shared-core.md — Stage Report Protocol rules gain a bullet about committing the section in the same commit as stage work, noting the FO's advancement refusal under opt-in.
  • skills/commission/SKILL.md — commission template's stages.defaults block documents the new optional flag.

Test plan

  • make test-static — 643 passed, 27 deselected, 15 subtests passed (full repo, no regressions)
  • New TestStageReportGuard suite — 8 tests, all RED first then GREEN, covering every branch of the guard

Notes for the maintainer

The guard's "initial stage exempt" rule reads the workflow's stages block and skips when current_status is in the set of initial: true stages. This matches the conventional shape (one initial stage, optionally pure-state) but degrades gracefully when no stage is marked initial — every transition is then checked.

I chose opt-in via stages.defaults rather than per-stage because the recurring bug is the same shape across stages (the FO writes YAML but forgets the prose), and a per-stage flag would force every workflow author to enumerate it for each stage. If you'd prefer per-stage, the change is mechanical — happy to revise.

If you want the default flipped to ON in a future release, that's a one-line change in parse_stages_with_defaults once existing fixtures are updated.

🤖 Generated with Claude Code

…acedock-dev#235)

Issue spacedock-dev#235 reports an intermittent failure mode in the
spacedock-answer-questions workflow: the FO advances entity status past
a stage that emits stage-side YAML approval evidence (e.g. a
semantic-mapping.yml with `review.status: approved`) without appending
the corresponding `## Stage Report: <stage>` section to the entity body.
A downstream consumer that gates artifact publishing on the approval-
evidence pair then either rejects valid runs or has to backfill the log
section manually.

Add an opt-in mechanical guard in `status --set`. When the workflow
declares `stages.defaults.require-stage-report: true`, the status binary
refuses `--set <slug> status=<next>` unless the entity body contains
`## Stage Report: <prior_status>` (with optional `(cycle N)` suffix for
feedback rounds). The initial stage is exempt because some workflows use
a pure-state intake; only transitions out of stages that ran a worker
need the audit log to exist. `--force` bypasses the guard for explicit
manual recovery.

Default OFF preserves backward compatibility — all 643 existing tests
pass without change. Workflows opt in by adding the flag to their
README's stages.defaults block.

Docs updated in three places:
- skills/first-officer/references/first-officer-shared-core.md
  (Completion and Gates — new "Stage-report-presence enforcement" para)
- skills/ensign/references/ensign-shared-core.md
  (Stage Report Protocol — note about the FO's advancement refusal)
- skills/commission/SKILL.md
  (commission template — optional defaults flag documented)

Tests: 8 new test cases in TestStageReportGuard cover refusal, allowed
transitions with the report, initial-stage exemption, idempotent --set,
--force bypass, opt-in absence (backward compat), `(cycle N)` suffix
matching, and the error message contract.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FO can advance past confirm-mapping without writing the stage-report section to index.md

1 participant