Skip to content

Drive worker guards from route contracts#41

Merged
isuttell merged 5 commits into
mainfrom
t3code/2f13bf23
May 24, 2026
Merged

Drive worker guards from route contracts#41
isuttell merged 5 commits into
mainfrom
t3code/2f13bf23

Conversation

@isuttell

@isuttell isuttell commented May 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Route contracts now drive worker request enforcement instead of only documenting it. This implements ADR 0072 by moving auth, scopes, rate-limit selection, idempotency header validation, and common error envelopes into a shared worker runtime registrar.

Changes

  • Added @agent-paste/worker-runtime with contract-backed route mounting, principal types, centralized error status mapping, rate-limit handling, idempotency handling, and focused guard tests.
  • Extended routeContracts with rateLimit, WorkOS callback provisioning metadata, and the two authenticated Agent View routes.
  • Cut over API, upload, and content workers to mount contract routes through the registrar, including route drift tests and updated auth/scope/idempotency coverage.
  • Updated project status docs for ADR 0072 completion.

Risk: HIGH

  • Areas touched: API worker, upload worker, content worker, route contracts, auth/rate-limit/idempotency guard flow.
  • Security: auth, scopes, signed tokens, request idempotency, and rate-limit enforcement are centralized.
  • Performance: rate-limit behavior is preserved, with completed idempotency replay still before limiter consumption.
  • Breaking: yes, routes now enforce declared scopes consistently where handlers previously could drift.

Test plan

  • bun run ci:check under Node 24.15.0
  • pre-commit hooks: gitleaks, biome/prettier, typecheck
  • pre-push hooks: affected tests
  • CodeRabbit local review loop: 3 passes, actionable findings addressed

CodeRabbit notes: skipped the hono ^4.12.22 bump because all workspace Hono packages currently use ^4.12.21; skipped exposing Zod validation details because current worker error envelopes intentionally return generic invalid_request responses.

Summary by CodeRabbit

  • New Features

    • Centralized contract-driven routing and auth for API, upload, and content workers; OpenAPI available at /openapi.json.
    • Introduced a shared worker runtime exposing standardized auth principals, rate-limit bindings, and error helpers.
  • Improvements

    • Enforced scope-based authorization (e.g., publish/read) with proper 403 responses.
    • Enhanced rate limiting (actor/artifact/none) and stronger idempotency handling for reliable requests.
  • Documentation

    • Marked worker runtime cutover checklist items as done.

@isuttell isuttell temporarily deployed to pr-preview-41 May 24, 2026 16:26 — with GitHub Actions Inactive
@coderabbitai

coderabbitai Bot commented May 24, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b4873e00-2614-4cd1-96d4-66e49eb0e4d1

📥 Commits

Reviewing files that changed from the base of the PR and between 1fc34f0 and 1c076a2.

📒 Files selected for processing (3)
  • packages/worker-runtime/src/errors.ts
  • packages/worker-runtime/src/registrar.test.ts
  • packages/worker-runtime/src/registrar.ts

Walkthrough

This PR adds packages/worker-runtime and migrates API, Content, and Upload workers to a contract-driven registrar pipeline. Contracts gain per-route rateLimit metadata and additional agentView routes. The runtime provides principal types, error helpers, rate-limit bindings, and createRegistrar (idempotency → replay → rate-limit → scope → handler). Workers now mount route contracts, export mountedRouteIds and nonContractRoutePaths, and convert handlers to principal/db/guard signatures. Tests were added/updated to assert route mounting, rate-limit declarations, and scope-based authorization; docs mark the cutover steps done.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Registrar
  participant AuthResolver
  participant RateLimit
  participant Handler
  participant DB

  Client->>Registrar: HTTP Request + Contract
  Registrar->>AuthResolver: Resolve Principal
  AuthResolver-->>Registrar: Principal or Error
  alt Auth Failure
    Registrar-->>Client: 401 Error
  else Auth Success
    Registrar->>Registrar: Check Idempotency Key
    alt Missing Idempotency Key (required)
      Registrar-->>Client: 400 invalid_idempotency_key
    else Idempotency Valid or Not Required
      Registrar->>DB: Check Idempotency Replay
      DB-->>Registrar: Replay Result or None
      alt Replay Found
        Registrar-->>Client: 200 Cached Result
      else No Replay
        Registrar->>RateLimit: Apply Rate Limit
        alt Rate Limited
          Registrar-->>Client: 429 rate_limited_*
        else Not Limited
          Registrar->>Registrar: Validate Scopes
          alt Missing Required Scope
            Registrar-->>Client: 403 forbidden
          else Scopes Valid
            Registrar->>Handler: Execute Handler(context, principal, db, guard)
            Handler->>DB: Read/Write Operations
            DB-->>Handler: Results
            Handler-->>Registrar: Response
            Registrar-->>Client: 200 OK
          end
        end
      end
    end
  end
Loading

Possibly related PRs

  • zaks-io/agent-paste#32: Modifies WorkOS web authentication and idempotency-key handling in the API's callback flow, overlapping with this PR's refactor of web auth endpoints into the principal-based registrar pipeline.
  • zaks-io/agent-paste#15: Adjusts /openapi.json implementation in worker entrypoints; this PR preserves /openapi.json via nonContractRoutePaths in each worker's contract-driven routing.
  • zaks-io/agent-paste#4: Introduces runIdempotent and idempotency-key wrapping for admin operations; this PR refactors idempotency to use the registrar/guard's centralized idempotencyKey field instead.

"🐰 I hopped through routes and mounted each one,
principals signed, and rate-limits spun,
idempotent replays saved the day,
workers now sing from a registrar-run runway!"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Drive worker guards from route contracts' clearly summarizes the main change: refactoring worker request enforcement (auth, scopes, rate-limiting, idempotency) to be driven by route contracts through a new registrar system.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 t3code/2f13bf23

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@packages/worker-runtime/src/errors.ts`:
- Around line 66-73: The error response builder returns JSON without cache
directives; add a "cache-control": "no-store" header to the headers object so
error envelopes (4xx/5xx) are never cached. Update the Response construction in
the error envelope function (where ERROR_STATUS and REQUEST_ID_HEADER are used)
to include "cache-control": "no-store" alongside the existing content-type,
defaultHeaders, and headers; mirror the behavior used by jsonResponse to ensure
intermediaries don't cache error responses.

In `@packages/worker-runtime/src/registrar.ts`:
- Around line 159-163: The idempotency-key check in registrar.ts currently
accepts whitespace-only values; update the validation to trim the header value
and reject if the trimmed string is empty. Specifically, after retrieving
idempotencyKey from context.req.raw.headers.get("idempotency-key"), compute a
trimmed value (e.g., const key = idempotencyKey?.trim()) and change the guard to
if (!key) return { ok: false, code: "invalid_idempotency_key" }; and return {
ok: true, state: { idempotencyKey: key } } so the stored key is normalized.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 645897f9-aa6f-48dc-b193-09c2359e4aa6

📥 Commits

Reviewing files that changed from the base of the PR and between ae119fd and 1fc34f0.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (23)
  • apps/api/package.json
  • apps/api/src/index.test.ts
  • apps/api/src/index.ts
  • apps/api/src/local-mvp.test.ts
  • apps/content/package.json
  • apps/content/src/index.test.ts
  • apps/content/src/index.ts
  • apps/upload/package.json
  • apps/upload/src/error-envelope.test.ts
  • apps/upload/src/index.test.ts
  • apps/upload/src/index.ts
  • docs/ops/project-status.md
  • docs/ops/worker-runtime-todo.md
  • packages/contracts/src/mvp-contracts.test.ts
  • packages/contracts/src/routes.ts
  • packages/worker-runtime/package.json
  • packages/worker-runtime/src/errors.ts
  • packages/worker-runtime/src/index.ts
  • packages/worker-runtime/src/principal.ts
  • packages/worker-runtime/src/rate-limit.ts
  • packages/worker-runtime/src/registrar.test.ts
  • packages/worker-runtime/src/registrar.ts
  • packages/worker-runtime/tsconfig.json

Comment thread packages/worker-runtime/src/errors.ts
Comment thread packages/worker-runtime/src/registrar.ts Outdated
@isuttell isuttell temporarily deployed to pr-preview-41 May 24, 2026 16:39 — with GitHub Actions Inactive
@isuttell isuttell merged commit 0b57218 into main May 24, 2026
4 checks passed
@github-actions

Copy link
Copy Markdown

agent-paste PR preview resources were cleaned up. The pr-preview-${context.issue.number} environment is left in place; remove it from the GitHub UI if desired.

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