Move /rpc into Astro; one function owns /api/\*\*
date: 2026-05-10 status: accepted outranks: []
Section titled “date: 2026-05-10 status: accepted outranks: []”Move /rpc into Astro; one function owns /api/**
Section titled “Move /rpc into Astro; one function owns /api/**”Context
Section titled “Context”Per-user Airlock auth (TASKSET 4) shipped with /api/auth/callback and
/api/auth/logout as Astro APIRoutes inside dashboard/src/pages/api/,
while /rpc continued to live as a root-level Vercel function
api/rpc.ts declared in vercel.json functions.
In production, /console/* correctly redirected to the Airlock handoff,
but /api/auth/callback returned 404 on the return leg — breaking
sign-in for every user. Verified against the petrova-host deploy of
fb5fb23: middleware ran, the Astro _render function was reachable at
/console/*, but /api/** paths didn’t dispatch to _render for
routes other than the root-declared api/rpc.ts. Vercel’s filesystem
handler appears to claim the /api namespace once any root-level
function is declared, short-circuiting the Build Output API routes
copied in from dashboard/.vercel/output.
Decision
Section titled “Decision”/api/rpc is now an Astro APIRoute at
dashboard/src/pages/api/rpc.ts. The root-level api/rpc.ts and the
functions block in vercel.json are removed. A single Astro server
function (_render.func) owns the entire /api/** and /console/**
surface. The /rpc → /api/rpc rewrite stays so existing callers (the
CLI MCP client, dashboard/src/lib/rpc.ts, the production smoke test)
keep working unchanged.
Data files (registry.yaml, state/**, docs/**, contracts/**,
spec/verbs/**, host/src/**, cli/dist/**, the eva-mini and
grace-mini fixtures) are staged into
.vercel/output/functions/_render.func/ by vercel.json buildCommand
after astro build. They were previously bundled via the
functions.api/rpc.ts.includeFiles glob, which no longer applies once
the function is gone. Astro’s adapter includeFiles only accepts paths
inside the project root; ours live one level up at the monorepo root,
hence the explicit cp -a step.
Consequences
Section titled “Consequences”- Sign-in works again.
/api/auth/callbackis reachable, the EdDSA handoff JWT verifies, the__petrova_sessioncookie gets minted, and the user lands at/console. - One function, one cold-start, one set of env vars. Bearer
(
PETROVA_HOST_BEARER), session secret, Octokit App credentials, and Airlock URL all live on the same function now. - The dashboard package now depends on
@octokit/auth-app,@octokit/rest,@petrova/cli(file:),ajv,ajv-formats,commander,glob,js-yaml, andpicocolors— these moved from the root@petrova/host-vercelshell, which is now dependency-less and exists only as a Node 20 hint for Vercel. MR-10: verification round for this fix is the post-deploy smoke test againsthttps://petrova.host/api/auth/callback?token=…— expected to redirect (302) instead of returning 404.