Skip to content

feat(workflow): require plan presentation before approval on planning 4 - #48

Merged
whitelonng merged 1 commit into
experiment/guidance-slimfrom
feat/plan-approval-gate
Oct 25, 2026
Merged

whitelonng merged 1 commit into
experiment/guidance-slimfrom
feat/plan-approval-gate

Conversation

@whitelonng

Copy link
Copy Markdown
Owner

Stacked on #46. The base will be retargeted to main once #46 merges.

Why

In a real /man test on unjs/ufo #297, agents on both the baseline and the slimmed guidance self-recorded plan confirm --plan-decision governed_execution. They never showed the plan to the operator. The basis they cited was an earlier generic "Go with your recommendations", given in reply to requirement questions. Prompt text alone does not stop a strong model from reading broad authorization as plan approval.

What

This applies to new tasks only, which use policyVersions.planning = 4. Planning 1–3 tasks keep today's confirm behaviour, and their metadata round-trips byte-identically.

  • workflow plan <ref> present
    • A journalled plan_present operation, bound to (planVersion, plan digest, scope digest, requirementsDigest).
    • Prints the plan summary and tells the agent to wait for the operator's reply.
    • Idempotent, owner-only, and validates the bound plan file before writing.
  • plan confirm on planning 4
    • Requires a current, unconsumed presentation, plus exactly one of --operator-reply or --unattended-waiver.
    • Records planApproval.approvalSource.
  • Bypasses closed
    • revise --plan-decision.
    • Solo handoff start.
    • plan_only → governed_execution resumption.
    • Confirm after reframe.
    • The metadata parser rejects a planning-4 decision with no approval record. This also covers writes that don't go through the CLI, such as git-ref materialization, recovery replay, and hand edits.
  • unattendedWaiver: allowed | forbidden in .mancode/shared/config.json.
    • Absent means allowed for local single tasks and forbidden for manteam and shared tasks.
    • Waivers are flagged in delivery inspect/check, the context index, and the progress page. So are approvals whose scope changed after confirm (PLAN_APPROVAL_SCOPE_CHANGED).
  • Reply-reuse check. Compares only against human-authored task text, after normalising case, whitespace, and trailing punctuation.

What it does not do

The CLI cannot prove a reply came from the operator. A model can still run present and then confirm with an invented reply. What the gate guarantees:

  • The plan is presented before approval.
  • Stale or consumed presentations are rejected.
  • The approval basis is recorded with the digests it approved, so a self-approval is visible in review.

The docs state this. Terminal approval was considered and declined because of operator friction.

Compatibility

  • Older CLIs fail closed on planning-4 tasks (MANCODE_POLICY_VERSION_UNSUPPORTED), so teams must upgrade together.
  • Known limits, documented: hand-editing local metadata from 4 back to 3 bypasses the gate, and the model can edit the waiver config.
  • The man entry grows by 135 tokens. The guidance budget fixture is updated.

Review

Implemented by a Sonnet subagent and reviewed adversarially by an independent Opus subagent. There were no blocking findings. All 7 should-fix items and the minor items are addressed, each with a regression test. tests/plan-gate-contracts.test.ts has 30 tests.

Test plan

  • tsc --noEmit and biome check pass.
  • Full npm run check: 1788 passed.
    • The single failure, tests/secrets.test.ts "handles output without retries", is the pre-existing macOS-only EPERM bug in src/secrets/run.ts. It is fixed separately, and CI skips it on ubuntu.

In real /man runs, agents self-recorded `plan confirm
--plan-decision governed_execution` from an earlier generic "go with your
recommendations" without ever showing the plan. Add a CLI gate for new
tasks (planning policy 4); planning 1-3 tasks keep their confirm
semantics and metadata bytes.

- `workflow plan <ref> present`: journalled `plan_present` operation bound
  to (planVersion, plan digest, scope digest, requirementsDigest); prints
  a plan summary and "Show this plan to the operator and wait for their
  reply before running plan confirm." Idempotent for an unchanged tuple,
  owner-only, validates the bound plan file before writing.
- `plan confirm` on planning 4 needs a current, unconsumed presentation
  plus exactly one of `--operator-reply` or `--unattended-waiver`, and
  records `planApproval.approvalSource`.
- Close bypasses: `revise --plan-decision`, solo handoff start,
  plan_only -> governed_execution resumption, and confirm after reframe
  all go through the gate. The metadata parser rejects a planning-4
  decision without an approval record, covering non-CLI writers.
- Project config `unattendedWaiver: allowed|forbidden` (absent: allowed
  for local single tasks, forbidden for manteam/shared). Waivers and
  approvals whose scope changed later are flagged in delivery
  inspect/check, the context index and the progress page.
- Reply reuse is checked against human-authored task text only, after
  normalising case, whitespace and trailing punctuation.

The gate cannot prove a reply came from the operator; it guarantees the
plan is presented before approval, stale presentations are rejected, and
the approval basis is auditable. Docs say so explicitly.

New man/manteam tasks use planning 4; older CLIs fail closed on them with
MANCODE_POLICY_VERSION_UNSUPPORTED, so teams must upgrade together.
@whitelonng
whitelonng merged commit a39ae9d into experiment/guidance-slim Oct 25, 2026
6 checks passed
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.

1 participant