Repository navigation
Description
Follow-up from #1884 (closed) / #1878 / ADR-0049.
Re-evaluated 2026-07-18. The original blocker has partially cleared and the remaining work changed shape; body rewritten (original framing preserved below under "History").
Status: still open, re-scoped
Agent visibility (global/organization/private) is still not enforced — deliberately, per ADR-0049 / ADR-0056 D8 it is carried as [EXPERIMENTAL] with banners at every authoring surface (all pointing at this issue):
- schema:
packages/spec/src/ai/agent.zod.ts(EXPERIMENTAL banner onvisibility) - authoring form:
packages/spec/src/ai/agent.form.ts(helpText: settingprivatedoes not hide the agent) - liveness registry:
packages/spec/liveness/agent.json(visibility: experimental) - QA conformance matrix:
packages/qa/dogfood/test/authz-conformance.matrix.ts(agent-visibility: experimental)
access + permissions remain the enforced gates at the chat route (#1884, evaluateAgentAccess() in service-ai agent-access.ts).
What changed since filing (2026-06-15)
✅ Original blocker resolved — tenant is now in the request context. ADR-0095 (accepted 2026-07-14, implemented): resolveAuthzContext (packages/core/src/security/resolve-authz-context.ts) is the single transport-agnostic authz resolver and carries tenantId (session activeOrganizationId or API-key principal), org_user_ids, and a monotonic posture rung PLATFORM_ADMIN > TENANT_ADMIN > MEMBER > EXTERNAL derived from capability grants. Tenant isolation is Layer 0 (plugin-security/src/tenant-layer.ts).
❌ New blocker — the agent-side anchor was removed. agent.tenantId was deleted from the spec (#2377, ADR-0049: it had no runtime reader and did not actually scope the agent), and the agent schema has no owner/creator field. The originally proposed gate — organization → caller tenant must match agent.tenantId — is therefore no longer implementable as written.
Remaining design decisions
Agents are metadata artifacts, so the anchor question is now:
organization: with no per-agent tenant id, the meaningful reading is "authenticated members only" (posture ≥MEMBER), vsglobal= also anonymous/EXTERNAL. Note theEXTERNALrung is defined but never derived yet (no external principal type —posture-ladder.ts), soorganizationvsglobalis currently indistinguishable at runtime; enforcement becomes meaningful once external principals exist.private: needs an owner. Studio-authored agents havecreated_byon theirsys_metadatarow; file-authored agents have no owner. Either restrictprivateto DB-stored agents, or define it as "requires an explicitaccesslist".
Work plan (when scheduled)
- Decide the semantics above (small ADR, or amendment to ADR-0049/0095).
- Enforce in service-ai: extend
evaluateAgentAccess()for the chat route and filter theGET /ai/agentslist route by visibility (both surfaces, orprivatestill leaks by listing). - Add conformance coverage in the authz matrix.
- Flip liveness
experimental → liveand remove the EXPERIMENTAL banners inagent.zod.ts/agent.form.ts/liveness/agent.json/authz-conformance.matrix.ts.
Priority: P1 → P2
The P1 rationale was a silent false-security gap. Since the 2026-07 ADR-0056 D8 sweep, visibility is loudly marked EXPERIMENTAL / NOT ENFORCED at every authoring surface, and real gating (access/permissions) is enforced — this is now a documented unimplemented feature, not a silent hole.
History — original issue body (2026-06-15)
#1884 enforced agent access + permissions in the chat route, but visibility (global/organization/private) is still not enforced. The route's RouteUserContext carries userId/roles/permissions but no tenant id, and the agent has no owner field — so a correct gate (organization = same tenant; private = owner only) can't be decided at the route layer today.
Needed
- Add tenant id (and, for
private, an agent owner/creator) to the authenticated request context via the auth middleware. - Then gate the chat route:
global→ open;organization→ caller tenant must matchagent.tenantId;private→ owner only.
Until then, enforcing a partial/guessed version was intentionally avoided (would risk both lock-out and false security). See agent-access.ts for the documented gap.
Activity
refactor(spec): remove dead compliance subsystems (ADR-0056 D8) + mar…
[-][P1][security] Enforce agent `visibility` (organization/private) — needs tenant/owner in request context[/-][+][P2][security] Enforce agent `visibility` (organization/private) — context now has tenant (ADR-0095); needs owner/org anchor on the agent[/+]os-zhuang commentedon Jul 18, 2026
Re-evaluated against
main(2026-07-18,865ccb9) and rewrote the body. Summary of what changed:resolveAuthzContextnow carriestenantId+ a capability-derived posture rung, and tenant isolation is Layer 0. The "needs tenant/owner in request context" framing is stale.agent.tenantIdwas removed from the spec in [11.0][A2] Remove dead author-facing metadata properties (ADR-0049 enforce-or-remove) #2377 (no runtime reader), and agents still have no owner field — so the gate as originally specified can't be built. The open question moved from the request side to the agent side (what anchorsorganization/privateon a metadata artifact).EXTERNALposture rung is defined but never derived (no external principal type yet), soorganizationvsglobalis runtime-indistinguishable until external principals exist.visibilitycarries EXPERIMENTAL/NOT-ENFORCED banners in the schema, authoring form, liveness registry, and QA conformance matrix — the silent-false-security risk that justified P1 is gone; what remains is a documented unimplemented feature with a design decision pending.Generated by Claude Code
os-zhuang commentedon Jul 18, 2026
Resolved by removal (ADR-0056 D8 "design+enforce or remove"), not enforcement — closing.
Re-evaluation showed the "enforce" path is not buildable today:
agent.tenantIdwas already removed (#2377), agents carry no owner field, and theEXTERNALposture rung is never derived — soprivatehas no anchor andorganizationvsglobalis runtime-indistinguishable. The semantics, not just the plumbing, are undesigned. Carrying a security-shaped field that never gates anything is exactly the D8 liability, sovisibilityis dropped rather than left marked[EXPERIMENTAL].Implemented in:
visibilityfromAgentSchema, the authoring form, the liveness ledger, and the docs; movesagent-visibilityfromexperimental→removedin the ADR-0056 D10 authz-conformance matrix.visibilityfrom AgentPreview (#1901) objectui#2665 — removes the now-deadvisibilitypill fromAgentPreview.Real gating is unchanged:
access/permissionsremain enforced at the chat route (#1884). Re-introducevisibilityunder a fresh issue when the agent listing surface gains real owner/org semantics (external principals + an agent owner field).Both PRs carry
Closes #1901; closing now as the development is complete.Generated by Claude Code
refactor(spec): remove unenforced agent `visibility` field (ADR-0056 …
fix(i18n): clear the accumulated drift in the generated translation b…
feat(cli): gate generated translation bundles with `os i18n extract -…
feat(spec)!: remove SkillSchema.permissions — it never gated anything (…