E5 — One real attempt: implementation plan

Companion to the accepted design spec docs/superpowers/specs/2026-08-20-e5-real-attempt-design.md.

Goal: Ship satyrn-engine attempt CONTRACT and make /implement CONTRACT run that command inside E3 delivery, using E4 for every write.

Architecture: Python prepares one frozen contract and revision map, starts one hermetic Pi print-mode child, preserves transcript and Git patch artifacts, and returns a typed result. The TypeScript slash command owns only command construction, transport conversion, and receipt display. E3 owns isolation, timeout, cleanup, commit, and publication. satyrn-evals remains an executable consumer and imports no engine code.

Scope guard: no packaging, Windows proof, file creation, shell, subagent, validation, grading, retry, batching, or performance claim.

Correction, 2026-08-21

Implement the accepted design with its 2026-08-21 correction. Artifact checks cover every registered worktree plus Git administrative/common roots using filesystem identity, and publication is descriptor-relative after pinning the parent during preparation; all result and exception paths close each acquired descriptor exactly once. Revision enumeration follows no symlink. All engine-owned temporary and artifact cleanup failures have typed precedence and retained-path evidence; secondary cleanup exceptions preserve the primary exception identity. Inner and outer argv use --model=VALUE and literal -- boundaries. Delivery cancellation reports completion only after E3 has torn down the attempt group, closed, and cleaned or retained its worktree. The default tier injects these seams and proves descriptor-relative parent swaps on the real filesystem; the marked integration tier proves cross-worktree and Git-administration identity, tracked-symlink, and delayed-descendant cases with successful siblings. Stream read errors, diagnostic callback failures, and filesystem-canonical, component-boundary contract classification are also typed adapter behavior rather than uncaught Node exceptions.

The cancellation proof also owns a POSIX outer delivery process group. A deadline before spawn waits for the spawn event; afterward the first refusal sends group SIGTERM and settles only after direct-child close and a group probe reporting ESRCH. The adapter does not SIGKILL that outer group because E3’s separately-sessioned attempt and worktree cleanup remain E3’s ownership; an arbitrary outer grace period cannot prove those inner resources are safe. Windows keeps the previous direct-child fallback and is not part of the E5 platform proof. Keep the one-shot Spawner stable and add the PID/spawn-event/ detached options only to DeliverySpawner.

Task 1: Freeze the public command and typed result

Files:

  • Create src/satyrn_engine/attempt.py

  • Modify src/satyrn_engine/exits.py

  • Modify src/satyrn_engine/cli.py

  • Create tests/test_attempt.py

  • Modify tests/test_exits.py

  • Modify tests/test_delivery.py

Steps:

  1. Add ExitCode.ATTEMPT_FAILED = 10 without renumbering existing exits.

  2. Add AttemptCode, frozen/slotted AttemptResult, AttemptContext, and AttemptArtifacts, plus an explicit exhaustive code-to-exit map.

  3. Add attempt [--model MODEL] CONTRACT; resolve the model from the flag, then SATYRN_MODEL, otherwise return usage 2.

  4. Keep deliver’s literal -- parser unchanged and add parser siblings for attempt help, model precedence, malformed arguments, and refusal output.

  5. Prove every detailed result maps to one stable process exit and exit 1 is still unassigned.

Commit after the tests pass.

Task 2: Prepare one immutable mutation context

Files:

  • Modify src/satyrn_engine/attempt.py

  • Modify tests/test_attempt.py

  • Create tests/test_integration_attempt.py

Steps:

  1. Check cwd and the contract through E1; require a real Git worktree and capture exact HEAD once.

  2. Copy accepted contract bytes to an engine-owned temporary file outside the worktree and point E4 at that copy.

  3. Enumerate tracked paths with NUL-safe Git output, normalize safe POSIX names, reject every symlink component, apply E4’s fnmatch rule, and hash exact bytes.

  4. Refuse before Pi when no existing regular tracked file is writable.

  5. Build the version-1 mutation context and deterministic prompt from the sorted path list.

  6. Give every Git refusal a successful real-Git sibling. Include spaces, control characters where supported, a contract path inside the repo, an external contract, and an attempted contract self-edit.

Commit after default tests and focused integration pass.

Task 3: Run one Pi child and preserve artifacts

Files:

  • Modify src/satyrn_engine/attempt.py

  • Modify tests/test_attempt.py

  • Modify tests/test_integration_attempt.py

  • Create tests/fixtures/attempt/fake_pi.py

Steps:

  1. Define the PiRunner protocol and production subprocess implementation.

  2. Build exact Pi argv with print JSON, no session, explicit E3.5/E4 extensions, all discovery disabled, no project approval, and only read,edit.

  3. Sanitize Git routing variables, VIRTUAL_ENV, and SSH_AUTH_SOCK; set the frozen mutation context and explicit engine checkout.

  4. Spool Pi stdout to a safe regular temporary file, forward stderr, reap the child, export exact transcript bytes, and replay them to stdout.

  5. Produce a raw binary-capable Git diff against captured HEAD and atomically export it only when non-empty.

  6. On nonzero Pi exit, preserve both artifacts first, then return ATTEMPT_FAILED. On any artifact failure, that failure takes precedence.

  7. Cover start failure, nonzero, empty output, no diff, binary-like diff, artifacts in any registered worktree or Git administrative root, case-alias containment, pre-existing/symlink paths, parent replacement, path redirection after descriptor acquisition, partial descriptor acquisition, write/cleanup failures, exactly-once close, and primary/secondary exception precedence with success siblings.

Commit after default and integration evidence passes.

Task 4: Turn /implement into E3 around E5

Files:

  • Modify packages/engine/orchestrator.ts

  • Modify packages/engine/package.json only if the final extension split needs it

  • Modify tools/replay_orchestrator.mjs

  • Modify tests/test_mutator.mjs only for shared transport types

  • Create or modify tests/test_orchestrator.mjs

  • Modify tests/test_integration_protocol.py or add a focused integration file

Steps:

  1. Keep the one-shot E2/E4 exchange helper used by mutator.ts.

  2. Add typed E3 receipt parsing and a delivery-spawn seam with stdout, stderr, spawn, close, error, deadline, and synchronous termination observation. On POSIX start the outer delivery in a detached process group, hold a refusal that precedes spawn unless a spawn error proves no child exists, then send group SIGTERM. A timeout or transport refusal is not returned before both the delivery child closes and signal-0 observes ESRCH. Keep present/unknown group states pending, and do not use outer SIGKILL as a substitute for E3’s inner teardown. Retain the existing direct-child TERM/KILL/close fallback on Windows.

  3. Resolve an in-repository contract to a worktree-relative inner argument; retain an external contract as absolute.

  4. Build the exact outer E3 command and inner E5 command with SATYRN_ENGINE_REPO and SATYRN_MODEL explicit, --model=VALUE, and a literal -- before a potentially dash-leading contract.

  5. Replace the check-only /implement handler with delivery. Notify success with candidate ref/commit and refusal with exact receipt or adapter code.

  6. Contain missing model/repo, start failure, timeout, malformed/extra output, nonzero without receipt, asynchronous stdin/stdout/stderr errors, diagnostic-sink failures, and handler exceptions. Prove timeout teardown has completed before returning, including deadline-before-spawn and synchronous close during signaling. Preserve each close-time refusal code while waiting for the POSIX group to disappear.

  7. Replay the real package through an isolated fake engine, then exercise real E3 delivery around the fake Pi attempt.

Commit after Node behavior/coverage and Python integration pass.

Task 5: Prove the complete slice

Files:

  • Modify tests/test_integration_attempt.py

  • Add only the minimal fixture contract/repository data needed

  • Modify tools/ only for a reusable live-gated command if necessary

Steps:

  1. Start fake Pi in a real detached E3 worktree and make its one edit travel through the shipped TypeScript mutator and Python protocol.

  2. Assert candidate parent, tree, declared changed path, transcript bytes, receipt, and unchanged caller HEAD/index/status.

  3. Add a refusal sibling where E4 rejects a stale/undeclared replacement and E3 publishes no candidate.

  4. Run a separately gated real model with an exact model string; record Pi version, base, contract, candidate ref, receipt, and transcript evidence in docs/sdd.md without committing model-generated project output.

Commit after the recorded proof is reproducible.

Task 6: Close E5 documentation and gates

Files:

  • Modify README.md

  • Modify ROADMAP.md

  • Modify docs/architecture.md

  • Modify docs/contributing.md

  • Modify docs/glossary.md only if one E5 term earns its place

  • Modify docs/index.md

  • Modify docs/sdd.md

  • Modify docs/usage.md

  • Modify src/satyrn_engine/__init__.py

Steps:

  1. Document direct-attempt danger and /implement safety without implying container isolation or grading.

  2. Show exact model configuration, source-checkout command, artifact seam, candidate inspection, and recovery/refusal behavior.

  3. Mark E5 complete and E6 current only after live evidence exists.

  4. Run and record measured commands, not copied counts:

uv run pytest -q
uv run pytest -m integration -q
uv run pytest -m '' --cov --cov-report=term -q
node --experimental-strip-types --test --experimental-test-coverage ...
uv run ruff check .
uv run pyrefly check
uv run sphinx-build -W -b html docs docs/_build/html
git diff --check
  1. Require 100% Python statement/branch coverage and 100% lines/branches/ functions for E5-owned TypeScript.

  2. Split the final history into design, implementation, and documentation branches so each stacked PR is understandable and independently checked.