From 595391b41b35574bc0cce62391afc652fa885039 Mon Sep 17 00:00:00 2001 From: hzhang Date: Sun, 24 May 2026 19:38:06 +0100 Subject: [PATCH] feat(users): auto-default agent accounts to general-agent role MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --claw-identifier `. The recruitment workflow.md is updated to note the new default. Co-Authored-By: Claude Opus 4.7 (1M context) --- app/api/routers/users.py | 26 ++++++++++++++++++++++---- app/init_bootstrap.py | 15 +++++++++++++++ 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/app/api/routers/users.py b/app/api/routers/users.py index d62b203..58906c6 100644 --- a/app/api/routers/users.py +++ b/app/api/routers/users.py @@ -68,11 +68,29 @@ def require_account_creator( 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: - role = db.query(Role).filter(Role.name == "guest").first() + 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() 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 role = db.query(Role).filter(Role.id == role_id).first() @@ -112,7 +130,7 @@ def create_user( if existing_agent: 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 # passwordless (cannot password-login) and is expected to sign in via a # bound OIDC identity. API keys still work for such users. diff --git a/app/init_bootstrap.py b/app/init_bootstrap.py index ac9a371..b5ced51 100644 --- a/app/init_bootstrap.py +++ b/app/init_bootstrap.py @@ -124,11 +124,26 @@ _ACCOUNT_MANAGER_PERMISSIONS = { "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 = [ ("admin", "Administrator - full access to all features", None), # None ⇒ all perms ("account-manager", "Account manager - can only create accounts", _ACCOUNT_MANAGER_PERMISSIONS), ("mgr", "Manager - project & milestone management", _MGR_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 ]