* [fastapi-guard] Phase 1a: gated config flags for the HTTP security layer Adds the ROBOCO_GUARD_* settings (all default-off / secure-default) for the upcoming fastapi-guard 7.2.0 hardening — guard_enabled (master switch), guard_fail_secure (fail-closed default; NAS overrides to false), guard_telemetry_enabled + guard_agent_api_key + guard_project_id (guard-agent telemetry, opt-in), guard_emergency + guard_emergency_whitelist (lockdown kill switch). Inert until consumed: nothing reads them yet, so the request path is unchanged. Foundation for v0.16.0. * [fastapi-guard] Phase 1b: security foundation module + gated wiring Add fastapi-guard 7.2.0 + guard-core 3.3.0 (bare, unpinned) and roboco/security.py: - build_security_config() from settings — behind-nginx real-IP (trusted_proxies + trust_x_forwarded_proto), HSTS/CSP headers, threat-ban + 404-sweep rules, redis-backed state, exclude_paths (/ws + health + docs), env-driven enforce_https, fail_secure (secure default), emergency lockdown, guard-agent telemetry (opt-in), passive-mode calibration switch. - guard_deco singleton (SecurityDecorator) for per-route decorators (Phase 2+). - Three custom content validators guard's WAF can't cover: prompt-injection / role-override, secret-exfil / credential-in-body, internal-SSRF. - apply_guard(app) + guarded_lifespan() wired into create_app AFTER settings. guard_passive_mode config flag added. Entirely gated by ROBOCO_GUARD_ENABLED (default off): create_app mounts nothing and returns the unchanged app when off (verified). make quality GREEN (cov 95.32%, pip-audit clean, import-linter 2/0). 12 new unit tests. * [fastapi-guard] Phase 2: critical-path decorators Apply guard decorators to the highest-value endpoints (metadata-only; enforced only when the middleware is mounted, so no-op while ROBOCO_GUARD_ENABLED is off): - provider keys (ollama/grok/self-hosted writes): strict rate_limit + max_request_size + block_clouds (no datacenter IP should touch secret writes). - settings write + release approve/reject (CEO-gated): strict rate_limit. - intake chat (prompter start/messages/events): rate_limit + max_request_size + custom_validation(prompt_injection_validator) — the prompt-facing free-text ingress gets the injection/role-override/secret-exfil content scan. make quality GREEN (cov 95.32%, contracts 2/0). App builds with guard off, decorators inert (verified). * [fastapi-guard] Phase 3: wide decorator coverage across ingress + sensitive routes Targeted-wide application (metadata-only; no-op until ROBOCO_GUARD_ENABLED). The global SecurityMiddleware already rate-limits + WAF-scans every request, so this adds the custom content validators on free-text ingress + tight limits on sensitive ops (not blanket per-route rate_limit on reads): - agent gateway do verbs (note/say/commit/dm/pitch/progress/draft_playbook/...): rate_limit + max_request_size + custom_validation(secret_exfil or prompt_injection). - a2a message/send + chat writes: rate_limit + size + prompt_injection. - optimal/RAG (kb/search, rag/query, mentor/ask, errors/decisions/standards/ learnings): prompt_injection on searches, secret_exfil on record writes; docs index → internal_ssrf. - tasks: create/update → prompt_injection; QA/doc/PM transitions → secret_exfil; CEO-gated verbs → tight rate_limit. - secretary chat → prompt_injection; research → internal_ssrf; orchestrator spawn/mutations → rate_limit; git ops + flow verbs → tight rate_limit. Pure GET/reads left to the global middleware. Applied via a Sonnet workflow, then verified: app builds with guard off (decorators inert), make quality GREEN (cov 95.37%, contracts 2/0). Decoy/honeypot-path surface deferred (needs verified guard ban-API integration — not rushed). * [fastapi-guard] Phase 5: arm the NAS composes in passive/log-only mode Arm ROBOCO_GUARD_ENABLED=true + ROBOCO_GUARD_PASSIVE_MODE=true + ROBOCO_GUARD_FAIL_SECURE=false on the two NAS composes (docker-compose.yaml + .yml). Passive = guard mounts and logs what it WOULD block but blocks nothing, so the next NAS deploy calibrates against real traffic; flip PASSIVE_MODE off after the false-positive review to enforce. fail_secure=false keeps a guard-internal error from 500ing the personal deploy. The registry (user-facing) compose is deliberately left unarmed so its published default stays conservative. Phase 4 (passive calibration) is the operational step this enables. * feat(security): Phase 3b — full-arsenal per-route guard enrichment Stack the applicable guard decorators per surface instead of the minimal rate_limit/max_request_size/custom_validation triad: content_type_filter on every JSON-body write, honeypot_detection form-traps on human-facing POSTs, block_clouds on key-writes + CEO release ops, behavior_analysis runaway-rate rules on the agent flow/do verbs, suspicious_detection + usage_monitor on the sensitive surfaces. Nine distinct decorators now applied thoughtfully per endpoint. All metadata-only — no-op while ROBOCO_GUARD_ENABLED is off. * fix(a2a): permit PR reviewer to deliver gate verdicts to the owning PM can_a2a_direct had no pr_reviewer rule, so a reviewer (team=board, or a cell team) fell through to the cell-member path and was cross-cell-denied when the in-path gate delivered a pr_fail change-request to main-pm (or a cross-cell cell-pm): "Cannot A2A main-pm ... Ask None to coordinate with None". The delivery is best-effort, so pr_fail still transitioned but the verdict never reached the owning PM — the blind-re-submit signal-gap the pr_fail fix closes. Add an explicit pr_reviewer handler: it may A2A only cell_pm / main_pm (its sole comms surface — everything else it posts on the PR itself), with a matching route hint. The cell reviewers kept same-team access by coincidence; this scopes every reviewer to PM-only, the correct model, with no other A2A caller affected. Refresh uv.lock to the current resolution. * feat(models): adopt Claude Sonnet 5 as the sonnet tier Point the 'sonnet' alias at claude-sonnet-5 (MODEL_MAP) and give pr_reviewer its own opus tier in ROLE_MODEL_MAP — it was falling through to the sonnet default, and the role gates untrusted external/fork PRs plus root→master, which warrants opus. Price claude-sonnet-5 at the promotional 33% off Sonnet 4.6 ($2.01 / $10.05, cache 0.201 / 0.5025) through 2026-08-31 via a dedicated pricing fragment that beats the bare 'sonnet' alias; revert to full rate when the promo ends. Bare 'sonnet' stays full-rate as a conservative fallback (prod prices the resolved claude-sonnet-5 id from the transcript). Update the model docs and the billing / usage / manifest / spawn tests. * feat(security): calibrate the guard WAF for RoboCo traffic + document the layer The first end-to-end run of the fastapi-guard layer showed active enforcement would block ~50% of legitimate agent traffic — RoboCo request bodies are code, SQL, diffs, file paths, HTML, and URLs, which the stock signature WAF reads as attacks. build_security_config now excludes RoboCo's free-text top-level body fields (derived from the real request models, including the free-form container fields whose nested prose is stringified and scanned) from WAF scanning, dropping the active-mode false-positive rate to zero while keeping the WAF on every non-excluded (id/enum/slug/branch) field and leaving the prompt-injection / secret-exfil / internal-SSRF validators — which run independently of the exclusion — fully in force. enable_penetration_detection is made explicit. Only excluded_detection_body_fields is reliable on guard 7.2.1: the per-route categories knob is bypassed for JSON bodies, and the body scanner excludes top-level keys only (scanning str(value) of every non-excluded field), so free-form container fields must be excluded wholesale. Adds tests/unit/test_security_middleware.py — the first end-to-end exercise of the middleware (mounts it, drives guard's lifespan, fires real requests): proves passive mode is log-only, active mode does not false-positive on realistic agent payloads, threats are still blocked inside excluded fields, and the WAF still fires on non-excluded fields. Docs: CHANGELOG (Unreleased); a user-facing Optional-subsystems page + nav + env reference for the HTTP security layer; the agent-facing RAG corpus (what it is + why a request could be blocked); and the roboco mapping (api-core-websocket / deployment-tooling / _complete_map). * feat(security): Surface N — scanner honeytrap (guard /api auto-ban + nginx edge-drop) Turns scanner probes against the scanner, in two layers matched to where traffic lands. Behind nginx only /api, /ws, /health, /ready reach the orchestrator, so guard can only see (and ban) scanner probes on those paths; the classic root probes (/.env, /wp-login.php, /phpmyadmin, /.git/config) hit the panel. So: - build_security_config's threat_ban_config gains recon / sensitive_file / cms_probing categories. A scanner probing those fingerprints on an /api path is detected on the URL-path scan; repeated probes from one IP trip an adaptive per-IP auto-ban (redis-backed, 24h). Only bans in active mode (passive logs the recon hit) and needs redis (the 24h ban exceeds the in-memory cap). The spec's decoy-route file is redundant — the WAF url-path scan bans regardless of a registered route — so it is intentionally omitted. - docker/nginx.conf drops the classic root scanner paths at the edge with 444 (connection closed, no response) before they reach the panel, anchored to known scanner fingerprints so /.well-known and every real panel/API route are untouched. Always on, independent of ROBOCO_GUARD_ENABLED. Tests: 2 unit (the exclusion set + the scanner-ban categories are present) and 2 integration (a decoy path is blocked in active mode, passes in passive). The nginx regex was validated against 15 scanner + 19 legit paths (0 false positives). Docs: CHANGELOG, the HTTP-security page, the roboco mapping, and the agent-facing RAG corpus. * Token optimization — per-role observability, compute policy, spawn preflight (#291) * test(models): lock the sonnet→claude-sonnet-5 MODEL_MAP invariant * feat(usage): surface cache tokens + cache_hit_rate in usage breakdowns * feat(usage): add per-role usage breakdown endpoint * feat(usage): add spawn-waste signal (per-role unproductive rate + respawn strikes) * feat(panel): surface per-role cost/cache + spawn-waste on the metrics page * feat(routing): Phase 2 per-role compute policy — qa→haiku, main_pm→sonnet, per-role effort env mechanism (default-inert) * feat(orchestrator): Phase 3 flag-gated spawn preflight — refuse non-gateway delivery roles (respawn-forever guard) * chore(compose): arm ROBOCO_SPAWN_PREFLIGHT_ENABLED on the NAS composes * docs: per-role usage observability, per-role compute policy, and spawn preflight --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com> * fix(panel): pin outputFileTracingRoot so the standalone build isn't broken by stray lockfiles * feat(routing): populate ROLE_EFFORT_MAP + wire the verified --effort flag (cell_pm/board/auditor to medium) * feat(gateway): omit empty context_briefing sections (Phase 4 payload compaction) * refactor(orchestrator): extract spawn chokepoint guards to restore xenon rank B on spawn_agent --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com>
20 KiB
Slice: api-routes-schemas
Purpose
The FastAPI surface of RoboCo: every HTTP route under roboco/api/routes/ (the operator/panel api/* CRUD + dashboard/orchestrator/a2a/live bridges) and the agent-gateway api/v1/flow/* (intent verbs) + api/v1/do/* (content tools), with Pydantic request/response schemas under roboco/api/schemas/. Routes are thin handlers that resolve services via Depends and return typed responses; all agent-gateway verbs funnel through the Choreographer.
Files
| Path | Role |
|---|---|
| roboco/api/routes/health.py | Liveness/readiness (DB + Redis probes). |
| roboco/api/routes/agents.py | List/get agents. |
| roboco/api/routes/channels.py | Channel CRUD + member ops. |
| roboco/api/routes/groups.py | Group create/list. |
| roboco/api/routes/sessions.py | Communication sessions + messages. |
| roboco/api/routes/messages.py | Message list/create/patch/delete. |
| roboco/api/routes/notifications.py | Notification list/ack/send. |
| roboco/api/routes/stream.py | Agent stream chunk/complete/extract + permissions. |
| roboco/api/routes/journals.py | Journal entries, search, growth stats. |
| roboco/api/routes/kanban.py | Per-team kanban boards + main-pm/board/stats. |
| roboco/api/routes/cockpit.py | Cockpit summary/signals. |
| roboco/api/routes/company_goals.py | Company goals get/put. |
| roboco/api/routes/settings.py | Settings + feature-flags get/set. |
| roboco/api/routes/dashboard.py | CEO/auditor/kanban/metrics/agents/activity dashboards. |
| roboco/api/routes/tasks.py | Task CRUD + lifecycle transitions (claim/start/verify/qa/complete...). |
| roboco/api/routes/work_session.py | Work-session list/commit/files/PR/merge/complete/abandon. |
| roboco/api/routes/git.py | Per-project git status/log/diff/commit/push/PR/rebase. |
| roboco/api/routes/project.py | Project CRUD + workspace/sync/access + conventions. |
| roboco/api/routes/product.py | Product CRUD. |
| roboco/api/routes/optimal.py | RAG: kb/search, rag/query, mentor/ask, learnings, decisions, review. |
| roboco/api/routes/research.py | Web search/fetch. |
| roboco/api/routes/orchestrator.py | CEO-gated spawn/stop/resolve-wait/mark-waiting + status. |
| roboco/api/routes/a2a.py | Agent-to-agent inbox/conversations/tasks + SSE streams. |
| roboco/api/routes/prompter_live.py | Live Intake chat (start/stream/messages/confirm/confirm-batch). |
| roboco/api/routes/secretary.py | Company state + CEO directives confirm/reject. |
| roboco/api/routes/secretary_live.py | Live Secretary chat (start/stream/messages/stop/events). |
| roboco/api/routes/release.py | CEO-only release proposal approve/reject. |
| roboco/api/routes/playbooks.py | Playbook approve/reject/archive (Auditor/CEO). |
| roboco/api/routes/pitch.py | Pitch create/list/approve/reject. |
| roboco/api/routes/provider.py | Provider catalog + ollama/grok/self-hosted key + mode. |
| roboco/api/routes/usage.py | Token usage summary/time-series/by-agent/team/model/role/sessions, cache-efficiency, spawn-waste (per-role unproductive-spawn rate + respawn strikes). |
| roboco/api/routes/system.py | System-wide info. |
| roboco/api/routes/docs.py | Project docs write/read/list/delete. |
| roboco/api/routes/v1/_role_dep.py | Per-role HMAC guards + envelope_to_response helper. |
| roboco/api/routes/v1/do.py | Content verbs /api/v1/do/* (commit/note/say/dm/evidence/playbook...). |
| roboco/api/routes/v1/flow_dev.py | Developer flow verbs. |
| roboco/api/routes/v1/flow_qa.py | QA flow verbs (claim/pass/fail_review). |
| roboco/api/routes/v1/flow_doc.py | Documenter flow verbs. |
| roboco/api/routes/v1/flow_cell_pm.py | Cell-PM flow verbs (delegate/submit_up/triage/complete...). |
| roboco/api/routes/v1/flow_main_pm.py | Main-PM flow verbs (submit_root/triage_all/escalate_to_ceo...). |
| roboco/api/routes/v1/flow_board.py | Board (product_owner/head_marketing) triage/escalate_to_ceo. |
| roboco/api/routes/v1/flow_auditor.py | Auditor triage/i_am_idle. |
| roboco/api/routes/v1/flow_pr_reviewer.py | PR-reviewer verbs incl. gate pr_pass/pr_fail. |
| roboco/api/schemas/*.py | Per-domain Pydantic request/response models (one per route file). |
| roboco/api/schemas/v1/flow.py | All flow-verb request bodies + StrList coercion validator. |
| roboco/api/schemas/v1/do.py | All do-verb request bodies. |
Key Endpoints
| Method | Path | Handler (file) | Auth/Role |
|---|---|---|---|
| GET | /api/health, /api/ready | health.py | none |
| GET | /api/dashboard/ceo | dashboard.py | agent context |
| GET | /api/dashboard/metrics/{cycle-time,bottlenecks,rework,scorecard/*} | dashboard.py | agent context |
| GET/POST/PATCH/DELETE | /api/tasks, /api/tasks/{id}/{claim,start,verify,submit-qa,pass-qa,fail-qa,complete,cancel,escalate-to-ceo} | tasks.py | agent context + require_task_action |
| GET/POST | /api/orchestrator/{status,agents/{id},waiting} ; /spawn,/stop,/resolve-wait,/mark-waiting | orchestrator.py | _require_ceo (HMAC) |
| POST | /api/a2a/{send,send-stream} ; /chat/conversations ; /tasks/{id}/cancel | a2a.py | require_any_authenticated_agent |
| POST | /api/prompter/live, /live/{id}/{stream,status,messages,stop,confirm,confirm-batch} | prompter_live.py | require_panel_token (CEO HMAC) |
| POST | /api/secretary/live, /live/{id}/{stream,messages,stop,events} ; /api/secretary/{state,directives} | secretary*.py | panel token / agent ctx |
| GET/POST | /api/release/proposal, /proposal/approve, /proposal/reject | release.py | _require_ceo (agent.role==CEO) |
| GET/POST | /api/playbooks, /{id}/{approve,reject,archive} | playbooks.py | agent context (Auditor/CEO) |
| GET/POST/PUT/DELETE | /api/projects, /{id}/conventions, /workspace, /sync | project.py | agent context |
| POST | /api/v1/flow/developer/{give_me_work,i_will_work_on,open_pr,i_am_done,unclaim,resume,sync_branch} | flow_dev.py | require_dev (role + HMAC) |
| POST | /api/v1/flow/qa/{claim_review,pass_review,fail_review} | flow_qa.py | require_qa |
| POST | /api/v1/flow/cell_pm/{delegate,submit_up,complete,triage,unblock,reassign} | flow_cell_pm.py | require_cell_pm |
| POST | /api/v1/flow/main_pm/{submit_root,triage_all,escalate_to_ceo,complete} | flow_main_pm.py | require_main_pm |
| POST | /api/v1/flow/pr_reviewer/{claim_pr_review,claim_gate_review,pr_pass,pr_fail,post_pr_review} | flow_pr_reviewer.py | require_pr_reviewer |
| POST | /api/v1/do/{commit,note,say,dm,notify,evidence,draft_playbook,approve_playbook,...} | do.py | require_any_authenticated_agent (HMAC, any role) |
| GET | /ws/{channels,agents,sessions,notifications,system}/{id} | websocket.py | WS panel/HMAC token |
Key Symbols
| Name | Kind | File:Line | Responsibility |
|---|---|---|---|
require_any_authenticated_agent |
dep | v1/_role_dep.py | HMAC-verify X-Agent-ID/role/team token; router-level guard on do + a2a. |
require_<role> (require_dev/qa/...) |
dep | v1/_role_dep.py | Per-role guard: HMAC + role assertion, applied as router dependency. |
envelope_to_response |
fn | v1/_role_dep.py | Convert Choreographer Envelope to JSON, set status from envelope.status. |
_check_agent_auth_token |
fn | api/deps.py:217 | Core HMAC verify; rejects invalid tokens even in dev; required-only in prod. |
require_panel_token |
dep | api/deps.py:251 | CEO-signed HMAC gate for live-chat bridges (HTTP analog of WS gate). |
CurrentAgentContext |
dep | api/deps.py:376 | Resolves agent from headers + HMAC, injects AgentContext. |
_require_ceo |
dep | routes/orchestrator.py:37 | Router-level CEO-HMAC guard on orchestrator control routes. |
setup_middleware |
fn | api/middleware.py | Register exception handlers (422 scrub, HTTP, RobocoError, generic). |
request_validation_handler |
fn | api/middleware.py:407 | Log 422 body (secrets scrubbed) + uuid remediate hint. |
_scrub_secrets |
fn | api/middleware.py:389 | Deep-redact known secret fields from logged 422 bodies. |
StrList |
type | schemas/v1/flow.py:20 | list[str] with coerce_str_list BeforeValidator (XML-nested LLM lists). |
Choreographer |
svc | services/gateway/choreographer.py | Composes service intents behind every flow verb. |
ContentActions |
svc | services/gateway/ | Composes do-verb content actions (commit/note/say/...). |
router (do) |
router | v1/do.py:38 | /api/v1/do router, require_any_authenticated_agent dep. |
router (flow_dev) |
router | v1/flow_dev.py:24 | /api/v1/flow/developer router, require_dev dep. |
Data Flow
Request hits nginx (port 3000) -> FastAPI app (api/app.py) registers routers under /api/* plus /api/v1/flow/* and /api/v1/do/*. Middleware chain (CorrelationId -> RequestLogging) attaches a correlation ID and logs; exception handlers intercept 422/HTTP/RobocoError/generic. Router-level Depends resolves DbSession + agent context (HMAC-verified from X-Agent-* headers) and, on agent-gateway routes, the role guard. The thin handler pulls a service via Depends (TaskService, Choreographer, ContentActions, GitService, OptimalService, ReleaseProposalService...) and returns a typed Pydantic response; flow/do verbs return the Choreographer Envelope via envelope_to_response. SSE (EventSourceResponse) is used for live-chat streams and a2a send-stream.
Mermaid
graph TD
app[FastAPI app.py]
app -->|/api/*| ops[Operator/Panel routes]
app -->|/api/v1/flow/*| flow[Flow routers]
app -->|/api/v1/do/*| do[do router]
app -->|/ws/*| ws[websocket.py]
ops --> tasks[tasks.py -> TaskService]
ops --> dash[dashboard.py -> MetricsService]
ops --> orch[orchestrator.py -> AgentOrchestrator CEO-gate]
ops --> a2a[a2a.py -> A2AService SSE]
ops --> livep[prompter_live.py -> PrompterService SSE panel-token]
ops --> lives[secretary_live.py -> SecretaryService SSE]
ops --> rel[release.py -> ReleaseProposalService CEO-gate]
flow --> fdev[flow_dev -> Choreographer.give_me_work/i_will_work_on/...]
flow --> fqa[flow_qa -> Choreographer.claim_review/pass/fail]
flow --> fpm[flow_cell_pm/main_pm -> Choreographer.delegate/submit_up/submit_root]
flow --> fpr[flow_pr_reviewer -> Choreographer.pr_pass/pr_fail]
do --> doR[do.py -> ContentActions.commit/note/say/dm/evidence]
flow -.->|HMAC role guard| _role_dep[_role_dep.py]
do -.->|HMAC any-role guard| _role_dep
orch -.->|HMAC CEO guard| deps[deps._require_ceo]
livep -.->|panel HMAC| deps2[deps.require_panel_token]
ws --> cm[ConnectionManager -> StreamEventBus]
Logical Tree
roboco/api/
├── routes/
│ ├── operator-panel (api/*)
│ │ ├── health.py liveness/readiness
│ │ ├── agents.py agent list/get
│ │ ├── channels.py channel CRUD + members
│ │ ├── groups.py group create/list
│ │ ├── sessions.py comms sessions + messages
│ │ ├── messages.py message CRUD
│ │ ├── notifications.py notification ack/send
│ │ ├── stream.py agent stream chunks/extract
│ │ ├── journals.py journal entries + growth
│ │ ├── kanban.py kanban boards
│ │ ├── cockpit.py cockpit summary/signals
│ │ ├── company_goals.py company goals
│ │ ├── settings.py settings + feature-flags
│ │ ├── dashboard.py CEO/auditor/metrics dashboards
│ │ ├── tasks.py task CRUD + lifecycle
│ │ ├── work_session.py work-session/PR/merge
│ │ ├── git.py per-project git ops
│ │ ├── project.py project CRUD + conventions
│ │ ├── product.py product CRUD
│ │ ├── optimal.py RAG kb/query/mentor
│ │ ├── research.py web search/fetch
│ │ ├── docs.py project docs
│ │ ├── system.py system info
│ │ └── usage.py token usage
│ ├── ceo-gated / live bridges
│ │ ├── orchestrator.py CEO spawn/stop/mark-waiting
│ │ ├── release.py release proposal approve/reject
│ │ ├── playbooks.py playbook curation
│ │ ├── pitch.py pitch approve/reject
│ │ ├── a2a.py agent-to-agent + SSE
│ │ ├── prompter_live.py live Intake chat
│ │ ├── secretary.py company state + directives
│ │ ├── secretary_live.py live Secretary chat
│ │ └── provider.py provider catalog/keys
│ └── v1/ (agent-gateway)
│ ├── _role_dep.py HMAC role guards + envelope helper
│ ├── do.py /api/v1/do/* content verbs
│ ├── flow_dev.py developer flow verbs
│ ├── flow_qa.py QA flow verbs
│ ├── flow_doc.py documenter flow verbs
│ ├── flow_cell_pm.py cell-PM flow verbs
│ ├── flow_main_pm.py main-PM flow verbs
│ ├── flow_board.py board flow verbs
│ ├── flow_auditor.py auditor flow verbs
│ └── flow_pr_reviewer.py PR-reviewer flow verbs
└── schemas/
├── *.py per-domain Pydantic models
└── v1/
├── flow.py flow-verb bodies + StrList
└── do.py do-verb bodies
Dependencies
- FastAPI + sse-starlette (SSE), pydantic v2.
roboco/api/deps.py— shared deps (DbSession, agent context, HMAC, orchestrator).roboco/api/middleware.py— exception handlers + correlation/log middleware.roboco/services/*— TaskService, GitService, OptimalService, AgentOrchestrator, Choreographer, ContentActions, ReleaseProposalService, PrompterService, SecretaryService, MetricsService, etc.roboco/foundation/identity.py(Role) +roboco/agents_config.py(verify_agent_token,CEO_AGENT_ID).roboco/api/websocket.py+websocket_bridge.py(WS event forwarding).
Entry Points
roboco/api/app.pycreate_app()builds the FastAPI app, mounts all routers under/api(prefix) +/ws(WS router).roboco/api/routes/v1/_role_dep.pyis imported by every flow router + do + a2a for HMAC/role guards andenvelope_to_response.roboco/api/routes/orchestrator.pyrouter constructed withdependencies=[Depends(_require_ceo)](router-wide CEO gate).
Config Flags
- Auth-gate mode:
_auth_required()(env-driven; HMAC mandatory in prod-ish, optional in dev) —api/deps.py. - Feature-flag routes are inert when their backing engine is off:
release.py(ROBOCO_RELEASE_MANAGER_ENABLED),prompter_live.pyMegaTask batch,optimal.pylearnings (ROBOCO_ORG_MEMORY_ENABLED),research.py(ROBOCO_RESEARCH_ENABLED),provider.pygrok/self-hosted (ROBOCO_GROK / self-hosted), CI-watch/dep-update originate elsewhere but surface via orchestrator/tasks.
Gotchas
do+a2arouters are token-only (any authenticated role), not role-asserted — any signed agent can call any content verb; service-layer scope is the only gate.request_validation_handlerscrubs secrets from the log but the 422 response body echoes the client's submission unchanged (comment explicit) — secrets can still leak to the caller if the caller is not the legitimate owner.- SSE live-chat bridges open one session per query/stream and rely on
require_panel_token(CEO HMAC injected by nginx); a missing/invalid token in dev mode is tolerated (_auth_required()false) — prod must arm it. StrListBeforeValidator is load-bearing: without it the Claude SDK's XML-nested list input crashesi_will_plan/delegatewith 422 (MegaTask memory Bug 3).orchestrator.pyandrelease.pyuse two different_require_ceoimplementations (HMAC header vsagent.role==CEOfrom context) — keep their semantics aligned.- WS endpoints live on
/ws/*(separate router inwebsocket.py), not under/api; the bridge subscribes toStreamEventBusand forwards per resource-id.
Drift from CLAUDE.md
- CLAUDE.md lists
pr_pass/pr_failunderpr_reviewerverbs and the in-path gate; code matches (flow_pr_reviewer.pyexposesclaim_gate_review,pr_pass,pr_fail). No drift found. - CLAUDE.md says agent comms use
say/dm/notifyvia do_server; code matches (v1/do.pyexposes all three). No drift. - CLAUDE.md lists
sync_branchas a developer verb; present inflow_dev.py:125. No drift. - CLAUDE.md's verb table omits
flow_pr_reviewer.post_pr_review(external PR comment) — present in code; additive, not contradictory. - None material.
Changes Since Baseline
git log fd10cc86..HEAD -- roboco/api/routes/ roboco/api/schemas/:
15effce0Chore: 141 Gaps fill-in (#283) — broad route/schema hardening pass (the only logic-touching commit in range).
(Baseline..HEAD contains a single sweep commit touching this slice; earlier per-fix commits predate the baseline.)
Regression Risks
| Title | File:Line | Claim | Severity |
|---|---|---|---|
| do/a2a any-role token gate | v1/do.py:43, a2a.py:114 | require_any_authenticated_agent only verifies HMAC + that the agent exists; it does NOT assert the role matches the verb's intended role family — a QA-signed token could call do/commit or a dev could call a2a admin paths. Service-layer scope is the sole guard; a missed service check = privilege escape. |
High |
| 422 response echoes secrets | middleware.py:407 | _scrub_secrets redacts only the log body; the JSON response still contains body with the caller's original secret fields. A 422 on git_token/api_key returns the secret back to the client (and to any MITM/log of the response). |
High |
| orchestrator CEO gate vs release CEO gate divergence | orchestrator.py:37 vs release.py:32 | Two independent _require_ceo implementations: orchestrator uses HMAC header verification, release uses agent.role == CEO from CurrentAgentContext. If one path's HMAC/context resolution drifts, the two CEO surfaces enforce different identities. |
Medium |
| SSE transport errors swallowed | prompter_live.py:122, secretary_live.py:61, a2a.py:195 | EventSourceResponse streams run long-lived; a Choreographer/orchestrator raise mid-stream is caught by contextlib suppress but can drop the stream silently without a terminal event to the panel. |
Medium |
| Cross-repo PR collision via /api/work-sessions/{id}/pr/merge | work_session.py:259 | PR merge by global pr_number (no project_id scoping in the route signature) — the same class of cross-repo collision already fixed in cell_pm_complete could recur if this endpoint is wired to merge. |
Medium |
| Dashboard/metrics endpoints role-gating | dashboard.py:58+ | /ceo, /auditor, /scorecard/* rely on CurrentAgentContext but the route-level gating is weak (no explicit require_pm_or_above); a non-CEO agent calling /dashboard/ceo is filtered only by service-layer logic, not the router. |
Medium |
| WS panel-token vs agent-token dual gate | websocket.py / deps.py | /ws/* endpoints use a WS-specific _require_panel_token for panel streams but agent-id keying for /ws/agents/{id}; mismatched HMAC secret rotation between the two could grant panel read of agent streams or vice-versa. |
Low-Med |
flow i_will_plan StrList crash recurrence |
schemas/v1/flow.py:20 | If a new LLM-authored list[str] field is added to a flow schema without StrList, the SDK XML-nesting crash reappears (silent 422 loop). Reviewer-only by inspection. |
Low-Med |
Health
The route layer is thin, consistently organized (one router per domain, one schema file per router), and the agent-gateway HMAC guard is centralized in _role_dep.py + deps.py. Main risks are the any-role do/a2a gate (relies on service-layer scope), the 422 response echoing secrets, and the two divergent CEO guards — all addressable without structural change. SSE live-chat streams are the fragile transport path.