feat(runtime): explicit transaction outcome taxonomy + effect journal + idempotency (Section 3)#259
Merged
Merged
Conversation
… + idempotency (Section 3) Replace the ambiguous success/halt/failure model with a first-class terminal transaction outcome that states what is known about the BUSINESS EFFECT, plus a per-step effect journal and caller-supplied idempotency. Builds on PR #250's "verify uncertain delivery without retry" behavior rather than duplicating it. New openadapt_flow.transaction module (leaf; reads only typed report fields): - TransactionOutcome enum: VERIFIED, HALTED_BEFORE_EFFECT, RECONCILIATION_REQUIRED, FAILED_PLATFORM, CANCELED, REJECTED_POLICY, COMPLETED_UNVERIFIED (plus ROLLED_BACK carried through 1:1). - classify_transaction_outcome: refines the coarse execution_outcome. Any unresolved uncertain/conflicting delivery or persistence dominates and yields RECONCILIATION_REQUIRED (never claim "no effect", never blind-retry). A coarse HALTED otherwise splits into REJECTED_POLICY (governed/identity refusal) vs HALTED_BEFORE_EFFECT (verifier established absence); a coarse FAILED maps to CANCELED or FAILED_PLATFORM. - is_billable / is_production_success / is_platform_fault: FAILED_PLATFORM is never a billable success; COMPLETED_UNVERIFIED is never a production success. - build_effect_journal + EffectJournalEntry: PHI-free per-consequential-step ledger (intended contract hashes, attempt state, observed effect, verifier freshness, collateral reconciliation), persisted in report.effect_journal. - IdempotencyLedger: file-backed at-most-once store; a repeat under the same key is suppressed before any actuation. Honesty linchpin: EffectVerdict.observed_effect + a new observed_effect field on EffectVerificationEvidence distinguish a verifier-established absence (no write) from a conflicting/uncertain write, so HALTED_BEFORE_EFFECT is only claimed when nothing landed. Backward compatibility: execution_outcome, success, production_eligible, and outcome_envelope are unchanged; every new field is additive/optional and defaults preserve old behavior. stamp_execution_outcome additionally stamps the transaction fields. Replayer gains an optional idempotency_ledger + run() idempotency_key; with neither set the run path is unchanged. Scoped out (follow-ups, noted in docs): full saga compensation steps, the human-reconciliation-task UI, and Cloud/runner propagation of the taxonomy. Tests: tests/test_transaction_outcome.py (25) covers each outcome from its own condition, duplicate suppression, RECONCILIATION_REQUIRED no-auto-retry via the #250 path, and the non-billable/non-success flags. Full mypy + ruff + paper artifacts + consistency gates pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NyCHrzA1psrKMFfroYbzaM
This was referenced Jul 26, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements roadmap Section 3 (transaction + reconciliation semantics) in the Flow runtime: replaces the ambiguous success/halt/failure model with explicit terminal transaction outcomes that describe what is known about the business effect, plus a per-step effect journal and caller-supplied idempotency. This is the runtime foundation only — it extends the existing runtime and builds on PR #250 ("verify uncertain delivery without retry") rather than duplicating or reverting it.
DO NOT MERGE without review.
What exists today (extended, not forked)
The runtime already had a coarse evidence outcome (
RunReport.execution_outcome:VERIFIED/COMPLETED_UNVERIFIED/HALTED/FAILED/ROLLED_BACK), centrally stamped inexecution_profiles.stamp_execution_outcome, plusActionDeliveryUncertaintyfrom #250 and an independentEffectVerifier. This PR refines that outcome; it does not replace it.New:
openadapt_flow/transaction.py(leaf module — reads only typed report fields)TransactionOutcomeenum:VERIFIED,HALTED_BEFORE_EFFECT,RECONCILIATION_REQUIRED,FAILED_PLATFORM,CANCELED,REJECTED_POLICY,COMPLETED_UNVERIFIED(plusROLLED_BACKcarried through 1:1 so no information is lost).classify_transaction_outcome— refines the coarse outcome. Any unresolved uncertain/conflicting delivery or persistence dominates →RECONCILIATION_REQUIRED(never claim "no effect", never blind-retry). A coarseHALTEDotherwise splits intoREJECTED_POLICY(governed/identity refusal) vsHALTED_BEFORE_EFFECT(verifier established absence); a coarseFAILED→CANCELEDorFAILED_PLATFORM.is_billable/is_production_success/is_platform_fault—FAILED_PLATFORMis never a billable success;COMPLETED_UNVERIFIEDis never a production success.build_effect_journal+EffectJournalEntry— PHI-free per-consequential-step ledger (intended contract hashes, attempt state, observed effect, verifier freshness, collateral reconciliation), persisted inreport.effect_journal.IdempotencyLedger— file-backed at-most-once store; a repeat under the same key is suppressed before any actuation.Honesty linchpin
EffectVerdict.observed_effect+ a newobserved_effectfield onEffectVerificationEvidencedistinguish a verifier-established absence (no write) from a conflicting/uncertain write, soHALTED_BEFORE_EFFECTis only claimed when nothing landed; everything else routes to reconciliation.Mapping from old results
execution_outcometransaction_outcomeVERIFIEDVERIFIEDROLLED_BACKROLLED_BACKCOMPLETED_UNVERIFIEDCOMPLETED_UNVERIFIED(orRECONCILIATION_REQUIREDif uncertainty)HALTEDRECONCILIATION_REQUIRED|REJECTED_POLICY|HALTED_BEFORE_EFFECTFAILEDCANCELED|FAILED_PLATFORMBackward compatibility
execution_outcome,success,production_eligible, andoutcome_envelopeare unchanged. Every new field is additive/optional with defaults that preserve old behavior; old reports round-trip.Replayergains an optionalidempotency_ledgerandrun(idempotency_key=...); with neither set the run path is byte-for-byte unchanged.No blind retry (builds on #250)
When delivery was uncertain and the complete contract did not resolve it, the outcome is
RECONCILIATION_REQUIREDand the runtime does not re-actuate (verified via the existing uncertain-delivery harness: one actuation, one verify call).Scoped out (follow-ups)
Tests
tests/test_transaction_outcome.py(25 tests): each terminal outcome from its own condition; idempotency suppresses duplicate actuation (fresh backend records no action);RECONCILIATION_REQUIREDdoes not auto-retry;COMPLETED_UNVERIFIED/FAILED_PLATFORMflagged non-billable/non-success. Fullmypy(strict, 200 files),ruff,python paper/check_artifacts.py, andscripts/check_consistency.pypass; existingtest_execution_profiles/test_uncertain_delivery/test_replayer/test_durable_runtimeremain green.🤖 Generated with Claude Code
https://claude.ai/code/session_01NyCHrzA1psrKMFfroYbzaM