feat(runtime): consume governed Activity Core closes

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a06ba0-10aa-7ea0-b20a-4f3fac39efe9
This commit is contained in:
tegwick 2026-09-04 19:54:07 +02:00
parent 0e6d795aa4
commit d00ffcb402
22 changed files with 1218 additions and 148 deletions

View file

@ -1,6 +1,6 @@
# Close-evidence outbox
Status: **durable core implemented, not connected to the live claim loop**.
Status: **active for profiled Activity Core terminal closes**.
The outbox preserves one bounded Activity Core completion or failure intent
after repository work. Replaying an outbox entry calls only the queue close
@ -21,9 +21,10 @@ 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.
repository `transaction_id` and retains the original worker identity.
Re-enqueuing the identical intent returns its existing pending or delivered
state. Reusing that identity for a different worker, action, result, error, or
reopen decision is a conflict and is refused.
The v1 close intent contains:
@ -41,7 +42,8 @@ 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
Normal callback return, including Activity Core `close_disposition=applied` or
`reconciled`, 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
@ -49,21 +51,29 @@ 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.
Activity Core refusal codes `not_found`, `wrong_owner`, `expired_lease`,
`state_conflict`, `evidence_conflict`, and `terminal_conflict` are conclusive,
not transient. Such entries move to quarantine rather than retrying forever.
An unclassified HTTP failure or transport outage remains pending.
## Activation gates
A network timeout may occur after Activity Core accepted a close. The worker
replays pending close-only work before it claims another row. Retrying the same
intent can repeat the queue request, but cannot repeat repository workload;
Activity Core reconciles the exact normalized terminal repeat.
The live claim loop still returns an in-memory failure when close delivery
fails. Connecting it to this outbox requires:
## Operator controls
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.
```bash
rein-aharness close-outbox status
rein-aharness close-outbox replay
rein-aharness close-outbox replay --limit 10
```
Until those gates are implemented and reviewed, no production close behavior
changes.
`status` and `replay` return non-zero while pending or quarantined material
exists, making them suitable for readiness/recovery checks. Quarantine content
and its bounded reason sidecar remain under the private state path for explicit
operator inspection; it is never silently deleted or treated as delivered.
Profile-absent compatibility closes are not yet outboxed. Granted rows without
an authoritative `harness_profile_ref` are refused before execution so they
cannot cross that weaker path.