Story-3325 · the delivered artifact is applied · pramaan-functions

The letter arrived. Nobody opened it.

Gate 1 proved delivery, and delivery is a real thing to have proved: Blake writes a result, the object lands under agent-runs/, the verifier recomputes its digest, the run reaches delivered, a replayed PUT is refused 412. Every one of those held.

And the document sat at registering.

agent_results_consumer persisted the verified effects into agents.agent_run_artifact_effect and emitted one agent_run.artifact_delivered outbox event. Nothing read either. grep -rl agent_run_artifact_effect src/ returned the table definition and its writer, and that was the whole list.

So a successfully classified document moved no product state at all. No tool intent ran, so nothing was named or tagged. No terminal registration_status was stamped, so counsel's screen said "registering" indefinitely. No IB-3 inbox item was minted, so a side conflict or a privilege suspicion went unraised. No understanding fan-out fired, so there was no card, no sections, no brief. This Story is the part that opens the letter.

Why it was ever like this

Because Bug-3323 removed the thing that used to do it. The registration consumer once held Blake's invoke stream open and folded it — intents, verdict, status, items, fan-out, all inline, all while the socket was still up. Blake has no streaming route left: since Story-2958 every result mode goes through start_artifact_run, which refuses an invocation carrying no result_delivery and answers a valid one with {"run_id", "status": "accepted"}. The broker mints, dispatches and returns.

Bug-3323 deleted the fold and left the four behaviours it drove standing in the file, unreachable, with a note pointing at this Story. That note was honest — the state was inert, not "retired" while still running — but inert is not shipped.

The delivered artifact cannot say what it classified

This is the fact the whole design turns on, and it is not obvious. registrar.classification.v1 is a TurnEffectData:

class TurnEffectData(ContractModel):
    """A versioned registrar result; token usage deliberately lives outside effects."""

    turn: dict[str, Any]

A turn, and nothing else. No document_id — and Blake's RegistrarTurn does not carry one either, because the broker supplies identity and the brain never gets to choose it. That is the right contract. It also means a delivered classification, sitting in the database as verified truth, cannot name the document it is about.

The run row can. The broker knew the document when it minted the run. So agents.agent_run gains document_id, written at dispatch by write_pending_artifact_run. A second thing falls out of that for free: _persist_effects skips the D19 staleness check for any effect that names no target, so every classify effect ever was in the always-apply branch. It now has an ordering surface like every other document-scoped effect.

The settlers cannot apply anything, and that is not a bug

The obvious design is to apply the effects where they are verified. It cannot work, and the reason is in Terraform rather than in Python.

LambdaWhat its role holds
agent-results-consumer · agent-run-sweeper runs-bucket read · VPC ENI · logs · one SSM read
document-registration-consumer AgentCore invoke · documents bucket · the graph · the enrich queue · the registrar DB scope

Applying a classification means execute_tool_intent under policy and RLS, the registration_status machine, the IB-3 producers and the CE-7 fan-out. That is the second row, not the first. Doing it in the settler would mean granting a second identity the whole fan-out's authority — the opposite of what R6 asks for.

The door that already existed

No new queue, no new bus, no new status store, no retry framework. Both settlers write ONE outbox row, inside the transaction that settles the run, and the relay that already drains registration and resegment rows to the registrar queue drains this one too.

  results consumer ─┐
                    ├─▶ ops.document_ingest_outbox        (existing table, 5th event type)
  sweeper ──────────┘             │
                                  ▼
                    registration relay                   (existing, tuple filter)
                                  │
                                  ▼
                    registrar SQS queue                  (existing)
                                  │
                                  ▼
                    process_artifact_delivered            (new route, same handler)
                      ├─ _execute_intents
                      ├─ _finalize_registration  (status + IB-3)
                      └─ _fan_out

The consumer already routes by event_type: registration, resegment, and now document.artifact.delivered. The relay already takes a tuple. The only new things are a migration widening a CHECK, one column carrying the run id, and one function reading the effect rows.

One detail worth naming: the apply ticket is deliberately NOT added to the immediate-dispatch map. Its producer has no SQS grant at all, so the scheduled relay tick is the delivery path — up to a minute of latency, never a lost apply, because the row is durable and undispatched until the relay sends it.

Exactly-once, three deep, and the outermost is the database's

  1. The ticket's key is the RUN. document_artifact:<run_id> with ON CONFLICT DO NOTHING, so a replayed artifact resolves to the same row and enqueues nothing twice. A genuine re-registration mints a new run and is entitled to its own ticket.
  2. The document row is locked, and the lock covers everything. SQS is at-least-once and the sweeper's ticket can race the delivery's, so two appliers can both read registering. _claim_terminal takes FOR UPDATE, and the whole apply — intents, status, IB-3 items — runs inside that one transaction. The loser blocks, wakes to a terminal status, and executes nothing at all.
  3. The old guards still stand. The executor's (document_id, extraction_run_id) claim and the inbox dedup key, exactly as the inline path had them — now the backstop rather than the front line.

The first cut ran each intent in its own transaction, outside the lock, on the reasoning that "one effect committing does not depend on the next" and that a redelivery would be harmless because the tools are upserts. Review caught it: that argument is about SEQUENTIAL redelivery and does not survive two CONCURRENT deliveries, where both passes execute every intent before either reaches the lock and claim_registration lets the same run proceed. It also meant a mid-list refusal committed the intents before it. Per-intent transactions were right when a broker held a stream and a long run could die halfway. On this path they were wrong twice.

And one level up, because not creating the conflict is a better answer than arbitrating it: the registering re-drive edge used to mint a SECOND Blake run over the same extraction, so both delivered, both ticketed, and whichever settled first took the document — the older classification could beat the newer one. A dispatch now stands down while a run is still inside its delivery window. Past deadline_at the sweeper settles it, its ticket stamps the terminal failure, and the retry edge re-queues. One paid Blake pass per registration instead of two.

That is prevention, and prevention alone was not enough — which review said plainly and the first cut of this Story did not believe. The window it leaves open is the one that matters: past deadline_at the older run A is no longer "in flight", so a redelivery legitimately mints newer run B before the sweeper has got to A. A's late artifact then settles first, finds no persisted effect to lose to, tickets, and wins the document; B arrives to a terminal document and is dropped as a duplicate. The same inversion in its worse dress: the sweeper settles the lapsed A, and A's expiry ticket stamps registration_failed on a document B is at that moment classifying perfectly well.

So the arbitration is against newer RUNS, not against effects that happened to arrive first. The freshness guard in _persist_effects compares one effect against effects already persisted for the document — it can only ever see runs that arrived, so arrival order silently stands in for run order. The document-level guard lives instead at write_artifact_apply_ticket, the ONE door all three settlers pass through (the results consumer's success, its refusal, the sweeper's expiry): a run that a newer run of the same agent family has superseded for the same document writes no ticket at all. Whatever it settled to stays on the run row as RUN truth; it is simply not allowed to become DOCUMENT truth over a fresher attempt. The predicate is (firm, document, agent_key) — scoped by family on purpose, so a document's card pass can never starve the classification it is downstream of.

Both hot reads on that column — the dispatch's "is one already in flight" and the settle's "has a newer one superseded me" — get an index in the same migration. agents.agent_run grows forever; unindexed they are two sequential scans per document.

No branch can leave a document at registering

That sentence is the acceptance criterion, so it is worth listing what "no branch" covers:

What happened to the runWhat the document gets
delivered, all_clearregistered, intents executed, fan-out fired
delivered, needs_reviewregistration_needs_review + the blocking IB-3 item
delivered, both advisory holds open (I-3)escalated to registration_needs_review
delivered, clean, but a privilege hold is openregistered, fan-out HELD and logged (I-11)
brain reported a failureregistration_failed + the brain's error class
artifact refused or quarantinedregistration_failed + the verifier's sentence
swept — nothing ever arrivedregistration_failed + the sweeper's sentence
a turn this broker cannot readregistration_needs_review (the default), never a crash loop

Each failure repeats the settler's own lawyer-safe message onto the document, so the reason is visible where counsel looks rather than only in a run row nobody opens.

How we know it works

Every assertion was watched red before it was watched green, by planting the revert and running the test:

Planted revertGoes red
settle stops writing the tickettest_the_results_consumer_settle_writes_the_apply_ticket
sweeper stops writing the tickettest_the_sweeper_tickets_the_document_it_would_have_stranded
event type dropped from the relay filtertest_the_apply_ticket_rides_the_existing_registration_relay
D19 fallback removedtest_the_results_consumer_settle_writes_the_apply_ticket
the applier returns skipped (the pre-Story state)11 unit tests
_claim_terminal no longer consultedtest_the_row_lock_is_what_stops_two_tickets_racing
unapplicable delivery stops stamping a terminal3 × test_an_unapplicable_delivery_stamps_a_named_terminal_failure
a typed tool refusal propagates instead of terminatingtest_a_refused_tool_intent_stamps_a_terminal_failure_not_a_retry_loop
the intents are hoisted back outside the locktest_the_intents_run_inside_the_locked_transaction AND test_two_concurrent_applies_execute_the_intents_exactly_once
_claim_terminal drops .with_for_update() and becomes a plain readtest_the_document_row_lock_really_blocks_the_second_apply
the intent retype is removed (an invalid intent escapes as ValidationError)test_an_intent_too_malformed_to_build_stamps_a_terminal_failure, test_a_deterministic_executor_raise_terminates_rather_than_retries
the terminal door is widened to swallow SQLAlchemyError tootest_a_database_failure_mid_apply_still_retries_and_stamps_nothing
a second run is minted while one is in flighttest_a_redelivery_does_not_mint_a_second_run_while_one_is_in_flight
the supersession guard is removed from the ticket doortest_a_superseded_run_writes_no_apply_ticket
the agent_key predicate is dropped from ittest_a_newer_run_of_another_family_supersedes_nothing
the document_id predicate is dropped from ittest_a_newer_run_for_ANOTHER_document_supersedes_nothing

The four database-level ones are not modelled races — they are two real transactions on one Postgres row, and the lock proof only passes because the second transaction genuinely blocks in the database and wakes to what the first committed. A _claim_terminal that had quietly become a plain SELECT passes every unit test in this Story and fails that one.

The replay proof counts rows in the database after two independent settles, not calls into a mock. A test that asserted "the writer was called once" would pass with the unique constraint removed.

What this Story does NOT close

The card and the sections still do not complete. The fan-out fires, and its card and segment legs stand down — because src/shared/context/card_pass.py and src/shared/context/segmentation.py still invoke Blake with accept="text/event-stream", and Blake has refused that on every mode since Story-2958. The card leg reports card_pass_skipped outcome=no_card_artifact; segmentation falls back to its flat D13 section. Recorded, not silent — but not complete. Filed as pramaan-spec Bug-3354, functions lane, under this Story's own Epic-3286 — a named ticket with an owner, not a paragraph in a walkthrough nobody re-reads.

Fixing it means moving both passes onto the artifact rail, which turns a synchronous seven-step fan-out into a chained applier across three artifact deliveries, and touches the CE-18 backfill and the D30 re-read door as well. This Story is deliberately shaped to be what that chains off: the apply dispatch is a schema lookup, so document.card.v1 and document.sections.v1 appliers land there and nowhere else, and write_pending_artifact_run(document_id=...) already gives their runs a projection target.

Four more callers are dead the same way and, as of 2026-08-03, unticketed: insight_brief.py, understanding_pass.py, deep_read_orchestrator/handler.py, clerk_worker/runner.py.

And what is deleted

_StreamResult, _StreamFailure and _persist_production_effects are gone in the same PR, along with the UNREACHABLE banner and the not_yet_applied / awaits: Story-3325 fields on the dispatch log line. The production-effects block was unreachable twice over: the classify route never emitted a family or a card event either. Nothing stays temporary.