Skip to content

feat: renew and cancel saved incoming arrivals (TECH-681 plan 010) - #27

Open
Ashex wants to merge 1 commit into
tech-681/public-handoff-provisioningfrom
tech-681/saved-user-lifecycle
Open

Ashex wants to merge 1 commit into
tech-681/public-handoff-provisioningfrom
tech-681/saved-user-lifecycle

Conversation

@Ashex

@Ashex Ashex commented Nov 12, 2026

Copy link
Copy Markdown
Contributor

Part of TECH-681. Stacked on #23. Completes the saved-user lifecycle slice of plan 010.

What changes

composeIncomingArrival gains two owner-checked callables, renewArrival and cancelArrival. They stay dormant: no routes are mounted, and nothing is operator-visible, so there is no Changeset.

Renewal rotates the one-time arrival code for the same arrival, so a lost preparation response is recovered without another allocation.

  • Refused with ArrivalExpired once the arrival is cancelled or provisioned, and with OperationRecoveryRequired while its allocation is uncertain.
  • Never reserves, signs, dispatches or releases anything. Only the code hash is stored.

Cancellation is limited to arrivals with no persisted obligation, no pending external attempt and an unchanged canonical PLC head. A prepared arrival always holds a reservation, so it cannot be cancelled. Cancel is idempotent, including after a crash between the local cancel and release of the DID claim.

Both require origin/CSRF checks, a browser session and a fresh email code. An unknown operation and another user's operation return the same 404 after the same proof work.

Changes to previously reviewed behaviour

These two points touch code accepted in #22/#23 and need review from both developers.

  1. Admissions survive renewal. The handoff and creation continuations required the admission's code generation to equal the current one. They now accept the generation recorded at admission (between 1 and the current generation). This follows C1, which refuses renewal only "once provisioned". A new admission still has to present the current code.
  2. A stale code fails the same way everywhere. At admission, a superseded, unknown or expired code now fails with ArrivalExpired before any request comparison, whether or not an admission exists for that body. Previously an old code got AccountMismatch or OperationConflict depending on state, and an exact replay of an accepted admission succeeded without a valid code. Exact replay now needs the current, unexpired code.

After a renewal, re-submitting an already admitted body stays an OperationConflict (the request digest includes the code generation). Recovery goes through the saved continuations, which plan 012 will mount.

Decisions recorded

  • Fresh email code is required for renew and cancel as well as prepare, with separate incoming-renew and incoming-cancel budgets at the existing limits.
  • docs/incoming-migration.md describes the behaviour above and adds renewal to the C1 error table.

Validation

Run natively on Node 24 at this head:

  • npm run build, typecheck, check, lint, format:check, check:architecture, schema:check: exit 0.
  • npm run test:unit: 667 tests, 648 pass, 0 fail, 19 skipped.
  • tests/contracts/incoming-arrival-composition.test.mjs: 82 pass, 0 fail.
  • npm run test:incoming-reconciliation: 23 pass, 0 fail.
  • Mutation checks: reverting the generation check to strict equality, loosening it to always pass, disabling the admission code check, and disabling the cancel race mapping each make tests fail.

Not run locally: the PostgreSQL dialect contract run and the live, Atmosphere in a Box and browser suites. No database code changed; these are left to CI.

Known limits

  • The generation check in provision-arrival.ts is defensive only: continueCreation refuses first, so no test reaches it independently.
  • A method outside com.atproto.* passed to the internal admission callable now returns ArrivalExpired, not InvalidRequest. The plan 012 adapter must validate the method before calling it.

🤖 Generated with Claude Code

Expose owner-checked renewArrival and cancelArrival from the incoming
arrival composition so a lost preparation response can be recovered
without another allocation.

Renewal rotates the one-time arrival code for the same arrival and is
refused once the arrival is cancelled or provisioned, or while its
allocation is uncertain. Admissions already recorded survive renewal:
continuations accept the generation recorded at admission instead of
requiring the current one. A superseded, unknown or expired code now
fails admission with ArrivalExpired whether or not an admission exists.

Cancellation is limited to arrivals with no persisted obligation, no
pending external attempt and an unchanged canonical PLC head. Both
operations require origin/CSRF checks and a fresh email code.

The callables stay dormant; no routes are mounted.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Nov 12, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 29c4fe99-73cd-4c09-8ced-44341941888c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@sonarqubecloud

Copy link
Copy Markdown

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