Skip to content

[sprites 1-2] Split checkAuth: pure access decision, lazy sprite resolution - #2000

Merged
2witstudios merged 4 commits into
masterfrom
pu/sprites-1-2-checkauth-split
Jul 11, 2026
Merged

2witstudios merged 4 commits into
masterfrom
pu/sprites-1-2-checkauth-split

Conversation

@2witstudios

@2witstudios 2witstudios commented Jul 11, 2026 •

Copy link
Copy Markdown
Owner

What & why

makeAgentTerminalCheckAuth (apps/realtime/src/index.ts) fused two jobs: (a) deciding whether the user may attach (DB-only), and (b) resolving/waking the Sprite. Per the platform lifecycle model — a Sprite is woken automatically by any exec (docs.sprites.dev/concepts/lifecycle) — authorization never needs to touch the Sprite. This leaf splits (a) into a pure decision and (b) into a lazy resolver that runs only when a fresh PTY must be created, so leaf 3-1 can re-check access alone.

New module: apps/realtime/src/terminal/agent-terminal-access.ts

  • decideAgentTerminalAccess(inputs) — pure function over plain data (access level, page/drive rows, canRunCode verdict, slot availability). Surfaces driveId/payerId on allow.
  • resolveTerminalSandbox(target, deps) — resolves the agent-terminal row + reads the Sprite exactly once (full getSprite collapse is leaf 1-4). provision_failed on a vanished Sprite.
  • buildAgentTerminalCheckAuth(deps) — DI factory composing both halves back into the AgentTerminalCheckAuthFn the PTY bridge consumes. Gathers inputs sequentially with short-circuit (no page read without edit access; no drive/user read once code-exec is denied), decrypts the actor email before reserving the slot, and releases the slot on every failure path — matching the fused implementation byte-for-byte on the socket surface.

index.ts now wires the real IO dependencies into both halves (plus a resolveAgentTerminalSandbox shell binding the stores + buildMachineSandbox).

Requirements → how satisfied

Requirement How
Given gathered DB inputs, compute allow/deny via a pure decideAgentTerminalAccess with unit tests: no access level, canRunCode false, billing-slot exhaustion, happy path Pure fn + no-mock riteway tests (all four named cases + canEdit=false, page/drive missing, gate-ordering)
Given an access check, perform zero sprite SDK calls (assert with an injected spy sprite client) buildAgentTerminalCheckAuth test injects a resolveSandbox wired through the real resolveTerminalSandbox with a spy getSprite; on no_edit_access/concurrency_limit the spy records 0 calls and resolveSandbox is never invoked
Given a caller needing the sprite, expose a separate resolveTerminalSandbox that resolves the sprite exactly once (single getSprite) Dedicated fn + test asserting getSprite.calls.length === 1; resolve-failure path asserts 0 sprite reads
Given deny paths, preserve behavior byte-for-byte on the socket surface (error events, released slots) Same deny reason strings in the same gate order (no_edit_access → page_not_found → canRunCode reason → drive_not_found → concurrency_limit); sequential short-circuit gather; slot released exactly once on every failure incl. a resolveSandbox rejection; actorEmail decrypted before slot reservation; existing agent-terminal-handler.test.ts (58) + index.test.ts (101) unchanged & green

Review-driven refinements (already applied)

  • Slot leak on rejection (CodeRabbit, Major — verified & resolved): resolveSandbox rejections (DB error inside resolveAgentTerminal, failed store/SDK lookup) now release the slot and re-throw, so a transient failure can't permanently consume concurrency capacity. Socket surface unchanged.
  • Restored short-circuit I/O (review): a denied attach no longer issues DB round-trips it would ignore, and a downstream DB error can't turn a clean, specific denial into a thrown generic error.
  • Collapsed the double decision-call and hardened tests (provision_failed logger routing + short-circuit assertions). Both pre-existed as latent issues in the fused code; the split closes them.

Out of scope (untouched)

onConnect reordering (1-3), getSprite collapse + wake removal (1-4), re-auth interval change (3-1).

Rename note

Branched from current master (post-#1992): uses PageType.MACHINE / machineId throughout; no terminalId/PageType.TERMINAL reintroduced. No DB schema change.

Test evidence

$ bun run test:coverage   # apps/realtime — exit 0
 Test Files  19 passed (19)
      Tests  607 passed (607)
 agent-terminal-access.ts | 100% stmts | 100% branch | 100% funcs | 100% lines
 (global funcs 85.47% ≥ 85%, branch 98.14% ≥ 98%)

$ bunx tsc --noEmit   # exit 0
$ bunx eslint <changed files>   # exit 0

CI green (Unit Tests, Lint & TypeScript). Mergeable, no conflicts.

🤖 Generated with Claude Code

… lazy sprite [sprites 1-2]

Splits the fused makeAgentTerminalCheckAuth into two jobs: a pure access
decision over gathered DB data (decideAgentTerminalAccess) and a lazy sprite
resolution (resolveTerminalSandbox / resolveAgentTerminalSandbox) that runs
only when a fresh PTY must be created. A Sprite is woken automatically by any
exec (docs.sprites.dev/concepts/lifecycle), so authorization never needs to
touch it — the access half now performs zero sprite SDK calls, letting the
re-auth interval (leaf 3-1) re-check access without re-resolving the Sprite.

- New apps/realtime/src/terminal/agent-terminal-access.ts:
  - decideAgentTerminalAccess(inputs): pure, gate order preserved byte-for-byte
    (no_edit_access -> page_not_found -> canRunCode reason -> drive_not_found ->
    concurrency_limit), surfacing driveId/payerId on allow.
  - resolveTerminalSandbox(target, deps): single getSprite; provision_failed on
    a vanished Sprite (full getSprite collapse is leaf 1-4).
  - buildAgentTerminalCheckAuth(deps): DI factory composing both halves.
- index.ts wires the real IO deps; deny reasons, released slots and the socket
  error surface are unchanged.

TDD: pure core tested with no mocks; shells tested with injected fakes
(18 new tests). Full realtime suite green (601 tests), typecheck + lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XrMtPG59TTgGX4rYzGCr1m
@coderabbitai

coderabbitai Bot commented Jul 11, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Agent-terminal authorization is split into pure access decisions and lazy Sprite resolution. Realtime wiring now composes these stages through injected callbacks, with expanded tests covering denials, slot handling, sandbox failures, auditing, and successful PTY payload assembly.

Changes

Agent terminal authorization

Layer / File(s) Summary
Access decision gates
apps/realtime/src/terminal/agent-terminal-access.ts, apps/realtime/src/terminal/__tests__/agent-terminal-access.test.ts
Adds typed access inputs and decisions, ordered denial gates, derived drive and payer identifiers, and coverage for denial and allow cases.
Lazy sandbox and Sprite resolution
apps/realtime/src/terminal/agent-terminal-access.ts, apps/realtime/src/terminal/__tests__/agent-terminal-access.test.ts
Resolves terminal launch metadata before fetching a Sprite once, supports command overrides, and reports provisioning failures with sandbox identifiers.
Composed auth wiring and validation
apps/realtime/src/index.ts, apps/realtime/src/terminal/agent-terminal-access.ts, apps/realtime/src/terminal/__tests__/agent-terminal-access.test.ts
Builds terminal auth from injected callbacks for access, slots, sandbox resolution, auditing, session keys, and logging, with tests for early exits, slot release, defaults, and successful payloads.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant makeAgentTerminalCheckAuth
  participant buildAgentTerminalCheckAuth
  participant resolveAgentTerminalSandbox
  participant SpriteSDK
  Client->>makeAgentTerminalCheckAuth: request terminal access
  makeAgentTerminalCheckAuth->>buildAgentTerminalCheckAuth: evaluate access and reserve slot
  buildAgentTerminalCheckAuth->>resolveAgentTerminalSandbox: resolve terminal sandbox
  resolveAgentTerminalSandbox->>SpriteSDK: getSprite
  SpriteSDK-->>resolveAgentTerminalSandbox: Sprite
  resolveAgentTerminalSandbox-->>buildAgentTerminalCheckAuth: launch data and Sprite
  buildAgentTerminalCheckAuth-->>Client: PTY session payload
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main refactor: splitting checkAuth into a pure access decision and lazy sprite resolution.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pu/sprites-1-2-checkauth-split

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

Comment @coderabbitai help to get the list of available commands.

…verage gate

CI's apps/realtime test:coverage failed at 97.93% branch (threshold 98%): the
new agent-terminal-access.ts had uncovered branches — the pageRow-missing path,
the non-provision_failed sandbox-failure path, and the tier/email fallbacks.
Adds three tests covering them; the module is now 100% stmts/branch/funcs/lines
and the global branch coverage clears the 98% gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XrMtPG59TTgGX4rYzGCr1m
@2witstudios

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 11, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/realtime/src/terminal/agent-terminal-access.ts`:
- Around line 218-223: Ensure the terminal setup flow releases the acquired slot
when deps.resolveSandbox rejects: wrap the resolveSandbox call and subsequent
handling in try/catch, call releaseSlot exactly once on rejection, then rethrow
the original error to preserve socket error behavior. Keep the existing
!sandbox.ok cleanup intact without double release, and add a regression test for
the rejecting resolver that verifies releaseSlot is called once.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6170adc1-659c-4c02-aa0c-f2b1c39f982d

📥 Commits

Reviewing files that changed from the base of the PR and between a1cc449 and f16f6c2.

📒 Files selected for processing (3)
  • apps/realtime/src/index.ts
  • apps/realtime/src/terminal/__tests__/agent-terminal-access.test.ts
  • apps/realtime/src/terminal/agent-terminal-access.ts

Comment thread apps/realtime/src/terminal/agent-terminal-access.ts
2witstudios and others added 2 commits July 11, 2026 01:11
…olution rejects

CodeRabbit (Major): resolveSandbox can reject during its DB/Sprite work
(a failed store/SDK lookup or a DB error inside resolveAgentTerminal), which
bypasses the !sandbox.ok deny branch after the slot was already acquired,
permanently consuming the user's concurrency capacity. The pre-split fused
checkAuth had the same latent leak (it awaited resolveAgentTerminal with no
try/catch), so this is not a regression — but the split is the right place to
close it. Wrap the resolveSandbox await, release the slot on rejection, and
re-throw so the socket surface is unchanged (onConnect's .catch still emits the
generic error). Adds a test asserting the slot is released and the original
error propagates.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XrMtPG59TTgGX4rYzGCr1m
…eckAuth + test hardening

Addresses a high-effort review pass on the checkAuth split:

1. (correctness) Restore the fused checkAuth's sequential short-circuit input
   gathering: no page read without edit access, and no drive/user read once
   code execution is denied. The interim eager/parallel gather issued DB
   round-trips a denied attach then ignored and — worse — let a transient DB
   error on drives/users turn a clean, specific denial (e.g.
   code_execution_disabled) into a thrown generic connection error. Deny paths
   are byte-for-byte again.
2. (cleanup) Collapse the double decideAgentTerminalAccess invocation to a
   single read-only decision plus explicit slot acquisition (the slot is a
   reservation, not a read); removes the redundant re-evaluation.
3. (tests) Assert the provision_failed branch routes to logSandboxLookupFailed
   with the sandboxId (not logDenied); add short-circuit tests proving a
   code-exec / no-edit denial never queries the downstream tables. Counters live
   on the covered default deps so no never-called override arrows drag function
   coverage.

apps/realtime test:coverage green (607 tests; agent-terminal-access.ts 100%
stmts/branch/funcs/lines, global funcs 85.47% / branch 98.14%). typecheck + lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XrMtPG59TTgGX4rYzGCr1m
@2witstudios

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 11, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@2witstudios
2witstudios merged commit 820842c into master Jul 11, 2026
3 checks passed
@2witstudios
2witstudios deleted the pu/sprites-1-2-checkauth-split branch July 11, 2026 12:17
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