You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[Feat] Tenant administration: roles, member management, and revocable sessions
Part of #777 (CAO 3.0). Co-delivered with #774 against the agreed tenant/identity contract from #778 and the shared migration in #775. Identity, administration and revocation integrate together; completion of all of #774 is not a prerequisite for starting this issue.
In plain terms
Once a team shares one CAO, somebody has to be in charge of it. Who can add a colleague? Who removes someone who has left? Who can shut down a runaway agent that isn't theirs?
Today the answer is nobody, because CAO has no users. There is an admin scope on API tokens, but that describes what a token may do — it does not describe a person, and there is no user record for it to attach to.
Without this, a shared CAO can be signed into but not run.
What this delivers
Two roles to begin with: administrator and member. Resisting a full permission system in the first version is deliberate — two roles cover the real needs and can be extended later. Inventing a fine-grained permission model before anyone has asked for one is how this gets expensive.
A member can create, inspect, run, stop, edit and delete their own work where the resource supports those operations. In 3.0 that work is private: [Feat] Sharing and visibility within a tenant (schema in 3.0, experience after) #780 enforces this on APIs/imports as well as the interface. Sharing user-owned work between members arrives after 3.0.
An administrator additionally manages who is in the tenant, and can intervene in anything inside it — including stopping another person's runaway agent.
Roles belong to the person's membership in a tenant. Being an administrator in one tenant grants nothing in another. These roles are distinct from token scopes and agent-profile tool-access roles. A tenant administrator is not the installation operator: global paths, platform credentials and deployment configuration remain operator-controlled under #778.
Define the action/ownership matrix with #774 rather than mapping "member" to today's write scope and leaving every guard unchanged. Deleting one's own session, terminal, flow or workflow spec currently hits an admin-only guard (api/main.py:3134, :6628, :7110, :4367). Those are ordinary owner operations in the new model, not a reason to give every member global administration.
Member management. Invite, remove, and change a person's role. Removing someone must have a defined answer for what happens to their work — it should not silently vanish, and it should not remain reachable by them.
The first administrator comes from #778's operator-controlled setup, not an invitation issued by an administrator who does not yet exist. Prevent concurrent removals/demotions from leaving a tenant without an active administrator, and document operator-controlled recovery. A valid IdP login alone must not recreate a removed membership or undo a deliberate role change.
In 3.0, user-owned work remains private. On removal it keeps its owner and is retained; access through that tenant membership ends, while an authorized tenant administrator can inspect it and complete cleanup. Other tenant memberships are unaffected. Ownership transfer and the fate of deliberately shared work remain after 3.0.
Sessions that can be ended. If someone leaves the company, their access has to actually stop. This is the part that's easy to get wrong, and the sources genuinely pull in two directions:
AWS SaaS guidance says put tenant and role into the token's claims, to avoid a lookup on every request — "without requiring a roundtrip to another service… Managing this context through another service adds latency and often creates bottlenecks."
OWASP ASVS 8.3.2 says authorisation changes "are applied immediately", and notes that where they can't be — "such as when relying on data in self-contained tokens" — there must be compensating controls.
Both are right. The resolution has to be explicit rather than accidental: short-lived tokens plus a way to revoke. Pick the lifetime deliberately and write down why.
Revocation must reach more than the next HTTP request:
End the affected sessions/refresh authority and delegated data/tool grants, and close affected browser WebSocket/SSE subscriptions. Check the current authority for subsequent actions and reconnections; do not wait for a signed token to expire.
Prevent new queued, scheduled or resumed work from starting under the removed membership. Persist enough tenant/owner context to apply this after a server restart, not just while the original request is in memory.
Request cancellation of active work and report the actual outcome using [Feat] Centralize cao-server in one Kubernetes Deployment and Service #745's requested-versus-stopped contract. Retain the bounded server/runtime control path needed to stop execution and preserve final diagnostics after human/tool access is revoked. An unreachable worker is not proof of termination, and cancellation cannot undo external effects already made.
Apply role changes to the permissions they remove, including existing channels and delegated operations. Do not reinstate old authority from cached claims.
A browser disconnect closes the channel, not the CAO login session. Sign-out or expiry ends that session's access. Neither is member removal, and neither silently cancels independently authorized background work.
The current terminal WebSocket checks scopes at connection time (api/main.py:6850-6877) and then forwards input/output in long-running loops (:6960-7019). Adding a membership check only to new HTTP calls would miss that path. The OWASP WebSocket guidance explicitly covers session expiry, closing connections on logout and message-level authorization.
The local laptop has none of this. One person, implicitly the administrator of their tenant of one, with nothing to manage and no sign-in.
Acceptance criteria
Administrator and member roles attach to a person's tenant membership, not merely to a token or agent profile. The same identity may have different permissions in different tenants.
An administrator can invite, remove, and re-role people within their own tenant, and only their own tenant.
Removal ends the affected tenant access across HTTP, refresh/session credentials, live WebSocket/SSE connections and delegated tools; retained work keeps its owner and another tenant membership is unaffected.
Queued/scheduled/resumed work cannot start for a removed membership, including after restart. Active work is cancelled with confirmed, failed or unknown outcomes; authorized control/cleanup still works after delegated access is revoked.
A still-valid IdP token or group claim cannot silently recreate a removed membership or reverse an administrator's role change.
Token lifetime and the revocation mechanism are chosen deliberately and the reasoning is recorded.
An administrator can stop another member's agent or terminal within their tenant.
A member can complete the supported create/run/stop/edit/delete lifecycle of their own work without gaining tenant-management or installation-wide permissions. Test owner, same-tenant administrator and unrelated-tenant callers against the same action matrix.
Neither tenant role can change operator-only installation settings through a broad legacy scope.
A transport disconnect preserves a still-valid login for reconnect. Sign-out/session expiry ends the affected session's access; neither silently cancels independently authorized jobs. Membership removal follows the separate policy above.
On a laptop, none of this appears and nothing needs configuring.
Evidence
Claim
Where
admin is a token scope, not a person
security/auth.py:44-46
No user, role, or session table exists
14 tables, none identity-related
Token-claims vs immediate-revocation tension
AWS SaaS Lens; OWASP ASVS 8.3.2, 7.4.1
All repository observations verified on main at 29b235cf62ed0f9d624bc9ad9afce09ab72f8ddf.
[Feat] Tenant administration: roles, member management, and revocable sessions
Part of #777 (CAO 3.0). Co-delivered with #774 against the agreed tenant/identity contract from #778 and the shared migration in #775. Identity, administration and revocation integrate together; completion of all of #774 is not a prerequisite for starting this issue.
In plain terms
Once a team shares one CAO, somebody has to be in charge of it. Who can add a colleague? Who removes someone who has left? Who can shut down a runaway agent that isn't theirs?
Today the answer is nobody, because CAO has no users. There is an
adminscope on API tokens, but that describes what a token may do — it does not describe a person, and there is no user record for it to attach to.Without this, a shared CAO can be signed into but not run.
What this delivers
Two roles to begin with: administrator and member. Resisting a full permission system in the first version is deliberate — two roles cover the real needs and can be extended later. Inventing a fine-grained permission model before anyone has asked for one is how this gets expensive.
Roles belong to the person's membership in a tenant. Being an administrator in one tenant grants nothing in another. These roles are distinct from token scopes and agent-profile tool-access roles. A tenant administrator is not the installation operator: global paths, platform credentials and deployment configuration remain operator-controlled under #778.
Define the action/ownership matrix with #774 rather than mapping "member" to today's write scope and leaving every guard unchanged. Deleting one's own session, terminal, flow or workflow spec currently hits an admin-only guard (
api/main.py:3134,:6628,:7110,:4367). Those are ordinary owner operations in the new model, not a reason to give every member global administration.Member management. Invite, remove, and change a person's role. Removing someone must have a defined answer for what happens to their work — it should not silently vanish, and it should not remain reachable by them.
The first administrator comes from #778's operator-controlled setup, not an invitation issued by an administrator who does not yet exist. Prevent concurrent removals/demotions from leaving a tenant without an active administrator, and document operator-controlled recovery. A valid IdP login alone must not recreate a removed membership or undo a deliberate role change.
In 3.0, user-owned work remains private. On removal it keeps its owner and is retained; access through that tenant membership ends, while an authorized tenant administrator can inspect it and complete cleanup. Other tenant memberships are unaffected. Ownership transfer and the fate of deliberately shared work remain after 3.0.
Sessions that can be ended. If someone leaves the company, their access has to actually stop. This is the part that's easy to get wrong, and the sources genuinely pull in two directions:
Both are right. The resolution has to be explicit rather than accidental: short-lived tokens plus a way to revoke. Pick the lifetime deliberately and write down why.
Revocation must reach more than the next HTTP request:
A browser disconnect closes the channel, not the CAO login session. Sign-out or expiry ends that session's access. Neither is member removal, and neither silently cancels independently authorized background work.
The current terminal WebSocket checks scopes at connection time (
api/main.py:6850-6877) and then forwards input/output in long-running loops (:6960-7019). Adding a membership check only to new HTTP calls would miss that path. The OWASP WebSocket guidance explicitly covers session expiry, closing connections on logout and message-level authorization.The local laptop has none of this. One person, implicitly the administrator of their tenant of one, with nothing to manage and no sign-in.
Acceptance criteria
Evidence
adminis a token scope, not a personsecurity/auth.py:44-46All repository observations verified on
mainat29b235cf62ed0f9d624bc9ad9afce09ab72f8ddf.