Skip to content

fix(runtime): wire ai_access→ai_seat synthesis into the AI route dispatch (per-user seat gate)#2334

Merged
os-zhuang merged 1 commit into
mainfrom
fix/ai-seat-gate-route-synthesis
Jun 25, 2026
Merged

fix(runtime): wire ai_access→ai_seat synthesis into the AI route dispatch (per-user seat gate)#2334
os-zhuang merged 1 commit into
mainfrom
fix/ai-seat-gate-route-synthesis

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Problem

The per-user AI-seat gate ([ADR-0024], service-ai evaluateAgentAccess → requires the ai_seat capability) reads req.user.permissions. But the dispatch ExecutionContext that serves the /ai/* routes never carried the seat, so a seated user (sys_user.ai_access=true) was still denied:

  • GET /ai/agents returned an empty catalog for every authenticated caller (and the console hides the AI surface when the catalog is empty), while unauthenticated requests leaked the full catalog.
  • in-UI build/ask would 403.

…even though the cap (cloud#525) correctly counted the same ai_access booleans. Root cause: the ai_access → ai_seat synthesis lived only in plugin-hono-server's data-route resolveCtx — which is exactly why the cap (data-write path) worked but the gate (AI-route path) did not.

Fix

Add the guarded synthesis to both AI req.user construction paths in @objectstack/runtime:

  • http-dispatcher.ts — the /ai/* wildcard dispatch (the active path). Build req.user.permissions, then read sys_user.ai_access via the default (current per-request env) ObjectQL service and push ai_seat when true.
    • ⚠️ Must not pass context.environmentId: in the cloud runtime that's the control-plane env UUID, which keys a distinct, empty per-scope engine; the env's own sys_user is served by the default service. (Passing it returned [] in testing — diagnosed live.)
  • dispatcher-plugin.ts resolveRequestUser — the concrete-route mount path (defensive; guarded read via the env kernel's data service).

Guarded system read → can only ever ADD access, never break auth (absent / false / missing-column / error → no seat, unchanged).

Verification

Live on a local cloud + objectos stack, OS_CLOUD_AI_SEAT_MODE=enforce, driving the env REST as the seated admin (env session via the sso-open bridge):

state /ai/agents
seated (ai_access=true) [build, ask]
unseated (ai_access=false) []
restored (true) [build, ask]

Cap (cloud#525) still 400s past the licensed seat count. Unblocks flipping OS_CLOUD_AI_SEAT_MODE=enforce (the boot-verify that ai-seat-simple-model flagged as mandatory).

🤖 Generated with Claude Code

…atch (ADR-0024)

The per-user AI-seat gate (evaluateAgentAccess → requires the `ai_seat`
capability) reads `req.user.permissions`, but the dispatch ExecutionContext
serving `/ai/*` never carried the seat — so a SEATED user
(`sys_user.ai_access=true`) was denied: `/ai/agents` returned an EMPTY catalog
(and in-UI build/ask would 403), even though the #525 cap correctly counted
them. The synthesis lived only on plugin-hono-server's data-route resolveCtx
(which is why the cap, on the data-write path, worked but the gate did not).

Add the guarded synthesis to BOTH AI req.user construction paths:
- http-dispatcher.ts — the `/ai/*` wildcard dispatch (the ACTIVE path): build
  req.user.permissions, then read sys_user.ai_access via the DEFAULT (current
  per-request env) ObjectQL service and push `ai_seat` when true. NB do NOT pass
  context.environmentId — in the cloud runtime that is the control-plane env
  UUID, which keys a DISTINCT empty per-scope engine; the env's own sys_user is
  served by the default service (this exact mismatch returned [] in testing).
- dispatcher-plugin.ts resolveRequestUser — the concrete-route mount path
  (defensive; guarded read via the env kernel's `data` service).

Guarded system read → can only ever ADD access, never break auth
(absent/false/missing-column/error → no seat, unchanged).

Live-verified on a local cloud+objectos stack (OS_CLOUD_AI_SEAT_MODE=enforce):
seated ai_access=true → /ai/agents=[build,ask]; false → []; restore →
[build,ask]; cap still 400s past the licensed seat count. Unblocks flipping
OS_CLOUD_AI_SEAT_MODE=enforce.

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

vercel Bot commented Jun 25, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jun 25, 2026 6:37pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/runtime.

17 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/concepts/cloud-artifact-api.mdx (via packages/runtime)
  • content/docs/concepts/implementation-status.mdx (via @objectstack/runtime)
  • content/docs/concepts/north-star.mdx (via packages/runtime)
  • content/docs/concepts/packages.mdx (via @objectstack/runtime)
  • content/docs/guides/api-reference.mdx (via @objectstack/runtime)
  • content/docs/guides/authentication.mdx (via @objectstack/runtime)
  • content/docs/guides/cloud-deployment.mdx (via @objectstack/runtime)
  • content/docs/guides/deployment-vercel.mdx (via @objectstack/runtime)
  • content/docs/guides/driver-configuration.mdx (via @objectstack/runtime)
  • content/docs/guides/hook-bodies.mdx (via @objectstack/runtime)
  • content/docs/guides/packages.mdx (via @objectstack/runtime)
  • content/docs/guides/plugin-chatbot-integration.mdx (via @objectstack/runtime)
  • content/docs/guides/production-readiness.mdx (via @objectstack/runtime)
  • content/docs/guides/single-project-mode.mdx (via @objectstack/runtime)
  • content/docs/protocol/objectos/http-protocol.mdx (via @objectstack/runtime)
  • content/docs/protocol/objectos/index.mdx (via @objectstack/runtime)
  • content/docs/protocol/objectos/lifecycle.mdx (via @objectstack/runtime)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@os-zhuang
os-zhuang merged commit 635849f into main Jun 25, 2026
16 checks passed
@os-zhuang
os-zhuang deleted the fix/ai-seat-gate-route-synthesis branch June 25, 2026 18:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant