Subscription activation can remain permanently in flight after a crash during commit
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 176
- Forks
- 66
- Avg merge
- 1d 1h
- Merged PRs (30d)
- 52
Description
Context
SubscriptionStore.activate() writes a non-expiring committingAt marker before persisting the subscription record and lookup index.
This protects a settled activation from being replaced by another activation, but a process exit during the following multi-key commit leaves the lookup locked indefinitely.
Reproduction
I added deterministic fault injection at two boundaries:
- Writing the subscription record
- Writing the lookup index
Reproduction commit: cc1e84b
In both cases:
create()completes and thecommittingAtmarker is persisted.- The selected store write fails.
- A new store wrapper simulates a process restart.
activationTimeoutMsis set to0.- Multiple fresh challenge IDs still return
inFlight. create()is never called again.- The original committing marker remains.
A record-write failure leaves no record or lookup. A lookup-write failure leaves an orphaned record with no resolvable lookup.
Why not simply expire the marker?
create() may already have completed the first-period payment. Clearing or expiring the marker can let another challenge call create() and charge again.
Persisting the result in the marker only covers failures after that write. It does not cover a crash after the external payment succeeds but before the result is persisted, especially for custom activation hooks.
Design question
What recovery guarantee should the store provide once settlement may have happened?
A safe implementation appears to require:
- A constrained, versioned activation journal
- Authoritative reconciliation when settlement may have succeeded
- CAS-based record and lookup materialization
- Fail-closed handling for conflicting lookup ownership
- Explicit handling for legacy committing markers
For automatic Tempo activation, could reconciliation use the challenge attribution written onchain? Should custom activation hooks receive an idempotency or reconciliation contract?
I have deterministic tests ready and can implement the agreed recovery semantics once this boundary is decided.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with SubscriptionStore.activate() and the deterministic fault-injection reproduction in commit cc1e84b. Review the failures at the subscription-record and lookup-index write boundaries, including the restart behavior with activationTimeoutMs set to 0. Done requires agreed recovery semantics for committing markers and tests covering the observed orphaned and permanently in-flight states.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- payments
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100