# E3 — Delivery Implementation Plan > **For agentic workers:** implement this plan task by task. Do not import the > old `harness/` package or copy its code. The old repository is evidence for > behavior and incidents, not source. **Goal:** Deliver `satyrn-engine deliver --repo REPO [--timeout SECONDS] CONTRACT -- COMMAND [ARG ...]`: run one synchronous command in a detached worktree pinned to the repository's exact `HEAD`, then emit one exact receipt without modifying the caller's working tree, index, branch, or `HEAD`. A successful changed tree also publishes one reviewable candidate ref; every other handled attempt publishes none. **Architecture:** `delivery.py` owns the delivery state machine and its frozen receipt. The existing `check()` seam still parses the contract and performs the E1 repository-path check. A small internal Git boundary invokes real Git; it is not injectable, and its real Git subprocess behavior is exercised by the marked integration tier. The command runs without a shell in a new POSIX process group. The worktree is removed before the candidate ref is created atomically. `cli.py` owns the literal `--` split and renders the receipt as the only stdout record. **Tech stack:** Python 3.14 standard library, Git, `pytest`, `pytest-cov`, `ruff`, `pyrefly`, and the existing Sphinx documentation stack. No new runtime dependency. **Spec:** `docs/superpowers/specs/2026-08-18-e3-delivery-design.md` > **Correction — 2026-08-19:** The accepted plan originally prescribed direct > pipe forwarding, an engine-owned empty hooks directory, and setting the > cleanup guard only after confirmed worktree registration. Review replaced > those steps with a temporary-file spool and synchronous process-group > teardown, `core.hooksPath=/dev/null` plus `core.fsmonitor=false` on every > engine-owned Git call, and an uncertain guard set before the mutating > `git worktree add`. These changes prevent descendant-held descriptor > blocking, suppress publication and fsmonitor hooks for the full operation, > and cover interruption after Git registers a worktree but before Python sees > the return. Production still exposes no injected Git runner or failure hook; > marked integration tests exercise real Git and process behavior, while > default-tier failure tests monkeypatch private production functions only for > destructive or non-portable abnormal branches. The current tasks below are > normative. ## Phase-size decision Keep E3 as one phase, implemented in six reviewable tasks below. Although the Git and process details are substantial, every task enforces one user-visible promise: one attempt always becomes a receipt while the caller's checkout remains untouched, and only a successful changed tree also becomes a candidate ref. Split or defer work immediately if implementation requires any of the following: a platform-neutral process abstraction, a persistent supervisor, model or Pi knowledge, writable-path policy, validation policy, a second public command, or a generalized candidate-management API. Those are not supporting details of E3; they belong to E4–E6 or a separately proposed phase. ## Global constraints - The supported implementation and evidence target is POSIX, a normal non-bare repository, and a synchronous trusted command. Windows packaging is E6. - Worktree isolation is not a security sandbox. Do not add a container, namespace, filesystem interposer, or claim about adversarial commands. - The command is invoked once, with its exact argv, `shell=False`, `DEVNULL` stdin, the isolated worktree as `cwd`, and a new POSIX session/process group. - On timeout, signal the process group with `SIGTERM`, allow five seconds, then signal any remaining group with `SIGKILL` and reap the direct child before Git cleanup. - `candidate-created`, `discarded`, and `refused` remain the only receipt outcomes. The specific `code`, not a fourth outcome, carries the cause. - All handled non-candidate results after CLI acceptance exit `NO_CANDIDATE = 8`; inherited E1 refusals keep their existing values. - The candidate ref is `refs/satyrn/candidates//head`; ids are rejected, never normalized. Publication is create-if-absent with `git update-ref --no-deref REF COMMIT ""`. - The default test tier starts no process and opens no network connection. Real Git and command behavior live only in `@pytest.mark.integration` tests. - A refusal test always has a sibling success. Integration tests use temporary local repositories and the production CLI/library seams, never a fake Git runner or injected failure backend. - The earlier `local-ai-pi` repository is consulted at immutable revision `8588ba4`, especially `harness/candidate.py`, `harness/processes.py`, the two-repository rewrite, and `HARVEST-INDEX.md`. No implementation is copied. --- ### Task 1: Freeze the public receipt and CLI boundary **Files:** - Modify: `src/satyrn_engine/exits.py` - Modify: `src/satyrn_engine/cli.py` - Modify: `tests/test_exits.py` - Create: `src/satyrn_engine/delivery.py` - Create: `tests/test_delivery.py` - Create: `tests/fixtures/delivery/receipt-ok.json` - Create: `tests/fixtures/delivery/receipt-repo-dirty.json` - Create: `tests/fixtures/delivery/receipt-command-failed.json` - Create: `tests/fixtures/delivery/receipt-candidate-exists.json` - Create: `tests/fixtures/delivery/receipt-cleanup-failed.json` **Produces:** - `ExitCode.NO_CANDIDATE = 8`, without renumbering any existing member. - A frozen `DeliveryReceipt` with the exact fields and nullability from the spec, plus one deterministic compact-JSON renderer. - Closed `DeliveryCode` and `DeliveryOutcome` enums, a typed JSON payload, and complete code-to-outcome and code-to-exit mappings. `outcome` is derived, rather than accepted independently by the receipt constructor. - Pure timeout validation accepting finite positive values only. - A CLI parser that requires a literal `--`, preserves every command token after it, and reports missing separator/command as argparse `USAGE = 2` without producing a receipt. **Steps:** 1. Add failing default-tier tests for the complete exit table, all five exact receipt fixtures, valid/default timeout, invalid timeout values, a missing separator, an empty command, and exact argv preservation. 2. Add `NO_CANDIDATE` and implement only the frozen receipt plus pure parsing helpers. Do not start Git or a command from these tests. 3. Make `main()` dispatch shape explicit while keeping delivery execution unwired until Task 2. Existing `check` and `protocol` behavior must remain byte-for-byte compatible. 4. Run the default suite, ruff, and pyrefly. Record one success fixture and one refusal fixture by name. **Evidence:** parsing `deliver --repo . contract.yaml -- tool --flag` returns the command tuple `("tool", "--flag")`; parsing without `--` fails with exit 2. The committed `OK` and `COMMAND_FAILED` receipt bytes match the renderer. **Commit:** `feat(delivery): freeze the E3 receipt and CLI boundary` --- ### Task 2: Refuse before creating a worktree **Files:** - Modify: `src/satyrn_engine/check.py` - Modify: `src/satyrn_engine/delivery.py` - Modify: `src/satyrn_engine/cli.py` - Modify: `tests/test_check.py` - Create: `tests/test_integration_delivery.py` **Produces:** - `deliver(repo, contract_path, command, timeout) -> DeliveryReceipt` as the library seam. - Contract-first E1 refusals with `contract_id` populated whenever parsing succeeded, including `REPO_UNAVAILABLE`. - Git-root resolution, exact `HEAD^{commit}` capture, full dirty-state refusal, candidate-id validation, and early existing-ref refusal. - A sanitized engine/command environment that removes repository-local routing variables reported by `git rev-parse --local-env-vars`, plus `GIT_NAMESPACE`, while preserving ordinary configuration and `PATH`. **Steps:** 1. Add a sibling test showing `check()` retains the parsed contract on a repository-path refusal; this is required by the receipt's nullability rule. 2. Add integration helpers that initialize a temporary local Git repository, create one base commit, invoke the real CLI, and parse exactly one stdout receipt. 3. Add failing integration pairs for a clean repository versus dirty tracked, deleted, and untracked state; repository root versus a subdirectory; ordinary root versus linked worktree root; valid id versus `/` and Git-invalid ids; absent versus existing ordinary, symbolic, and dangling-symbolic refs. 4. Implement preflight using byte-oriented Git output and `--no-optional-locks status --porcelain=v1 -z --untracked-files=all --ignore-submodules=none`. The input repository shown in the receipt is an absolute normalized path without resolving its symlink spelling; Git operations use the canonical root. 5. Wire the CLI to print exactly one receipt and return the receipt's exit code. 6. Run the default suite plus the focused integration tests. Confirm every refusal leaves the source status and `HEAD` unchanged and creates no engine-owned worktree. **Evidence:** one clean-root invocation reaches the later delivery boundary; `REPO_DIRTY` and `INVALID_CANDIDATE_ID` refuse before worktree registration and match their receipt fields exactly. **Commit:** `feat(delivery): refuse unsafe repositories and candidate ids` --- ### Task 3: Run one isolated attempt and create one candidate **Files:** - Modify: `src/satyrn_engine/delivery.py` - Modify: `tests/test_integration_delivery.py` **Produces:** - A unique temporary parent verified outside every registered worktree, with a detached linked worktree at the captured base commit. - One exact command invocation whose stdout/stderr are spooled outside engine memory and forwarded in chunks to stderr while stdout remains receipt-only. - POSIX process-group timeout teardown. - Explicit typed states for uncertain Git registration and process cleanup, with a minimal process protocol and an immutable teardown result. - Staging, explicit tree/commit creation, deterministic changed-path extraction, worktree removal, and create-if-absent candidate publication. **Steps:** 1. Add a successful fixture command that modifies, adds, and deletes files. Assert the candidate commit has exactly `BASE` as parent, contains the three changes, is reachable by the expected ref, and leaves the caller's tree, index, branch, and `HEAD` unchanged. 2. Add sibling failure fixtures for no change, nonzero exit, missing executable, timeout, and a command that moves the isolated `HEAD`. Assert exact codes, no candidate ref, and no registered worktree. 3. Add the timeout descendant proof: the command starts a same-process-group child that would write a delayed sentinel, then exceeds the timeout. Assert `COMMAND_TIMEOUT`, absence of the sentinel after its delay, and cleanup. 4. Implement registration-aware cleanup. Set the cleanup guard to uncertain immediately before the mutating worktree-add call, refine it after Git's registration query, and clear it only after Git confirms absence. This covers interruption after Git mutates shared metadata but before Python sees the return. Do not use a temporary-directory context manager that deletes a retained or uncertain registered worktree after cleanup failure. 5. Create the commit with `write-tree` and `commit-tree`, explicit parent, fixed engine identity, disabled signing, and a message containing candidate id and base SHA. Derive final paths from `diff-tree -z`, not pre-commit filesystem observation. 6. Remove the worktree before publishing the ref. If command or Git handling fails, route through the same cleanup and apply `CLEANUP_FAILED` precedence. 7. Run the focused integration tests repeatedly to demonstrate that no delayed process writes after timeout. **Evidence:** the success receipt names a readable candidate commit/ref and three canonical paths; the timeout receipt names no candidate and its delayed sentinel never appears. **Commit:** `feat(delivery): create candidates in isolated worktrees` --- ### Task 4: Prove atomic publication and cleanup precedence **Files:** - Modify: `src/satyrn_engine/delivery.py` - Modify: `tests/test_integration_delivery.py` **Produces:** - Race-safe create-if-absent publication with diagnostic re-read after a failed compare-and-swap. - Correct differentiation of `CANDIDATE_EXISTS` from `GIT_FAILED` without claiming retry safety. - Visible retained-path reporting when a locked worktree cannot be removed. - Unexpected-exception cleanup through the same registered-worktree guard. **Steps:** 1. Add two real CLI processes synchronized inside their attempt commands so both target the same absent candidate id. Assert exactly one `OK`, exactly one `CANDIDATE_EXISTS`, one published ref, and an unchanged caller checkout. 2. Add an ancestor-ref collision whose sibling candidate id succeeds. Assert the collision is `GIT_FAILED`, not mislabeled as candidate existence. 3. Add a command that locks its own worktree before returning. Assert `CLEANUP_FAILED` supersedes the pending result, reports the retained path, and publishes no candidate. The test must explicitly unlock/remove/prune in its own teardown. 4. Exercise a real failure after worktree registration and confirm the common cleanup path. Do not add an injected Git runner solely to manufacture the failure. 5. Implement `update-ref --no-deref` with an empty expected-old object id. After failure, inspect symbolic and ordinary state; an existing ref is `CANDIDATE_EXISTS`, every absent or unreadable result is `GIT_FAILED`. 6. Run focused integration tests several times so the race proof is not an ordering accident. **Evidence:** the two-process fixture always produces one winner and one loser; the locked-worktree fixture retains a real path and proves result precedence. **Commit:** `fix(delivery): make publication and cleanup outcomes atomic` --- ### Task 5: Close the supported Git and environment edges **Files:** - Modify: `src/satyrn_engine/delivery.py` - Modify: `tests/test_integration_delivery.py` **Produces:** - Canonical UTF-8 byte ordering for receipt paths and refusal of non-UTF-8 candidate paths before publication. - Engine Git operations that cannot be redirected by inherited local Git routing variables or execute repository, reference-transaction, or external fsmonitor hooks. - Engine Git operations that ignore inherited author and committer dates while the trusted command continues to receive them. - Preserved ordinary Git configuration and filters. **Steps:** 1. Add a UTF-8 success fixture whose path ordering differs between locale/text sorting and raw UTF-8 byte sorting. Assert the specified byte order. 2. On POSIX, create a non-UTF-8 path through a trusted fixture command. Assert `GIT_FAILED`, no publication, and cleanup; pair it with the UTF-8 success. 3. Configure a hook sentinel and a harmless repository filter. Assert the hook never fires during engine Git operations while the filter still participates successfully in checkout/staging. 4. Invoke delivery with inherited `GIT_DIR`, `GIT_WORK_TREE`, and `GIT_NAMESPACE` pointing elsewhere. Assert Git still discovers the isolated worktree and publishes in the intended repository. 5. Confirm all Git subprocesses use the canonical cwd split from the spec and byte-oriented capture where paths are involved. **Evidence:** the UTF-8 candidate succeeds in byte order; the non-UTF-8 sibling refuses without a ref; the hook sentinel remains absent while the filter-backed candidate succeeds. **Commit:** `fix(delivery): harden Git environment and path handling` --- ### Task 6: Document the behavior and record the phase **Files:** - Modify: `README.md` - Modify: `ROADMAP.md` - Modify: `CLAUDE.md` - Modify: `docs/architecture.md` - Modify: `docs/usage.md` - Modify: `docs/glossary.md` - Modify: `docs/contributing.md` - Modify: `docs/index.md` - Modify: `docs/superpowers/index.md` - Modify: `pyproject.toml` - Modify: `src/satyrn_engine/__init__.py` **Produces:** - Contributor-facing documentation of why the design uses a separate worktree, detached `HEAD`, a pinned base commit, one synchronous attempt, and a dedicated non-branch ref. - An explicit trust boundary: worktree isolation is not a sandbox; unrestricted absolute paths and shared Git mutation remain trusted until E4/E5 constrain the model tool surface. - Honest prior-art framing: worktree isolation exists elsewhere; E3 applies it to single-attempt safety and a dedicated candidate-ref namespace. - Correct provenance instructions pointing at `local-ai-pi@8588ba4`; a visible correction in `CLAUDE.md` for its stale commit/guard-copy wording. - `candidate`, `receipt`, and `worktree isolation` definitions added to the concept budget and glossary only now that E3 implements them. - Integration-marker help that describes local Git/process requirements rather than claiming every integration test needs network access. **Steps:** 1. Add CLI usage examples for candidate creation, inspection with `git show`, and explicit deletion with `git update-ref -d`. Do not document merge or automatic apply behavior that E3 does not have. 2. Add an architecture section explaining the snapshot-isolation analogy and each design choice's reason. Cite the old evidence and verified external prior art; do not copy unverified claims from a PR comment. 3. Update the roadmap and SDD record with the commands and named success/failure fixtures actually run. Preserve corrections rather than rewriting them away. 4. Correct stale repository instructions before they can direct a later worker to the wrong provenance or claim the guards were already copied. 5. Run the full default suite, the E3 integration file, ruff, pyrefly, strict Sphinx build, combined statement/branch coverage, and `git diff --check`. 6. Review the final diff against the phase-size decision. Move anything about Pi, validation, mutation policy, model quality, candidate management, or Windows to its named later phase. **Evidence:** one documented success command creates a candidate ref and one documented failure command emits a stable receipt; strict docs and all code checks pass from a clean checkout. **Commit:** `docs: explain and record E3 delivery` --- ## Completion commands ```console uv run pytest uv run pytest -m integration tests/test_integration_delivery.py -v uv run pytest -m "" --cov uv run ruff check . uv run pyrefly check uv run --group docs sphinx-build -W -b html docs docs/_build/html git diff --check ``` The final report names the exact candidate-created fixture, at least one discarded fixture, at least one refused fixture, the two-process race result, and the process-group descendant sentinel result. It does not claim model improvement, sandboxing, validation, Windows support, or packaging.