Skip to content

fix(compat): harden legacy purchase continuation recovery - #1060

Merged
bokelley merged 1 commit into
mainfrom
fix/legacy-continuation-recovery
Aug 21, 2026
Merged

fix(compat): harden legacy purchase continuation recovery#1060
bokelley merged 1 commit into
mainfrom
fix/legacy-continuation-recovery

Conversation

@bokelley

Copy link
Copy Markdown
Contributor

Summary

  • add revision-fenced, principal-scoped recovery for abandoned legacy purchase operations
  • make reservation/completion cancellation-safe and run synchronous executor/reconciler callbacks off the event loop
  • validate exact 2.5/3.0/3.1 success, error, submitted, working, and input-required results before durable replay
  • bind the complete buyer-visible product/pricing projection and actual peer replay guarantee into continuations
  • harden SQLite parent/path/sidecar ownership, CAS transitions, thread snapshots, migrations, and purge locking

Root cause

The initial continuation ledger could durably reserve a seller mutation without leaving a public, fenced recovery path. It also treated arbitrary executor mappings as success, retained only seller-observed pricing rather than the buyer-visible projection, inferred replay safety too broadly, and reopened SQLite through a pathname whose parent was not validated.

Migration behavior

Pre-hardening ledgers migrate in place. Missing execution snapshots can be adopted only by an exact retry, which increments the operation revision. Because old ledgers did not retain the buyer-visible pricing subset or a verified 3.x replay guarantee, unresolved rows remain non-executable; already-terminal results still replay and authoritative reconciliation can still recover an applied result.

Validation

  • uv run pytest tests/ -q — 6,835 passed, 40 skipped, 9 deselected, 1 xfailed
  • focused continuation suite — 79 passed
  • Black, Ruff, mypy, adopter type fixtures, Bandit, and repository pre-commit hooks passed
  • independent protocol review: release-ready, no findings
  • independent security review: release-ready, no blocking findings

Follow-up to #1059.


async def get_operation(
self, operation_id: str, *, principal_id: str
) -> CompatibilityPurchaseOperation | None: ...
self, operation: CompatibilityPurchaseOperation
) -> CompatibilityPurchaseOperation:
"""CAS ``IN_FLIGHT`` to ``AMBIGUOUS`` using the operation revision."""
...
except asyncio.CancelledError:
# Let the durable CAS settle so cancellation cannot strand an unknown
# transition between seller completion and local persistence.
await task
task.cancel()
store.release.set()
with pytest.raises(asyncio.CancelledError):
await task

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ladon verdict: Approve

Approve — hardening follow-up to #1059 on the legacy purchase continuation coordinator; no blocking findings.

Checked against the decision table top-down:

  • No no-auto-approve team match (HARD RULE clear).
  • gated_paths: false, so row 2 does not apply despite review_decision REVIEW_REQUIRED.
  • high_risk: true, but both reasons are (modified) files with no medium-or-higher finding — no escalation (rows 3–5 do not fire).
  • No prior decision; no sticky escalation.
  • Zero medium findings, so row 8 does not fire.
  • Falls through to row 9 → approve.

What the reviewer verified: revision-fenced principal-scoped recovery API, cancellation-safe reservation/completion, off-loop sync callbacks, exact source-version result validation before durable replay, buyer-visible pricing projection binding, and SQLite parent/path/ownership/purge hardening. CAS/migration/recovery flows consistent against base and covered by 79 continuation tests. The one semver-prefix note is low and defused (module unreleased).

Nits (non-blocking):

  • src/adcp/compat/purchase_continuation.py:476 — new required params added to public exports without ! marker (module unreleased, no live break).
  • Migration fail-closed gate and lexical-path symlink rejection confirmed correct.

@bokelley
bokelley merged commit fb89aa0 into main Aug 21, 2026
27 checks passed
@bokelley
bokelley deleted the fix/legacy-continuation-recovery branch August 21, 2026 07:09
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