email-connect/docs/sender-attended-entry-2026-10-31.md
tegwick e795b2970c
Some checks failed
Transactional mail acceptance / mail (push) Failing after 49s
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Add attended sender release with sealed sources and recovery
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a18e2d-f513-7742-b8e7-12af0b744ff3
2026-10-31 17:18:52 +01:00

6.5 KiB

Attended sender release preparation

The source-bound launcher and node coordinator are implemented. Twenty-one new tests pass; the full owner suite reports 270 passed, 21 existing optional harness skips in 36.83s. This does not establish live sender deployment. No runnable proposal has been sealed: the exact credential owner task RPF-WP-0068-T04 is still todo, and the preparation command refuses before contacting the node.

EMAIL-WP-0010-T02 now waits for that owner return so the live proposal and its exact digest can be prepared and reviewed. T03/T04 retain attended execution and independent acceptance. The existing native adapter's five server dry-runs and pinned-image snapshot rehearsal remain source-valid; those sources did not change.

Source binding and admission

scripts/release-sender.py is the workstation entry point. It defaults to no action and requires an explicit prepare/show/inspect/execute/recover selection. Preparation requires a private fresh output path and the complete successful foundation journal. It checks that the exact RPF-WP-0068-T04 UUID and workplan UUID are marked done in State Hub before any node call. The owner handoff must include the review's Warden session revocation/cleanup, CCR verification and rotation inventory obligations; the consumer never marks that task done itself.

The node repeats the owner check, validates the foundation receipt's fixed digest and proof fields, verifies current projection specs/Ready/ownership and the admitted service-account UID, then performs the five native server dry-runs. Its proposal binds complete before/after observations, the exact foundation contract, all four projection resource UIDs, successful owner receipt, completed task identity and eight source hashes. A replaced projection refuses forward execution even if its spec is unchanged; explicit configuration recovery remains available. The capsule contains those eight exact files, including launcher and coordinator. It is saved exclusively as mode 0600 in a private operator directory.

Execution requires the retained capsule, unchanged reviewed source bytes, the full proposal digest, a human terminal and the exact DEPLOY <digest> response. It rechecks the owner return and native preimages. Source transfer uses a fixed SSH target and a quoted Python bootstrap; payload data never enters shell code. The bootstrap verifies source hashes and uses a temporary private directory with isolated Python imports. No generic archive extraction or persistent installation is involved.

Durable execution and recovery

The node creates one exclusive attempt directory under /var/lib/email-connect-sender-release/<proposal-sha256>/. It retains the capsule, private append-only journal, process identity and ledger snapshots. The journal is exclusively locked; the existing ledger capture lock excludes concurrent backup capture. The coordinator invokes the actual native adapter and protocol for pause, fence, capture/replay, configuration change, resume, runtime checks, postflight comparison and UID-bound helper cleanup.

The successful forward result is sender_deployed_verified; successful explicit recovery is configuration_recovered_verified. The final journal phase is the durable evidence if the launcher's response is lost. A failed response or timeout is unconfirmed, not proof that the node stopped. The launcher never retries.

--inspect checks the retained capsule, process PID plus Linux start ticks, the journal and current recognized configuration. It checks recovery processes as well as the original process. A running attempt is reported as running. After the process exits, it reports the journal phase and actual sender/replica count; it does not mutate or resume the application. Inspection refuses a truncated journal or unrelated native drift instead of reporting success.

--recover requires the same capsule/digest and a human RECOVER <digest> confirmation. It does not depend on a still-healthy new projection or a renewed owner approval: it restores the already reviewed original configuration after capturing the current ledger. It retains subsequent accepted and uncertain sends, suppression and consumed/canceled handles. It never restores an older database, clears unknown reservations or sends a message. Repeated forward execution and completed recovery refuse. If source files have changed, restore the exact reviewed capsule sources before invoking its launcher; never edit or reseal a retained attempt to bypass a mismatch.

Some ambiguous states require separate inspection: a truncated journal, a crash during initial metadata creation, an unconfirmed helper creation without a recorded UID, or unrelated configuration drift. The tool does not silently adopt pods, discard evidence or invent a recovery from those states. A missing local capsule must be recovered from the retained private node copy, not reconstructed from current configuration.

Validation scope

The assembly tests invoke the actual coordinator, protocol and native adapter. Kubernetes transport is modeled with synthetic objects; SQLite capture/replay runs in real subprocesses, and runtime probes contact the actual scoped WSGI application on local loopback with zero synthetic provider sends. Host path/UID substitutions are explicit in that harness. It is not a real Kubernetes rollout or a new pinned-image claim; the separate isolated-image rehearsal covers that snapshot path.

Tests cover complete forward/recovery, a lost native configuration reply, later accepted and uncertain sends, capture-lock contention, retained exclusive attempts, source/proposal/foundation/owner drift, private complete final foundation receipts, terminal and exact confirmation gates, bootstrap hash refusal, unconfirmed SSH timeouts and real process lifetime observation. The earlier protocol tests retain their ten real abrupt child exits around phase boundaries.

Ruff passes. Suite log: /tmp/email-sender-release-final-check-20261031.log. Source hashes and the live owner-gate refusal are in sender-attended-entry-2026-10-31.json.

Next owner handoff

After RPF-WP-0068-T04 has its verified native return, prepare a fresh private capsule using --prepare --packet <new-private-path> --foundation-receipt <successful-private-journal>. Preparation is read-only for production. Review the resulting digest, native preimages and bound sources; publish the concrete execution/recovery packet before asking for attended execution. The actual --execute and --recover calls then use that retained --packet and its exact --expect-digest. No live digest or execution instruction is claimed ready here.