refactor(gate): fold grants.ts's inline KeyedMutex into the shared one (#140) - #169
Open
TheBlackBit wants to merge 2 commits into
Open
refactor(gate): fold grants.ts's inline KeyedMutex into the shared one (#140)#169TheBlackBit wants to merge 2 commits into
TheBlackBit wants to merge 2 commits into
Conversation
#140) PR #103 (delegated payments) added a shared `ceremony/keyed-mutex.ts` and PR #135 (grants correctness) added a byte-for-byte equivalent inline `KeyedMutex` inside `grants.ts` — merged ~2h apart on 2026-07-28, so `main` carried both. Two copies of a concurrency control guarding money means a future hardening lands in one and silently misses the other. - Promote the file to `src/keyed-mutex.ts`. `ceremony/` is the home for authorization rails (passkey, dc-payment, credential-gate); a generic per-key serializer is not a rail, and `grants.ts` is not a ceremony. - Delete the inline copy in `grants.ts`; import the shared one. - Generalize the header to document BOTH call sites (order id for `completeOrder` #103, grant id for spends #104) and keep the in-process-only fencing. Behaviour is identical, with one improvement: the shared implementation drops a key's map entry once nothing is queued behind it, while the inline copy retained one entry per grant id for the process lifetime. Verified the guard is load-bearing rather than assumed: neutering `KeyedMutex.run` to `return fn()` fails 3 tests across both call sites (1 in completion.test.ts, 2 in grants.test.ts). Restored, full gate suite 484/484 green, `npm run build` clean. Closes #140 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Ever Morales <ever.morales@koombea.com>
…140) The shared `keyed-mutex.ts` header already documents both call sites and the in-process-only fencing; restating it at the import site duplicates what the dedup was meant to remove, and the surrounding imports carry no comments. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Ever Morales <ever.morales@koombea.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
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.
In plain terms
The same small piece of code existed twice. This deletes one copy and points both users at the other.
What you're approving
No behaviour change and no public API change — the class is internal, not exported. One improvement comes along for free: the deleted copy never cleared its internal map, keeping one entry per grant for the life of the process; the surviving copy clears entries once nothing is waiting.
How to test
484/484 pass, build clean.
For reviewers — the detail
Two pull requests each needed a per-key async serializer and merged ~2h apart on 2026-07-28: #103 added the shared
ceremony/keyed-mutex.ts, and #135 added an equivalent inlineKeyedMutexingrants.ts. Both serialize a read-check-write on the same key so two concurrent callers can't both pass an "already done?" check and each act — double-settle for orders, double-spend for grants.Three files:
ceremony/keyed-mutex.ts→keyed-mutex.ts(git-tracked rename).ceremony/is the home for authorization rails — passkey, dc-payment, credential-gate — and a generic serializer is not a rail, so it moves up a level now thatgrants.tsdepends on it too. Header generalized to document both call sites and keep the in-process-only fencing.grants.ts— inline class deleted, shared one imported.ceremony/completion.ts— import path updated.On the guard being load-bearing (CLAUDE.md: a test that still passes with the control removed is not a useful test). Verified rather than assumed — replacing
KeyedMutex.runwithreturn fn()fails 3 tests across both call sites:completion.test.ts— concurrent same-order verifies settle EXACTLY once (Real delegated payments: verify + settle through an external checker (#87, #88, #89) #103)grants.test.ts— BYPASS: concurrent same-key spends collapse to one charge, never a falserevokedgrants.test.ts— BYPASS: a revoke landing while authorize is in flight never leaves a spendable grantCloses #140
🤖 Generated with Claude Code