Skip to content

feat(runtime): explicit transaction outcome taxonomy + effect journal + idempotency (Section 3)#259

Merged
abrichr merged 1 commit into
mainfrom
agent/txn-outcome-taxonomy-section3
Jul 26, 2026
Merged

feat(runtime): explicit transaction outcome taxonomy + effect journal + idempotency (Section 3)#259
abrichr merged 1 commit into
mainfrom
agent/txn-outcome-taxonomy-section3

Conversation

@abrichr

@abrichr abrichr commented Jul 26, 2026

Copy link
Copy Markdown
Member

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 in execution_profiles.stamp_execution_outcome, plus ActionDeliveryUncertainty from #250 and an independent EffectVerifier. This PR refines that outcome; it does not replace it.

New: openadapt_flow/transaction.py (leaf module — 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 so no information is lost).
  • classify_transaction_outcome — refines the coarse outcome. Any unresolved uncertain/conflicting delivery or persistence dominatesRECONCILIATION_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 FAILEDCANCELED or FAILED_PLATFORM.
  • is_billable / is_production_success / is_platform_faultFAILED_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; everything else routes to reconciliation.

Mapping from old results

coarse execution_outcome transaction_outcome
VERIFIED VERIFIED
ROLLED_BACK ROLLED_BACK
COMPLETED_UNVERIFIED COMPLETED_UNVERIFIED (or RECONCILIATION_REQUIRED if uncertainty)
HALTED RECONCILIATION_REQUIRED | REJECTED_POLICY | HALTED_BEFORE_EFFECT
FAILED CANCELED | FAILED_PLATFORM

Backward compatibility

execution_outcome, success, production_eligible, and outcome_envelope are unchanged. Every new field is additive/optional with defaults that preserve old behavior; old reports round-trip. Replayer gains an optional idempotency_ledger and run(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_REQUIRED and the runtime does not re-actuate (verified via the existing uncertain-delivery harness: one actuation, one verify call).

Scoped out (follow-ups)

  • Full saga compensation steps
  • The human-reconciliation-task UI
  • Cloud/runner propagation of the taxonomy (usage/billing surfaces)

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_REQUIRED does not auto-retry; COMPLETED_UNVERIFIED/FAILED_PLATFORM flagged non-billable/non-success. Full mypy (strict, 200 files), ruff, python paper/check_artifacts.py, and scripts/check_consistency.py pass; existing test_execution_profiles / test_uncertain_delivery / test_replayer / test_durable_runtime remain green.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NyCHrzA1psrKMFfroYbzaM

… + 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
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