feat(users): auto-default agent accounts to general-agent role
Previously every account created via POST /users without an explicit role_id fell through to the `guest` role. Recruitment workflow creates HF accounts for newly-onboarded agents with --agent-id/--claw-identifier set, so we can detect "this is an agent" at the backend boundary and pick a more appropriate default: payload.agent_id set → general-agent (guest reads + reset-self-apikey) payload.agent_id unset → guest (human users keep current behavior) Also adds `general-agent` to init_bootstrap.py's _DEFAULT_ROLES so fresh deployments seed it on first boot — the role already existed on prod (created via UI earlier); this is for re-seedability / new envs. No ClawSkills script changes required: the onboard script already calls `hf user create --agent-id <id> --claw-identifier <claw>`. The recruitment workflow.md is updated to note the new default. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -68,11 +68,29 @@ def require_account_creator(
|
|||||||
raise HTTPException(status_code=403, detail="Account creation permission required")
|
raise HTTPException(status_code=403, detail="Account creation permission required")
|
||||||
|
|
||||||
|
|
||||||
def _resolve_user_role(db: Session, role_id: int | None) -> Role:
|
def _resolve_user_role(db: Session, role_id: int | None, *, is_agent: bool = False) -> Role:
|
||||||
|
"""Resolve target role for user creation.
|
||||||
|
|
||||||
|
Default policy when caller didn't pin role_id:
|
||||||
|
- is_agent (i.e. payload had agent_id/claw_identifier) → general-agent
|
||||||
|
- human user → guest
|
||||||
|
|
||||||
|
general-agent ≈ guest + user.reset-self-apikey so agents can rotate
|
||||||
|
their own API key without admin intervention. Created in
|
||||||
|
init_bootstrap.py on every startup; falls back to guest if absent
|
||||||
|
(e.g. very old DB that hasn't been re-seeded yet).
|
||||||
|
"""
|
||||||
if role_id is None:
|
if role_id is None:
|
||||||
|
default_name = "general-agent" if is_agent else "guest"
|
||||||
|
role = db.query(Role).filter(Role.name == default_name).first()
|
||||||
|
if not role and is_agent:
|
||||||
|
# general-agent missing from this DB → fall back to guest, log warn
|
||||||
role = db.query(Role).filter(Role.name == "guest").first()
|
role = db.query(Role).filter(Role.name == "guest").first()
|
||||||
if not role:
|
if not role:
|
||||||
raise HTTPException(status_code=500, detail="Default guest role is missing")
|
raise HTTPException(
|
||||||
|
status_code=500,
|
||||||
|
detail=f"Default role '{default_name}' is missing (DB not seeded)",
|
||||||
|
)
|
||||||
return role
|
return role
|
||||||
|
|
||||||
role = db.query(Role).filter(Role.id == role_id).first()
|
role = db.query(Role).filter(Role.id == role_id).first()
|
||||||
@@ -112,7 +130,7 @@ def create_user(
|
|||||||
if existing_agent:
|
if existing_agent:
|
||||||
raise HTTPException(status_code=400, detail="agent_id already in use")
|
raise HTTPException(status_code=400, detail="agent_id already in use")
|
||||||
|
|
||||||
assigned_role = _resolve_user_role(db, user.role_id)
|
assigned_role = _resolve_user_role(db, user.role_id, is_agent=has_agent_id)
|
||||||
# In OIDC-only mode, ignore any supplied password: the user is created
|
# In OIDC-only mode, ignore any supplied password: the user is created
|
||||||
# passwordless (cannot password-login) and is expected to sign in via a
|
# passwordless (cannot password-login) and is expected to sign in via a
|
||||||
# bound OIDC identity. API keys still work for such users.
|
# bound OIDC identity. API keys still work for such users.
|
||||||
|
|||||||
@@ -124,11 +124,26 @@ _ACCOUNT_MANAGER_PERMISSIONS = {
|
|||||||
"user.reset-apikey",
|
"user.reset-apikey",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Default role for agents (assigned automatically by POST /users when
|
||||||
|
# the create-user payload carries agent_id/claw_identifier — see
|
||||||
|
# app/api/routers/users.py:_resolve_user_role). Guest-tier reads +
|
||||||
|
# self-service API-key rotation so agents can manage their own creds
|
||||||
|
# without admin intervention.
|
||||||
|
_GENERAL_AGENT_PERMISSIONS = {
|
||||||
|
"project.read",
|
||||||
|
"task.read",
|
||||||
|
"milestone.read",
|
||||||
|
"monitor.read",
|
||||||
|
"calendar.read",
|
||||||
|
"user.reset-self-apikey",
|
||||||
|
}
|
||||||
|
|
||||||
_DEFAULT_ROLES = [
|
_DEFAULT_ROLES = [
|
||||||
("admin", "Administrator - full access to all features", None), # None ⇒ all perms
|
("admin", "Administrator - full access to all features", None), # None ⇒ all perms
|
||||||
("account-manager", "Account manager - can only create accounts", _ACCOUNT_MANAGER_PERMISSIONS),
|
("account-manager", "Account manager - can only create accounts", _ACCOUNT_MANAGER_PERMISSIONS),
|
||||||
("mgr", "Manager - project & milestone management", _MGR_PERMISSIONS),
|
("mgr", "Manager - project & milestone management", _MGR_PERMISSIONS),
|
||||||
("dev", "Developer - task execution & daily work", _DEV_PERMISSIONS),
|
("dev", "Developer - task execution & daily work", _DEV_PERMISSIONS),
|
||||||
|
("general-agent", "General agent - read-only + self API key rotation", _GENERAL_AGENT_PERMISSIONS),
|
||||||
("guest", "Guest - read-only access", None), # special: *.read only
|
("guest", "Guest - read-only access", None), # special: *.read only
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user