Roster composer writes keep their app_edit / snippets gate and require
workspace agent.manage in addition, instead of replacing it. Generic app
management routes (update/delete/copy/export/name/icon/site/site-enable/
api-enable/api-keys) enforce agent.manage when the target is a roster
Agent App and reject hidden workflow-only backing apps outright, which
stay usable only through the Agent runtime surfaces. Importing an Agent
DSL requires agent.manage and the denial surfaces as 403 instead of a
generic failed import.
The RBAC resource resolver returned the hidden runtime backing app for
workflow-only agents, which never receives a resource access policy, so
per-resource overrides on the parent workflow app were ignored. Resolve
agent_id to the policy-owning agent.app_id instead and keep
backing_app_id strictly runtime-only.
Replace the enterprise_enabled system feature with an env-derived edition
signal. enterprise_enabled expanded both the Console and Web API contracts,
and via cloudSystemFeatures it made Cloud render the Community Edition
warning.
- Drop enterprise_enabled from SystemFeatureModel; regenerate contracts and
openapi docs.
- Add NEXT_PUBLIC_ENTERPRISE_ENABLED (forwarding the existing backend
ENTERPRISE_ENABLED) and derive IS_ENTERPRISE_EDITION / IS_COMMUNITY_EDITION.
- CommunityEditionTip renders only on community edition, so enterprise and
cloud both correctly hide it.
Review fixes:
- edit-in-console-link: render the enabled control as an anchor instead of a
Button-rendered link, which forced type="button" onto the <a>; export
buttonVariants for the styling.
- agents-access-guard: named export; redirect to '/'.
- Remove the redundant @detailSidebar/agents access-guard layout.
The sandbox isolation caveat was suppressed by inspecting license.status,
which answers a different question: a lapsed enterprise licence would start
showing a community edition warning that still does not apply, because sandbox
isolation is a property of how the deployment was built, not of billing state.
ENTERPRISE_ENABLED already drives branding, webapp auth and the plugin manager
in get_system_features, but was never surfaced, leaving the frontend to infer
the edition from side effects. Expose it alongside rbac_enabled and read it
directly, which drops the license status set entirely.
The app-scoped RBAC gate hand-rolled a query to turn an agent_id into the id
of the App backing it, duplicating AgentRosterService.runtime_backing_app_id.
Worse, it filtered on id and tenant only, so the auth check accepted archived
Agents and sources the runtime resolver rejects -- a weaker notion of a valid
Agent than the layer it guards.
Add peek_runtime_backing_app_id, a read-only counterpart to
get_agent_runtime_app_model that never materializes the hidden backing App,
and share the eligibility filter between the two so they cannot drift.
The tenant filter was also untested: deleting it left all 50 tests passing,
since the specs stubbed the db module wholesale. Assert the resolver is called
with the caller's tenant instead, and cover the precedence and read-only
guarantees against the service directly.