What to build
On the scope-matched standing-rule path, a failed gmail.create_draft is recorded twice: the shared executor appends draft.creation_failed and calls batch_failure (pipeline/approval_draft.rs:322-336), and the mediation error handler then re-appends action.dispatch_failed and calls batch_failure again for the DispatchError::Connector that dispatch_scoped_effect synthesised from the executor's ConfirmedFailure/NotAttempted disposition (api/scoped_admission.rs:542-552, api/actions.rs:1286-1331). The interactive approval path records it once because it discards the executor outcome (pipeline/post_approval.rs:80). Result: duplicate owner-digest rows and path-dependent audit vocabulary for one failure; a pre-effect NotAttempted refusal (recipient changed) is also mis-filed as a Connector-class failure.
Make the record-once decision key off the typed disposition, the same way settlement already does: the executor self-audits and self-batches for its own dispositions, so the mediation error handler must not re-audit/re-batch a scoped effect whose executor owns its record.
Acceptance criteria
- One failed scoped draft produces exactly one owner-digest row and one audit row; parity test: scoped ConfirmedFailure and interactive-approval ConfirmedFailure produce the same digest/audit count.
- Digest-row and audit-row count assertions added to
scoped_admission_outcome_tests.rs::failure_after_attempt_cancels_reservation plus a NotAttempted (target-mutated) case pinning the record shape.
- Pre-effect NotAttempted refusals are not batched as Connector-class failures.
Blocked by
Nothing (coordinate with #216's settlement-seam landing — same surface).
Original report
Effect Truth architecture-review checkpoint (run_5eb4c103537d), finding F1/F2; serves the Auditor + owner digest accuracy per the DIRECTION.md decision test.
What to build
On the scope-matched standing-rule path, a failed
gmail.create_draftis recorded twice: the shared executor appendsdraft.creation_failedand callsbatch_failure(pipeline/approval_draft.rs:322-336), and the mediation error handler then re-appendsaction.dispatch_failedand callsbatch_failureagain for theDispatchError::Connectorthatdispatch_scoped_effectsynthesised from the executor'sConfirmedFailure/NotAttempteddisposition (api/scoped_admission.rs:542-552,api/actions.rs:1286-1331). The interactive approval path records it once because it discards the executor outcome (pipeline/post_approval.rs:80). Result: duplicate owner-digest rows and path-dependent audit vocabulary for one failure; a pre-effectNotAttemptedrefusal (recipient changed) is also mis-filed as a Connector-class failure.Make the record-once decision key off the typed disposition, the same way settlement already does: the executor self-audits and self-batches for its own dispositions, so the mediation error handler must not re-audit/re-batch a scoped effect whose executor owns its record.
Acceptance criteria
scoped_admission_outcome_tests.rs::failure_after_attempt_cancels_reservationplus a NotAttempted (target-mutated) case pinning the record shape.Blocked by
Nothing (coordinate with #216's settlement-seam landing — same surface).
Original report
Effect Truth architecture-review checkpoint (run_5eb4c103537d), finding F1/F2; serves the Auditor + owner digest accuracy per the DIRECTION.md decision test.