Repository navigation
Conversation
…ons (Anchor-kit#718) A hosted interactive URL only carries a transaction id, so it proves nothing on its own. Persist interactive session identity and make every interactive endpoint consult one shared gate: sessions are bound to a single account and transaction, carry their own expiry and revocation, and spend a permitted action exactly once via compare-and-set. Expired, revoked, wrong-account and wrong-transaction sessions are now rejected before any write, and the new POST /transactions/:id/complete consumes the permitted action once so a completion cannot be replayed. 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
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.
What I changedsrc/runtime/interfaces.ts — added InteractiveSessionRecord (id, account, transactionId, permitted actions, consumed consumedActions, expiresAt, revokedAt), the InteractiveSessionRejection union, and four DatabaseAdapter methods: insertInteractiveSession, getInteractiveSessionById, revokeInteractiveSession, consumeInteractiveSessionAction.src/runtime/database/sql-database-adapter.ts — persisted interactive_sessions for SQLite and PostgreSQL with lookup indexes; revoke is a guarded UPDATE ... WHERE revoked_at IS NULL; consumption is a bounded compare-and-set on the stored consumed_actions text, re-reading on a lost race, so a given action can only ever be appended once.src/runtime/http/interactive-session-gate.ts (new) — the single shared validator. It reads the X-Interactive-Session header, loads the session, and checks identity binding first (account, then transaction), then revocation, then expiry, then the action set — so a caller who doesn't own a session can't learn whether it was revoked or expired. It also maps each rejection to a status: 401 for missing/unknown/expired/revoked, 403 for wrong-account/wrong-transaction/unpermitted action, 409 for an already-consumed action.src/runtime/http/express-router-impl.ts — new state-changing POST /transactions/:id/complete runs the gate before the transaction is even read, then spends the complete action exactly once. GET /transactions/:id uses the same gate when a session is presented, while plain bearer reads keep working. Transaction-id parsing was extracted to one shared helper, and the new header was added to both CORS allow-lists.Focused check — tests/runtime/interactive-session-revocation.test.ts, 8 assertions driving the real HTTP router over a real SQLite adapter. It wraps the write helper with a counter to prove rejected sessions never invoke it and that no row changes, asserts exactly-one consumption under both a replay and a five-way concurrent race, and asserts the read endpoint applies the identical gate.Acceptance criteriaCriterion StatusExpired, revoked, wrong-account, wrong-transaction sessions fail before writes ✅ Verified by counter + before/after row snapshotsCompletion consumes the permitted action once ✅ Replays and 5 concurrent callers yield exactly one 200Reproducible focused check with command and expected result documented ✅ Test header, docs/acceptance-checks.md, and PR_DESCRIPTION.mdVerificationbun test — 926 pass, 0 fail across 73 files (also run automatically by the pre-commit hook)bun run typecheck (tsc --noEmit + eslint . --max-warnings 0) — passbun run format:check, bun run lint, bun run build, scripts/check-fallow-health.mjs — all pass
closes #718