Story-1719 Recovery (b/2) — fail-honest · undo-as-write · never-fake-success

RFD-AGT-002 D-10. This is the brain-side half of the failure-&-recovery contract (the agents sibling of the functions Recovery a/2). The broker owns the control plane — it mints the idempotency key, re-GETs row state at confirm time, executes a write exactly once, and reports back one typed outcome (ADR-110: the broker owns all safety machinery exactly once). The brain's only job after a write is to turn that outcome into honest counsel-facing copy and, on request, propose an undo. All of it is pure, offline, no model call.

The outcome vocabulary (recovery.WriteOutcome)

OutcomeMeaningRecovery action
appliedSucceeded and ackedconfirm_applied (offer undo)
failedDefinitively did not apply — zero rows changedreport_failed
ambiguousApplied-but-unacked ("maybe-applied")surface_ambiguous (a GET resolves it)

ambiguous is a first-class outcome, deliberately not folded into failed — treating a maybe-applied write as failed is exactly the silent-retry / duplicate-filing trap D-10 exists to prevent.

AC1 — On rail/tool failure: honest message, zero fabricated rows

AC2 — Undo runs as a fresh idempotent write that revalidates current state

AC3 — Ambiguous outcome surfaced ("may have applied — here's how to check")

Files

Scope note: this delivers and pins the behaviour contract; wiring it into the live operation turn grows with the forums slice (D-11) — exactly as Story-1722 delivered the command vocabulary ahead of full wiring. WriteProposal is the propose_operation wire intent named (not yet modelled) in command_vocabulary.IntentClass.

Verification