Build durable close evidence outbox

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a02b6f-7db1-7222-918b-e813a6bda38d
This commit is contained in:
tegwick 2026-08-23 14:05:50 +02:00
parent 9866f35b30
commit c97e5ea92d
5 changed files with 978 additions and 0 deletions

View file

@ -0,0 +1,69 @@
# Close-evidence outbox
Status: **durable core implemented, not connected to the live claim loop**.
The outbox preserves one bounded Activity Core completion or failure intent
after repository work. Replaying an outbox entry calls only the queue close
adapter; it never dispatches a model, invokes an approach, or repeats repository
mutation.
## Storage and identity
The default root is:
```text
$REIN_AHARNESS_STATE_DIR/close-outbox/
```
When that variable is absent, the harness uses
`$XDG_STATE_HOME/rein-aharness` or `~/.local/state/rein-aharness`. State lives
outside target checkouts. Outbox directories are mode `0700`; lock and JSON
files are mode `0600`.
An entry is keyed by the digest of the bounded Activity Core `run_id` and
repository `transaction_id`. Re-enqueuing the identical intent returns its
existing pending or delivered state. Reusing that identity for a different
action, result, error, or reopen decision is a conflict and is refused.
The v1 close intent contains:
- `action`: `complete` or `fail`;
- bounded normalized JSON `result` evidence;
- a caller-sanitized bounded error and explicit `reopen` decision for failures;
- no prompt, provider body, credential, raw tool stream, or workload callback.
Results allow only finite JSON values, bounded depth, collection size, string
length, integer range, key length, and a total encoded size of 64 KiB.
## Durability and replay
Enqueue and retry-state updates use a same-directory temporary file, file
`fsync`, atomic replace, and directory `fsync` under a process-safe `flock`.
The retry attempt is durable before the delivery callback begins.
Normal callback return marks the entry delivered and atomically moves it from
`pending/` to `delivered/`. An ordinary exception retains it in `pending/` with
only the exception class and a generic failure marker; exception text is not
persisted. Process interrupts propagate; the already-recorded attempt remains
pending. Invalid, oversized, or directory/state-mismatched records are moved
intact to `quarantine/` with a bounded reason sidecar and are never delivered
automatically.
A network timeout may occur after Activity Core accepted a close. Retrying the
same close intent can therefore repeat the queue mutation request, but cannot
repeat repository workload. Activity Core remains authoritative for terminal
row state and must provide idempotent or reconcilable close semantics.
## Activation gates
The live claim loop still returns an in-memory failure when close delivery
fails. Connecting it to this outbox requires:
1. the lease-bound repository transaction and accepted-result envelope;
2. enqueue-before-close using the same run and transaction identities;
3. Activity Core review of repeat close behavior for already-terminal rows;
4. startup/periodic replay that never calls workload code; and
5. operator status, quarantine inspection, and replay controls.
Until those gates are implemented and reviewed, no production close behavior
changes.