Skip to content

feat(delivery): durable delivery ledger, newest-format only - #484

Merged
schickling-assistant merged 1 commit into
mainfrom
schickling-assistant/2026-09-06-delivery-ledger
Sep 6, 2026
Merged

feat(delivery): durable delivery ledger, newest-format only#484
schickling-assistant merged 1 commit into
mainfrom
schickling-assistant/2026-09-06-delivery-ledger

Conversation

@schickling-assistant

Copy link
Copy Markdown
Contributor

What

One canonical module, src/delivery_ledger.rs, replaces the two per-driver single-binding delivery guards — CodexDeliveryState in src/codex_app_server.rs and DeliveryState in src/opencode_session.rs. The record is st2.delivery-ledger.v1 at <state-dir>/delivery-ledger.json, holding one entry per inbox filename.

The ledger owns persistence, phase grading, retry authority, binding isolation, validation, quarantine, and durable replacement. The drivers only translate their provider observations into Evidence; they no longer carry a delivery state machine each.

Retained core:

  • Profile::proves — a harness may only record a phase it can actually observe. Codex proves transportAccepted and consumed; OpenCode proves transportAccepted and persisted. A record claiming any other phase is refused at load.
  • the monotone phase lattice attempted < transportAccepted < persisted < consumed; record() never downgrades.
  • typed Evidence (TransportAccepted / Persisted / Consumed) as the only driver-facing vocabulary.
  • per-filename entries, so two messages on one binding no longer share a single record.
  • quarantine: a foreign, malformed, or self-inconsistent ledger holds delivery instead of re-sending, and is never rewritten.
  • archive reconciliation: prune(is_unread) keeps the recipient's inbox the settlement authority.

Accepted harm

At first open after upgrade, at most one in-flight pre-ledger attempt per seat may be re-sent once; the archive receipt keeps inbox settlement idempotent (decisions Q2/Q7, seat dev3.direct.omp.43sz6ujq). Legacy delivery-state.json files are ignored, not read or deleted.

That is the whole reason this is a from-scratch replacement rather than #471: no predecessor reader, no rollback floor, no adoption path, no writer gate, and no dead configuration axes. Writers emit the newest format only.

Changes against the reviewed #471 draft

Two findings from the #471 architecture review are fixed here rather than carried:

  • Per-entry correlation. feat(delivery): add durable delivery ledger #471's load() accepted an entry whose correlation.value was merely anchored by some other entry in the record, so an injected entry with an arbitrary filename could load clean and wedge the transport. Here every entry must rederive its own correlation from its own binding and filename (every_entry_must_validate_its_own_correlation).
  • Write hardening. Routing the ledger through a plain File::create would have dropped the create_new(true).mode(0o600) property the Codex record had, and shared one fixed .<pid>.tmp name between files. The atomic write now creates the temp exclusively at 0600 under a name unique per process and per write (the_persisted_ledger_is_owner_only_and_leaves_no_temp_residue).

The remaining #471 findings (rollback-floor ordering, floor retention until archive, adoption when a ledger file already exists) do not exist here: there is no floor and no adoption.

Known limits

  • One owning adapter process per state directory. There is no cross-process lock; two writers can race read/replace.
  • runtime_id rebinding is provenance, not a fence.
  • OpenCode can prove persistence but not consumption, so its entries hold until archive.

INVARIANTS

Adds Durable native delivery ownership, proven by eight tests in src/delivery_ledger.rs, including begin_persists_attempted_before_transport (the durable pre-transport write) and foreign_or_malformed_state_quarantines_without_rewriting.

Gate

nix build .#st2 --no-link -L (doCheck runs the cargo suite).

Replaces #471.

agent-identity: dev3.direct.omp.43sz6ujq

Posted on behalf of @schickling
field value
agent_identity dev3.direct.omp.43sz6ujq
session dev3.43sz6ujq
agent_persona generalist
agent_supervisor unavailable
agent_tool OMP
agent_tool_version 18.1.7
agent_runtime OMP 18.1.7
tooling_profile dotfiles@39a19af

Replace the two per-driver single-binding `delivery-state.json` guards
(`CodexDeliveryState` in `src/codex_app_server.rs`, `DeliveryState` in
`src/opencode_session.rs`) with one canonical module, `src/delivery_ledger.rs`,
holding `st2.delivery-ledger.v1` at `<state-dir>/delivery-ledger.json`.

The ledger owns persistence, phase grading, retry authority, binding
isolation, validation, quarantine, and durable replacement. Codex and
OpenCode only translate their provider observations into `Evidence`.

Kept from the design: `Profile::proves`, the monotone phase lattice
(attempted < transportAccepted < persisted < consumed), typed `Evidence`,
per-filename entries, quarantine, and archive reconciliation via `prune`.

Dropped deliberately: every predecessor reader, rollback floor, adoption
path, and dead configuration axis. Writers emit the newest format only.

Load validation is stricter than the reviewed draft: every entry must
rederive its own correlation from its own binding and filename, so an
injected entry that merely shares another entry's correlation value can no
longer wedge a transport. The atomic write also keeps the `0600`
`create_new` hardening the Codex record had, under a temp name unique per
process and per write.

agent-identity: dev3.direct.omp.43sz6ujq
agent-persona: generalist
agent-supervisor: unavailable
agent-tool: OMP
agent-tool-version: 18.1.7
agent-runtime: OMP 18.1.7
tooling-profile: dotfiles@39a19af
@schickling-assistant
schickling-assistant force-pushed the schickling-assistant/2026-09-06-delivery-ledger branch from eb39b71 to 1116c4c Compare September 6, 2026 12:19
@schickling-assistant
schickling-assistant merged commit 29b5e7c into main Sep 6, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant