8000 Plan approval is a gate: no worker before the human's typed line (M0.9) by adeelahmad · Pull Request #99 · adeelahmad/aloop · GitHub
[go: up one dir, main page]

Skip to content

Plan approval is a gate: no worker before the human's typed line (M0.9) - #99

Merged
adeelahmad merged 10 commits into
masterfrom
claude/plan-approval-gate-mbyefz
Oct 20, 2026
Merged

adeelahmad merged 10 commits into
masterfrom
claude/plan-approval-gate-mbyefz

Conversation

@adeelahmad
@adeelahmad adeelahmad commented Oct 20, 2026 •
Copy link
Copy Markdown
Owner

Requested by Adeel · project thread

Before: "a human approves the plan" was a playbook rule. events authority trusted --initiated-by, and nothing stopped a supervisor from dispatching RED/SCAFFOLD/GREEN before the human said go, after the plan changed, or on work outside the plan.

After: gate-dispatch refuses every execution role of sprint N (RED, SCAFFOLD, GREEN, structural review, final gate) until the human approved that sprint's plan, as it is now, in a line they typed, and only for that plan's own stories and tasks. events authority --sprint N exits 3 with the line to relay, approve SN <code> (reviewed) or autonomous SN <code> (autonomy for that sprint, re-plans included). An approved autonomous mission grants autonomy with no line, but only to sprints whose plan serves a mission intent. This works on Claude Code (Task), Codex (spawn_agent), OpenCode (task) and dsh.

The code is the first 8 hex of the digest of the sprint's plan files (stories.md, plan.md, ids.json, and each story's tasks.md, validate.md, plan.md, targets.json). It reuses M9.17's mechanism: the line has to be among the human's captured prompts and typed after the sprint's last decision. So each of these blocks:

  • a changed plan;
  • a re-plan in reviewed mode;
  • a roll-back to an old line;
  • another sprint's line;
  • a negated line;
  • a hand-edited or forged record, or one created with events emit;
  • a dispatch whose STORY_DIR or TASK_ID is not a live story or task of the approved plan.

How

  • New plugin/bin/_approval.py:
    • grant_for builds the grant and asks for the line until the human types it.
    • check 8000 is the gate's verdict: the grant holds, then the dispatch is placed in the plan (STORY_DIR is the sprint dir or a live story of its ids.json; an SN-NN-Tk id is a live task of that story; RED/SCAFFOLD/GREEN need a task id).
  • events authority --sprint N records the grant. plan.approved is now emitted only for a plan grant of that sprint.
  • New events approval SPRINT|TASK_ID [--story-dir DIR] [--role ROLE] command: exit 0 if approved, 2 if not.
  • plan.approved and human.approval can no longer be created with events emit.
  • gate-dispatch:
    • reads Codex's agent_type/message;
    • skips the model check when a Codex or OpenCode call names no model, since the agent file pins it;
    • runs the approval check, with the STORY_DIR and role, after the contract checks.
  • OpenCode's task tool now runs gate-dispatch.
  • gate-mission-scope blocks claude -p, codex exec and opencode run from a tool call in every aloop project, not only where a mission is set (the prompt log is the approval's evidence). codex plugin … stays allowed.
  • transcripts prune keeps every human prompt and compacts only tool records, so pruning never takes an approval away.
  • The intake agent reuses an intent a mission handed off (intent check, never intent new), so the mission's sprint keeps its autonomy.
  • e2e-live.sh works out the autonomous S1 <code> line before the session starts and puts it in the prompt as the human's words.
  • Every test suite that dispatches now approves first, through fixtures/plan_fixture.py and plan-fixture.mjs. The door test (Core agent principles and the one-way / two-way door rule (M9.30, M9.38); hard-lined goals retire (M9.29) #86) now approves with the human's typed line instead of a bare record_authority.
  • Docs updated: ROADMAP M0.9 row and a §7 invariant, TRACKING, CHANGELOG, the aloop and mission skills, reference.md (the re-plan route), the bin and mission READMEs, and the evidence ledger (run 50).

Evidence

  • scripts/test/plan-approval.test.py: 20 checks.
    • Run against master, it fails at check 1 (an unapproved dispatch was admitted, exit 0).
    • Checks 14–20 each fail on the pre-review head and pass now.
  • A 4-lens review (bypass, correctness, hosts, docs) after CI was first green found 9 real defects and 3 test gaps, all fixed here: forged-record and replay handling, a mission grant that covered non-mission sprints, a stale-floor bypass, long-line parsing, malformed log lines, prune dropping prompts, codex plugin blocked, dispatches outside the plan, and intake re-creating a mission's intent.
  • Mutation test: 26 mutants of _approval.py, gate-dispatch, transcripts and gate-mission-scope (each patches out one check); the suite killed 26/26, and the unpatched copy passes.
  • Live run 50 (make e2e-live YES=1 INTENT=cli on 575d95a): 13/13, 2 tasks, 25 supervisor turns, $0.84, 10m17s. The supervisor recorded the autonomy grant citing the human's autonomous S1 a5e6261a line, and gate-dispatch admitted every dispatch (S1-01-T1, S1-01-T2, S1-SR-W2, S1-FG) with no dispatch block. Run 48 on master: $0.76, 14m13s.
  • DSH_BIN=… ALOOP_REQUIRE_DSH=1 make test-hosts: exit 0, including the real pinned dsh runtime suite.
  • make conformance: 78/78. make shellcheck, make pycheck, eval-validate, JSON, version check and node --check on both host plugins: clean.

Open (recorded in the ROADMAP M0.9 row)

  • The run log is not hash-chained. Deleting a later decision line restores an older approval.
  • A program's write to the prompt log (python -c) is invisible to the scope gate, as in M9.17.
  • The mission loop's APPROVE_PLAN step counts any plan.approved. It should ask events approval instead.
  • The door gate's live_run (M9.30) reads plan.approved or the autonomy env; it should ask events approval.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WBVFNrEg85XAnSfHQidCXN

gate-dispatch now runs `events approval <TASK_ID>` and refuses every
execution role of a sprint until the sprint's newest grant holds: the
human typed `approve SN <code>` naming exactly the plan as it is now
(reviewed), or `autonomous SN <code>` / an approved autonomous mission
(autonomy for that sprint, re-plans included). The line must be among
the captured prompts and typed after the sprint's last decision
(M9.17's mechanism), so a changed plan, a reviewed re-plan, a roll-back,
another sprint's line, a negated line and a hand-edited or emitted
record all block. plan.approved and human.approval can no longer be
emitted by hand.

Codex's spawn_agent (agent_type, message) and OpenCode's task now reach
gate-dispatch; host sessions started from a tool call are blocked in
every aloop project. e2e-live.sh pre-approves with a typed autonomy
line. Every dispatching suite approves first (plan fixtures).

Test: scripts/test/plan-approval.test.py (13 checks; fails on master
at check 1), host suites, conformance 78/78.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WBVFNrEg85XAnSfHQidCXN
@adeelahmad adeelahmad self-assigned this Oct 20, 2026
adeelahmad pushed a commit that referenced this pull request Oct 20, 2026
…M10.1-M10.4

Plan approval as a gate is already M0.9 (#99), so M10 keeps the four
containment rows. The project stop reuses the ledgered budget stop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QoagJDnsTcVnZvuXwTHTWy
A 4-lens adversarial review, each finding re-verified, found 9 defects:
- an approval of one sprint admitted another sprint's or an unplanned
  STORY_DIR, and tasks the frozen plan lacks: `events approval` now
  takes the contract's STORY_DIR and role and requires a live story
  and task of that sprint's ids.json, and RED/SCAFFOLD/GREEN a task id;
- a line typed again after a later decision stayed stale forever;
- an approved autonomous mission swallowed a non-mission sprint's
  typed autonomy line;
- `transcripts prune` dropped the prompts grants cite (it now keeps
  every prompt and compacts tool records only);
- `codex plugin add` (the documented install) was refused;
- a code past column 200 recorded a grant the gate then refused;
- a malformed run-log line crashed `events authority`;
- intake's own `intent new` made a mission sprint serve no mission
  intent (the persona now reuses the handed-off intent);
- the playbook said a reviewed re-plan never waits for the human.
Also covers three test gaps (forged autonomy grants, a replayed
approval, one sprint's autonomy over another) and corrects docs that
overstated the gate. suppression-contract.test.py no longer writes run
data into the checkout.

Test: plan-approval.test.py 20 checks (each new one red on the first
head); 26/26 mutants of the gate's checks killed; host suite, dsh
runtime and conformance 78/78 green.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WBVFNrEg85XAnSfHQidCXN
…l-gate-mbyefz

# Conflicts:
#	CHANGELOG.md
#	docs/ROADMAP.md
#	plugin/bin/events
#	scripts/test/suppression-contract.test.py
mission-doors.test.py (#86) made a sprint live by calling
record_authority() with no grant, which emitted plan.approved for any
plan. Since M0.9 plan.approved is only the human's typed approval of
that sprint's plan, so the test now records it the real way
(`events authority --sprint 1`, the human types the line it asks for).
ROADMAP: the door gate's live_run should read `events approval` too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WBVFNrEg85XAnSfHQidCXN
…gate branch

Run 49 and run 50 both kept in the evidence ledger. ROADMAP M0.9 records
that M10.2's dependency check should also ask `events approval`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WBVFNrEg85XAnSfHQidCXN
…gate branch

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WBVFNrEg85XAnSfHQidCXN
@adeelahmad
adeelahmad marked this pull request as ready for review October 20, 2026 13:11
@adeelahmad
adeelahmad merged commit fdda5d9 into master Oct 20, 2026
5 checks passed
adeelahmad pushed a commit that referenced this pull request Oct 20, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0197nSutKR1QAAqgJhFZ8fPj
8000
adeelahmad pushed a commit that referenced this pull request Oct 20, 2026
…ch; docs for M9.31

Conflicts: the OpenCode plugin's header comment (gate-dispatch on task from
master, the door gate last from this branch) and codex-hooks.test.py (both
blocks kept). ROADMAP: M9.31 done, new items M9.48, M9.49, M9.54-M9.59, and
the M9 row of section 0 brought up to date; TRACKING, CHANGELOG, OVERVIEW.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012rsDo9GYzNx8tXucywZHnA
adeelahmad pushed a commit that referenced this pull request Oct 20, 2026
…nto the dsh follow-ups

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AiLhAWTRtwbgMCDQNJUbea
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.

2 participants

0