Skip to content

feat(rfc-008): P3b-1 — relocate stop decision em-recall→enforce-contract.mjs#391

Merged
lantiscooperdev merged 2 commits into
mainfrom
feat/rfc-008-p3b1-enforce-contract-stop
Jun 15, 2026
Merged

feat(rfc-008): P3b-1 — relocate stop decision em-recall→enforce-contract.mjs#391
lantiscooperdev merged 2 commits into
mainfrom
feat/rfc-008-p3b1-enforce-contract-stop

Conversation

@lantisprime

Copy link
Copy Markdown
Owner

RFC-008 P3b-1 — relocate the stop decision em-recall → enforce-contract.mjs

First sub-PR of the load-bearing P3b. Pure relocation (R1 strong form, mirroring P3a): move em-recall.mjs's --gate stop handler verbatim into a new enforcement-layer scripts/enforce-contract.mjs, byte-identical, and repoint stop-gate.sh to it. The substrate's surviving enforcement code (em-recall --gate stop) is deleted in P3d.

Scope: why pure relocation (no contract reads)

The workplan listed the full thin waist under P3b, but the only consumer migrated is stop-gate.sh, which exercises only the stop event — and the claude-code stop decision is purely marker-state (RFC-008:464, "the stop gate reads marker state, not command labels"). The contract-driven tier layer (effective_tier = min(), _index.json capability lookup, config clamp, CLASS-C(a) fail-closed-on-unsupported) is inert for claude-code — min(STRONG,STRONG,identity)=STRONG→refuse_stop = today's behavior — and reading contract files from a globally-installed script would block-loud in every project but this one (install deploys only patterns/_index.json). So the tier layer defers to P3b-2, landing with its real dependencies (install-runtime contract deploy + the P4 config schema). P3d is unaffected.

Changes

  • scripts/enforce-contract.mjs (new): pure decideStop({repoRoot, sid}) + thin CLI. The 3 em-recall exits (:182/:211/:216) translate to return; console.log/process.exit live only in the CLI wrapper.
  • stop-gate.sh: repoint em-recall → enforce-contract; both loud-fail envelopes preserved (CLASS-C c — missing/erroring binary blocks loud, never allow-always).
  • Fail-OPEN fix: the initial isMain check (import.meta.url === pathToFileURL(argv[1])) no-ops under a symlinked install path (macOS /var/private/var) → stop gate degrades to allow-always. em-recall had no isMain guard so was immune. Fixed via realpath-both; pinned by test C4 + the /var/folders hook fixture. Found during E2E, before review.

Tests

  • node tests/test-enforce-contract.mjs44/0 (decideStop unit; 7-scenario parity vs em-recall --gate stop on stdout + exit + stderr-modulo-prefix + no-marker-side-effect; CLI + symlink regression).
  • bash tests/test-stop-gate.sh31/0 (Layer-1 em-recall unit; Layer-2/3 hook→enforce-contract integration incl. retargeted L3.4 loud-fail; Layer-4 retime/rearm).
  • node scripts/validate-plan-marker-sites.mjs2/2.

Review trail

  • Plan (Rule 18 step 2): negative-scenario-planner (HOLD→folded) + codex R1 (HOLD: install-runtime contract root, config schema, registry uniqueness → triggered the re-scope to pure relocation) + codex R2 (ACCEPT) + negative-scenario-planner on the final plan (HOLD: pure-function exit translation, stderr parity → folded).
  • Code (Rule 18 step 6): negative-scenario-reviewer ACCEPT-with-FU (0 blocker/major/minor, 2 NIT; independently reproduced the isMain fix + byte-identical parity).

Follow-ups

🤖 Generated with Claude Code

…tract.mjs

R1 strong form: relocate em-recall.mjs's `--gate stop` handler verbatim into a new
enforcement-layer `scripts/enforce-contract.mjs` (byte-identical), and repoint
`stop-gate.sh` to it. The stop decision is purely marker-state (RFC-008:464), so this
slice reads NO contract/registry/config/events files — the contract-driven tier layer
(min(), _index.json cap lookup, config clamp, CLASS-C(a)) is inert for claude-code and
defers to P3b-2 with its install-runtime contract deploy + P4 config schema. em-recall
`--gate stop` stays until P3d.

- scripts/enforce-contract.mjs: pure `decideStop({repoRoot, sid})` + CLI; the 3 em-recall
  exits (:182/:211/:216) translate to returns; console.log/process.exit only in the CLI.
- stop-gate.sh: repoint em-recall→enforce-contract; loud-fail port preserved (CLASS-C c).
- Fail-OPEN fix: isMain `import.meta.url === pathToFileURL(argv[1])` no-ops under a
  symlinked install path (/var→/private/var) → stop gate allow-always. Fixed via
  realpath-both; pinned by test C4 + the /var/folders hook fixture.
- tests: test-enforce-contract.mjs (44/0 — unit + 7-scenario parity vs em-recall on
  stdout+exit+stderr+no-side-effect + CLI + symlink regression); test-stop-gate.sh 31/0
  (fixture stages enforce-contract; L3.4 retargeted). validate-plan-marker-sites 2/2.

Plan review: codex ACCEPT (R2) + negative-scenario-planner (final). Code review:
negative-scenario-reviewer ACCEPT-with-FU. FU: structured-alert-probe.mjs same isMain
idiom (test-only); P3d retarget test-stop-gate Layer 1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@lantiscooperdev lantiscooperdev left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — RFC-008 P3b-1 (bot, substantive)

Reviewed the diff against em-recall.mjs:141-217 (the relocation source) and ran the suites locally.

Correctness

  • Byte-identical relocation confirmed. decideStop() matches em-recall's --gate stop handler verbatim — plan-pending strict 6-clause comparison, stopGateCarveOutApplies(repoRoot, sid), the postDoneSize === 0 check, the block-reason string, and the 3 exit→return translations (:182→null, :211→{decision,reason}, :216→null). The 7-scenario parity suite asserts stdout + exit-code + stderr (modulo prefix) + no-marker-side-effect, all green.
  • Pure-function discipline: console.log/process.exit appear only in the CLI if(isMain) wrapper; decideStop is side-effect-free. Correct.

Fail-open fix (the load-bearing catch)

The isMain realpath fix is correct and complete. Verified independently: under a symlinked install path the pre-fix idiom no-op'd → stop gate allow-always (fail-OPEN); the realpath-both check restores the block. The catch→false is the right direction — a script's argv[1] always exists when executing, so it can't mask a legitimate main invocation. No other fail-open path: a throw in decideStop/resolveRepoRoot propagates as non-zero exit → stop-gate.sh's || {block} envelope (loud-fail), not a silent allow.

Migration safety

  • Loud-fail port symmetric (missing-binary + non-zero-exit, both branches). CLASS-C(c) satisfied.
  • em-recall --gate stop untouched; both paths kept in parity until P3d.
  • validate-plan-marker-sites green — enforce-contract refs the plan marker by symbol, no new code-literal (no registry drift).

Tests

44/0 (enforce-contract) + 31/0 (stop-gate, incl. real hook integration) + 2/2 (validator). E2E via the /var/folders fake-home fixture exercises the production hook path and is what surfaced the fail-open bug.

Follow-ups (non-blocking)

  • #390 — same isMain idiom in structured-alert-probe.mjs (test-only today).
  • P3d — retarget test-stop-gate.sh Layer 1 off the to-be-deleted em-recall --gate stop.

Verdict: looks correct and well-covered. Deferring approval to the maintainer (UI).

…E fixture + wire test into CI

CI caught a fixture-completeness drift: test-issue-146.mjs's mkE2EHome() runs the
real stop-gate.sh hook (L4 runtime-E2E) through a fake canonical install, but staged
only em-recall.mjs — so post-repoint the hook hit its loud-fail "enforce-contract.mjs
not found" envelope and L4.1b (carve-out should allow) saw a block. Stage
enforce-contract.mjs (its lib import closure was already copied). Local: 51/0.

Also wire tests/test-enforce-contract.mjs into plan-marker-validate.yml (it ran in no
CI workflow).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lantiscooperdev

Copy link
Copy Markdown
Collaborator

Codex PR-level review — ACCEPT (consensus, round 1)

Cross-tool review via the second-opinion harness (episodes 20260615-100038-…e0e2 request / 20260615-100356-…985b reply). 0 blocking / major / minor findings.

Codex independently verified:

  • Byte-identical relocationdecideStop() matches em-recall.mjs:141-217; stop decision is marker-state only (RFC-008:464).
  • No fail-open path — confirmed the isMain realpath fix + C4 coverage; --gate/sid handling and the loud-fail port are correct.
  • Authority roots — decision root is resolveRepoRoot() from the hook-cd'd cwd; no --project/evidence/store surface in this slice.
  • Negative repro (codex-run): hook process cwd outside the project + input .cwd nested inside the target → block decision bound to the target repo, no marker under caller. ASSERT=PASS.
  • Suites (codex-run): test-enforce-contract 44/0 · test-stop-gate 31/0 · test-issue-146 51/0 · validate-plan-marker-sites 2/2.

Combined review trail for this PR: plan — codex R1 HOLD → R2 ACCEPT + negative-scenario-planner; code — negative-scenario-reviewer ACCEPT-with-FU; PR-level — codex ACCEPT. Deferring approval to the maintainer (UI).

@lantiscooperdev lantiscooperdev merged commit e077f55 into main Jun 15, 2026
2 checks passed
@lantisprime lantisprime deleted the feat/rfc-008-p3b1-enforce-contract-stop branch June 15, 2026 10:14
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