Skip to content

Add WorkOS dashboard API foundation#24

Merged
isuttell merged 8 commits into
mainfrom
codex/api-worker-integration
May 23, 2026
Merged

Add WorkOS dashboard API foundation#24
isuttell merged 8 commits into
mainfrom
codex/api-worker-integration

Conversation

@isuttell

@isuttell isuttell commented May 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds the API-side WorkOS foundation needed for the web dashboard to provision members and read canonical workspace data after PR #23. This keeps dashboard reads scoped by WorkOS membership while leaving key mutations, Access Link resolve, operator lockdown, and denylist reconciliation as later slices.

Changes

  • Add WorkOS JWT/JWKS verification and canonical WorkOS User API lookup in the API worker.
  • Add workspace_members schema, migration, RLS policies, repository methods, and first-login default API key provisioning.
  • Add /v1/auth/web/callback and read-only /v1/web/* routes with contracts and OpenAPI coverage.
  • Update web loaders to consume canonical contract response shapes.
  • Add API WorkOS secret/bootstrap wiring, Node 24 guardrails, ci:check, and a focused pnpm smoke:web harness.

Risk: HIGH

  • Areas touched: apps/api, packages/db, packages/contracts, apps/web loaders, smoke harness, CI/runtime config
  • Security: WorkOS bearer verification, workspace membership provisioning, one-time plaintext API key handling, secret bindings
  • Performance: dashboard list queries and JWKS/User API fetch path; JWKS is locally cached
  • Breaking: no public API contract removals; adds migration 0004_workspace_members and new dashboard endpoints

Test plan

  • bun run ci:check
  • pnpm verify
  • pnpm smoke:web
  • git pre-commit hooks: gitleaks, Biome, typecheck
  • git pre-push hook: test

CodeRabbit notes: local CodeRabbit pre-flight was skipped because the CLI auth flow failed with authentication_failed: Failed to start server. Is port 0 in use?.

Summary by CodeRabbit

  • New Features

    • WorkOS-backed web sign‑in, automatic workspace/member provisioning, and web dashboard reads (workspace, artifacts, API keys, audit, settings); UI shows session errors and default‑API‑key banner with clearer "not found / not provisioned" empty states
  • Chores

    • Persisted workspace‑member support and DB migration; enabled engine‑strict and Node version pinning; refined package export mappings
  • Tests

    • New unit suites and end‑to‑end smoke checks validating web auth, provisioning, and web API flows

Review Change Stack

@isuttell isuttell temporarily deployed to pr-preview-24 May 23, 2026 17:47 — with GitHub Actions Inactive
@coderabbitai

coderabbitai Bot commented May 23, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

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: 2e09c982-1eb8-4ec8-a289-c41fc6b215ba

📥 Commits

Reviewing files that changed from the base of the PR and between d5ed348 and 9fb15d7.

📒 Files selected for processing (1)
  • scripts/smoke-hosted.mjs

Walkthrough

This PR implements WorkOS-backed web authentication (JWT verification with cached JWKS and WorkOS user fetch), adds a workspace_members table and Drizzle schema/types, extends Local and Postgres repositories to provision/resolve workspace members and expose web-scoped read APIs (workspace, artifacts, keys, audit, settings), registers new web routes and OpenAPI contracts, updates web frontend typing and authed-session wiring, and adds unit tests plus an end-to-end smoke-test script.

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant WebUI as Web Frontend
  participant API as API Worker
  participant Auth as WorkOS/AuthService
  participant Repo as Repository/DB
  Browser->>WebUI: User action with WorkOS token
  WebUI->>API: POST /v1/auth/web/callback Authorization: Bearer <token>
  API->>Auth: authenticateWebIdentity(bearer)
  Auth-->>API: WorkOsIdentity or null
  alt identity
    API->>Repo: resolveWebMember(workos_user_id,email)
    Repo-->>API: WebAuthCallbackResponse (workspace, member, default_api_key?)
    API-->>WebUI: 200 web auth callback response
    WebUI->>API: GET /v1/web/artifacts Authorization: Bearer <token>
    API->>Repo: getWebMemberByWorkOsUserId -> verify scopes
    Repo-->>API: artifacts list
    API-->>WebUI: 200 artifacts response
  else no identity
    API-->>WebUI: 401 not_authenticated
  end
Loading

Possibly related PRs

Poem

🐰 I hopped through tokens, keys, and rows,

Verified claims where the WorkOS wind blows,
Members provisioned with a friendly squeak,
Smoke tests hop, the dashboard’s sleek,
A rabbit's patch of code—now live and neat!

🚥 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 'Add WorkOS dashboard API foundation' accurately summarizes the main change: foundational WorkOS authentication and web member provisioning infrastructure for a dashboard.
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 codex/api-worker-integration

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: 11

🤖 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 @.github/workflows/ci.yml:
- Around line 61-63: The CI guardrail block currently checks pnpm config keys
but does not assert Node version parity; add a new test in the same block (after
the existing pnpm config tests such as the
engine-strict/minimumReleaseAge/nodeLinker checks) that compares the declared
Node version files and fails the job if they differ by asserting the contents of
.nvmrc and .node-version are equal (normalizing/trimming newlines as needed) so
CI will break on drift between .nvmrc and .node-version.

In `@apps/api/src/workos.test.ts`:
- Around line 26-54: Add regression tests to explicitly cover the
requireClientIdClaim behavior when a token contains only aud (no client_id or
azp): create fixtures via tokenFixture with aud set to the expected clientId but
omit client_id and azp, stub the JWKS fetch with
stubWorkOsFetch(fixture.publicJwk), call resolveWorkOsIdentity(`Bearer
${fixture.token}`, options({ requireClientIdClaim: true })) and assert it
resolves to null; add equivalent assertions for both the tests around the "wrong
client_id" and the other related block (the tests around lines 66-80) so tokens
that only match aud are rejected when requireClientIdClaim is true.

In `@apps/api/src/workos.ts`:
- Around line 128-140: The clientIdMatches function currently allows tokens
without client_id/azp to pass when aud matches even if requireClaim is true;
change it so that when requireClaim is true and neither
stringClaim(payload.client_id) nor stringClaim(payload.azp) exist the function
immediately returns false, otherwise continue to compare the found clientClaim
to clientId; if no clientClaim and requireClaim is false then fall back to
checking payload.aud (string or array) as before. Ensure you update the logic in
clientIdMatches to perform the early requireClaim + clientClaim existence check
and keep the existing audience handling (payload.aud) for the non-strict case.
- Around line 76-88: fetchWorkOsUser can throw on network or JSON parse errors;
wrap the fetch + response.json() and normalization steps in a try/catch so any
thrown error returns null instead of bubbling up. Specifically, surrounding the
call to
fetch(`${workOsBaseUrl(options.apiBaseUrl)}/user_management/users/${encodeURIComponent(workosUserId)}`,
{ headers: { authorization: `Bearer ${options.apiKey}` } }) and the subsequent
await response.json() and normalizeWorkOsUser(value) with try/catch will ensure
you catch network/parse/normalize failures and return null; keep the existing
response.ok check and return null on non-ok as well.

In `@packages/db/migrations/0004_workspace_members.sql`:
- Line 6: The migration currently enforces global uniqueness on workos_user_id;
change it to a workspace-scoped uniqueness by replacing the single-column unique
constraint on workos_user_id with a composite unique constraint on
(workspace_id, workos_user_id) in the workspace_members migration so a single
WorkOS user can belong to multiple workspaces; update the CREATE TABLE / ALTER
TABLE in packages/db/migrations/0004_workspace_members.sql to drop the existing
unique constraint on workos_user_id and add a UNIQUE(workspace_id,
workos_user_id) constraint (refer to the workspace_members table, the
workos_user_id and workspace_id columns) and ensure any rollback/forward
migration steps reflect this change.

In `@packages/db/src/local-repository.ts`:
- Around line 245-253: The getWebWorkspace function accepts ApiActor but
immediately calls mustWorkspaceMember(actor.id), which only makes sense for
WorkspaceMemberActor; narrow the type of getWebWorkspace (and its interface in
repository) to WorkspaceMemberActor or add an explicit branch to handle
ApiKeyActor: update the method signature for getWebWorkspace to accept
WorkspaceMemberActor (and adjust callers such as the webWorkspace
endpoint/withWebMember usage if necessary) OR add a runtime guard that throws or
handles api_key cases before calling mustWorkspaceMember; reference
getWebWorkspace, ApiActor, WorkspaceMemberActor, mustWorkspaceMember,
webWorkspace endpoint, withWebMember, and db.getWebMemberByWorkOsUserId when
implementing the change.

In `@packages/db/src/postgres/repository.ts`:
- Around line 820-864: The same row-formatting helpers (toWebArtifactRow,
webArtifactStatus, toWebAuditRow, summarizeEventDetails) are duplicated in
local-repository.ts and postgres/repository.ts; extract them into a shared
module (e.g., transforms.ts) that exports those functions, re-use the Artifact
and OperationEvent types (or re-export them if needed), replace the local
implementations in both repository files with imports from transforms.ts, and
update any references to ensure behavior and types remain identical (including
exported names: toWebArtifactRow, webArtifactStatus, toWebAuditRow,
summarizeEventDetails).

In `@packages/db/src/queries/operation-events.ts`:
- Around line 64-84: The listForWorkspace function currently returns an
unbounded result set; update the operationEvents query in listForWorkspace to
accept and apply pagination parameters (limit and a cursor composed of
occurredAt and id) so results are bounded and stable. Modify listForWorkspace to
take optional params (e.g., limit: number, cursor?: { occurredAt: string, id:
string }), add a .limit(limit) and a WHERE clause that implements cursor
comparison (occurredAt < cursor.occurredAt OR (occurredAt = cursor.occurredAt
AND id < cursor.id)) while preserving the
orderBy(desc(operationEvents.occurredAt), desc(operationEvents.id)); also update
the returned payload to include a nextCursor (based on the last row's occurredAt
and id) and wire these new params/response fields through the route contract
that calls listForWorkspace.

In `@packages/db/src/schema.ts`:
- Line 32: The `scopes` column declaration
(jsonb("scopes").$type<Array<"publish" | "read" | "admin">>().notNull()) is
missing the default that the SQL migration sets; update the `scopes` column to
include a default value of ["publish","read","admin"] (i.e. add the default via
the column builder on the `scopes` jsonb column) so the Drizzle schema matches
the migration and prevents schema drift.
- Line 30: workos_user_id is currently marked globally unique (workosUserId:
text("workos_user_id").notNull().unique()), which prevents the same WorkOS user
from existing in multiple workspaces; remove the .unique() on workosUserId and
instead add a composite unique constraint/index for (workspace_id,
workos_user_id) at the table level so the pair is unique per workspace (search
for the table definition containing workosUserId and add the composite unique
key for workspace_id + workos_user_id).

In `@scripts/smoke-web-api.mjs`:
- Around line 19-20: Replace use of URL.pathname for spawned entry paths by
converting the file:// URLs to platform file paths using Node's fileURLToPath;
update the cliEntry and serverEntry initializations to call fileURLToPath(new
URL("... ", import.meta.url)) so the paths are percent-decoded and Windows-safe
(use the fileURLToPath function imported from 'url' and apply it to new
URL("../apps/cli/dist/src/index.js", import.meta.url) and new
URL("./local-mvp-server.mjs", import.meta.url)).
🪄 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: e9c43fee-9004-469b-8742-0a37e9a70638

📥 Commits

Reviewing files that changed from the base of the PR and between 361bf4c and d586faf.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (42)
  • .github/workflows/ci.yml
  • .node-version
  • .npmrc
  • apps/api/package.json
  • apps/api/src/index.test.ts
  • apps/api/src/index.ts
  • apps/api/src/workos.test.ts
  • apps/api/src/workos.ts
  • apps/api/wrangler.jsonc
  • apps/upload/src/index.ts
  • apps/web/src/routes/_authed.artifacts.$artifactId.tsx
  • apps/web/src/routes/_authed.artifacts.index.tsx
  • apps/web/src/routes/_authed.audit.tsx
  • apps/web/src/routes/_authed.keys.tsx
  • apps/web/src/routes/_authed.settings.tsx
  • apps/web/src/routes/_authed.tsx
  • package.json
  • packages/auth/package.json
  • packages/commands/package.json
  • packages/contracts/openapi/api.json
  • packages/contracts/src/index.ts
  • packages/contracts/src/mvp-contracts.test.ts
  • packages/contracts/src/openapi/api.ts
  • packages/contracts/src/openapi/shared.ts
  • packages/contracts/src/routes.ts
  • packages/contracts/src/web.ts
  • packages/db/migrations/0004_workspace_members.sql
  • packages/db/snapshot/schema.sql
  • packages/db/src/index.test.ts
  • packages/db/src/index.ts
  • packages/db/src/local-repository.ts
  • packages/db/src/postgres/repository.ts
  • packages/db/src/queries/api-keys.ts
  • packages/db/src/queries/index.ts
  • packages/db/src/queries/operation-events.ts
  • packages/db/src/queries/workspace-members.ts
  • packages/db/src/schema.ts
  • packages/db/src/types.ts
  • scripts/bootstrap-secrets.mjs
  • scripts/local-mvp-server.mjs
  • scripts/smoke-web-api.mjs
  • turbo.json

Comment thread .github/workflows/ci.yml
Comment thread apps/api/src/workos.test.ts
Comment thread apps/api/src/workos.ts Outdated
Comment thread apps/api/src/workos.ts
Comment thread packages/db/migrations/0004_workspace_members.sql Outdated
Comment on lines +820 to 864
function toWebArtifactRow(artifact: Artifact) {
return {
id: artifact.id,
title: artifact.title,
status: webArtifactStatus(artifact),
latest_revision_id: artifact.revision_id,
pinned: false,
lockdown: false,
last_published_at: artifact.created_at,
auto_delete_at: artifact.status === "deleted" ? null : artifact.expires_at,
};
}

function webArtifactStatus(artifact: Artifact): "Published" | "Deleted" | "Expired" {
if (artifact.status === "deleted") {
return "Deleted";
}
if (artifact.status === "expired") {
return "Expired";
}
return "Published";
}

function toWebAuditRow(event: OperationEvent) {
return {
id: event.id,
time: event.occurred_at,
actor: `${event.actor_type}:${event.actor_id ?? "unknown"}`,
action: event.action,
target: `${event.target_type}:${event.target_id}`,
change_summary: summarizeEventDetails(event.details),
request_id: event.request_id ?? "",
};
}

function summarizeEventDetails(details: Record<string, unknown>): string {
const keys = Object.keys(details);
if (keys.length === 0) {
return "";
}
return keys
.sort()
.map((key) => `${key}=${String(details[key])}`)
.join(", ");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider extracting shared web row formatters.

The toWebArtifactRow, webArtifactStatus, toWebAuditRow, and summarizeEventDetails functions are duplicated between local-repository.ts and postgres/repository.ts. Extracting them to a shared module (e.g., transforms.ts) would reduce duplication and ensure they stay in sync.

🤖 Prompt for 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.

In `@packages/db/src/postgres/repository.ts` around lines 820 - 864, The same
row-formatting helpers (toWebArtifactRow, webArtifactStatus, toWebAuditRow,
summarizeEventDetails) are duplicated in local-repository.ts and
postgres/repository.ts; extract them into a shared module (e.g., transforms.ts)
that exports those functions, re-use the Artifact and OperationEvent types (or
re-export them if needed), replace the local implementations in both repository
files with imports from transforms.ts, and update any references to ensure
behavior and types remain identical (including exported names: toWebArtifactRow,
webArtifactStatus, toWebAuditRow, summarizeEventDetails).

Comment on lines +64 to +84
async listForWorkspace(db: DrizzleDb, workspaceId: string): Promise<OperationEvent[]> {
const rows = await db
.select()
.from(operationEvents)
.where(eq(operationEvents.workspaceId, workspaceId))
.orderBy(desc(operationEvents.occurredAt));
return rows.map(
(row): OperationEvent => ({
id: row.id,
workspace_id: row.workspaceId,
actor_type: row.actorType as OperationEvent["actor_type"],
actor_id: row.actorId,
action: row.action,
target_type: row.targetType,
target_id: row.targetId,
details: row.details,
request_id: row.requestId,
occurred_at: row.occurredAt.toISOString(),
}),
);
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Add pagination/cursor bounds for workspace audit queries.

listForWorkspace is unbounded. On larger workspaces this can create large DB and response payloads, degrading latency and reliability. Add a limit + cursor (e.g., occurredAt/id) and wire it through the route contract.

🤖 Prompt for 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.

In `@packages/db/src/queries/operation-events.ts` around lines 64 - 84, The
listForWorkspace function currently returns an unbounded result set; update the
operationEvents query in listForWorkspace to accept and apply pagination
parameters (limit and a cursor composed of occurredAt and id) so results are
bounded and stable. Modify listForWorkspace to take optional params (e.g.,
limit: number, cursor?: { occurredAt: string, id: string }), add a .limit(limit)
and a WHERE clause that implements cursor comparison (occurredAt <
cursor.occurredAt OR (occurredAt = cursor.occurredAt AND id < cursor.id)) while
preserving the orderBy(desc(operationEvents.occurredAt),
desc(operationEvents.id)); also update the returned payload to include a
nextCursor (based on the last row's occurredAt and id) and wire these new
params/response fields through the route contract that calls listForWorkspace.

Comment thread packages/db/src/schema.ts Outdated
Comment thread packages/db/src/schema.ts Outdated
Comment thread scripts/smoke-web-api.mjs Outdated
@isuttell isuttell temporarily deployed to pr-preview-24 May 23, 2026 18:10 — with GitHub Actions Inactive

@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: 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/api/src/workos.ts`:
- Around line 77-82: The fetch calls to WorkOS must be bounded with an
AbortController timeout: wrap the user lookup fetch (the call using
workOsBaseUrl(options.apiBaseUrl)/user_management/users/${encodeURIComponent(workosUserId)}
and headers with options.apiKey) and the JWKS fetch (the other fetch in this
file) with an AbortController, start a setTimeout to call controller.abort()
after a sensible default (e.g., 5s or a configurable timeout), pass
controller.signal to fetch, and clearTimeout on success; ensure you handle
AbortError/timeout by throwing or returning a descriptive error so the auth path
fails fast instead of hanging.
🪄 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: 4f8f810f-8a66-45da-abec-74b18775082a

📥 Commits

Reviewing files that changed from the base of the PR and between d586faf and b419cd2.

📒 Files selected for processing (12)
  • .github/workflows/ci.yml
  • apps/api/src/index.test.ts
  • apps/api/src/index.ts
  • apps/api/src/workos.test.ts
  • apps/api/src/workos.ts
  • packages/db/migrations/0004_workspace_members.sql
  • packages/db/snapshot/schema.sql
  • packages/db/src/index.test.ts
  • packages/db/src/local-repository.ts
  • packages/db/src/postgres/repository.ts
  • packages/db/src/schema.ts
  • scripts/smoke-web-api.mjs

Comment thread apps/api/src/workos.ts
Comment on lines +77 to +82
const response = await fetch(
`${workOsBaseUrl(options.apiBaseUrl)}/user_management/users/${encodeURIComponent(workosUserId)}`,
{
headers: { authorization: `Bearer ${options.apiKey}` },
},
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify outbound WorkOS fetch call sites and whether they include a timeout signal.
rg -n 'fetch\(' apps/api/src/workos.ts -C3
rg -n 'signal\s*:' apps/api/src/workos.ts -C3

Repository: zaks-io/agent-paste

Length of output: 633


Add bounded timeouts to WorkOS fetch calls in the auth path.
apps/api/src/workos.ts performs WorkOS outbound fetch calls for both the user lookup (around lines 77-82) and the JWKS fetch (around line 108) without any AbortController/signal, so a hung dependency can stall request handling and cause cascading latency. Add an explicit timeout (e.g., AbortController + setTimeout + clearTimeout) to both calls.

🤖 Prompt for 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.

In `@apps/api/src/workos.ts` around lines 77 - 82, The fetch calls to WorkOS must
be bounded with an AbortController timeout: wrap the user lookup fetch (the call
using
workOsBaseUrl(options.apiBaseUrl)/user_management/users/${encodeURIComponent(workosUserId)}
and headers with options.apiKey) and the JWKS fetch (the other fetch in this
file) with an AbortController, start a setTimeout to call controller.abort()
after a sensible default (e.g., 5s or a configurable timeout), pass
controller.signal to fetch, and clearTimeout on success; ensure you handle
AbortError/timeout by throwing or returning a descriptive error so the auth path
fails fast instead of hanging.

@isuttell isuttell temporarily deployed to pr-preview-24 May 23, 2026 18:18 — with GitHub Actions Inactive

@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 @.github/workflows/pr-preview.yml:
- Line 97: Replace the fallback that assigns PR_PREVIEW_SECRET_SEED from
CLOUDFLARE_API_TOKEN so the workflow only uses the dedicated
PR_PREVIEW_SECRET_SEED; remove the "|| secrets.CLOUDFLARE_API_TOKEN" fallback
and add a fail-fast check step that validates secrets.PR_PREVIEW_SECRET_SEED is
set (exit non‑zero or use a conditional that stops the job) so preview admin
token/signing secrets/API key pepper never silently inherit
CLOUDFLARE_API_TOKEN.

In `@scripts/smoke-hosted.mjs`:
- Around line 157-175: In waitForAdminAuth, make transport errors (fetch/network
exceptions) retry until the deadline while treating permanent auth failures
(HTTP 401/403) as immediate failures: wrap the fetch call in a try/catch, on
catch record lastBody/lastStatus appropriately and continue the loop (sleep and
retry) until deadline; after a successful fetch, if response.status is 401 or
403 throw immediately with a clear error mentioning c.adminToken and url, but if
it’s another non-200 status keep retrying until deadline; preserve the existing
deadline, sleep(2000) backoff and include the last response body snippet in the
eventual thrown error.
🪄 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: 3d2ad787-6a97-4537-8b07-aa2284f2e633

📥 Commits

Reviewing files that changed from the base of the PR and between b419cd2 and 5ab637f.

📒 Files selected for processing (3)
  • .github/workflows/pr-preview.yml
  • scripts/deploy-pr-preview.mjs
  • scripts/smoke-hosted.mjs

Comment thread .github/workflows/pr-preview.yml Outdated
Comment thread scripts/smoke-hosted.mjs
@isuttell isuttell temporarily deployed to pr-preview-24 May 23, 2026 18:33 — with GitHub Actions Inactive

@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: 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 `@scripts/smoke-hosted.mjs`:
- Line 170: The code assigns the string "transport_error" to lastStatus (which
otherwise holds numeric HTTP statuses), causing type inconsistency and mixed
output; change that assignment to a numeric sentinel such as -1 (replace the
"transport_error" assignment to lastStatus with -1) and update any downstream
reporting/formatting that prints lastStatus (the timeout/deadline message that
currently interpolates lastStatus) to render -1 as a human-friendly label (e.g.,
map -1 => "transport_error") so comparisons and logs remain consistent;
alternatively, if you prefer, add a separate boolean/enum (e.g., transportError
flag) instead of mutating lastStatus.
🪄 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: 96fba547-3c9f-40da-b11a-98c2569e4f2a

📥 Commits

Reviewing files that changed from the base of the PR and between 5ab637f and d5ed348.

📒 Files selected for processing (2)
  • .github/workflows/pr-preview.yml
  • scripts/smoke-hosted.mjs

Comment thread scripts/smoke-hosted.mjs Outdated
@isuttell isuttell temporarily deployed to pr-preview-24 May 23, 2026 18:38 — with GitHub Actions Inactive
@isuttell isuttell temporarily deployed to pr-preview-24 May 23, 2026 18:41 — with GitHub Actions Inactive
@isuttell isuttell merged commit 22c4b36 into main May 23, 2026
4 checks passed
isuttell added a commit that referenced this pull request May 24, 2026
…ope boundary (#43)

* docs: reconcile project-status with main after #42

Snapshot pointed at 22c4b36 (#24), ~18 commits stale. Update to
76c2b34 (#42), refresh latest-commit list, mark Phase 1 ~99%
(only parked Logpush remains), drop completed backlog item 1, and
add a Recently Completed entry for the hosted Artifact Rate Limit
smoke assertion.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs: reconcile Phase 2/3 scope and retire Auth0 framing

phases.md, project-status.md, and web-app-todo.md disagreed on what
Phase 3 is. Align all three with project-status as the source of truth
and fold in the agreed decisions:

- Phase 3 = WorkOS dashboard + CLI login, no Access Links. Dashboard
  committed to Phase 3 (was hedged to Phase 6).
- CLI login uses WorkOS loopback PKCE; ADR 0060 rewritten off Auth0.
- Operator gate is Cloudflare Access; rotation agent authenticates via
  an Access service token (ADR 0046/0045), replacing the gone Auth0 M2M
  path. API keys stay barred from admin routes.
- Per-workspace retention in scope, bounded by ADR 0048 caps.
- Access Links confirmed Phase 4 (ADR 0047/0052); viewer dropped from
  the web todo.

Also fixes stale Auth0 references in ADR 0045/0046, the ADR README,
and CONTEXT.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs: resolve CLI-vs-dashboard scope boundary under WorkOS

Closes the open ADR 0060 implementation detail. WorkOS RBAC claims are
org-membership-scoped (identical across CLI and dashboard) and the JWT
template is per-environment, so the boundary cannot be a token claim.
Resolve it as a dedicated CLI WorkOS OAuth client (aud = cli_client_id)
plus a server-side per-client scope ceiling that strips the Member-Only
Scopes for the CLI surface; base scopes still come from
workspace_members.scopes. ADR 0034's Auth0 audience exception re-noted
for the WorkOS mechanism.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs: address CodeRabbit review on ADR README

- Reword "decided off Auth0" to plainer phrasing (ADR 0060 line).
- Fix MCP tense to "will be formally re-decided" and note its
  on-paper decision is still Auth0 DCR (ADR 0061 line); MCP was
  decided, so the word stays "re-decided", only the tense changes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
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