Files
roboco/docs/map/api-routes-schemas.md
T
0ca9d91b72 v0.16.0: fastapi-guard HTTP security layer — calibrated + scanner honeytrap (#290)
* [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>
2026-07-01 23:54:48 +02:00

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.py create_app() builds the FastAPI app, mounts all routers under /api (prefix) + /ws (WS router).
  • roboco/api/routes/v1/_role_dep.py is imported by every flow router + do + a2a for HMAC/role guards and envelope_to_response.
  • roboco/api/routes/orchestrator.py router constructed with dependencies=[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.py MegaTask batch, optimal.py learnings (ROBOCO_ORG_MEMORY_ENABLED), research.py (ROBOCO_RESEARCH_ENABLED), provider.py grok/self-hosted (ROBOCO_GROK / self-hosted), CI-watch/dep-update originate elsewhere but surface via orchestrator/tasks.

Gotchas

  • do + a2a routers 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_handler scrubs 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.
  • StrList BeforeValidator is load-bearing: without it the Claude SDK's XML-nested list input crashes i_will_plan/delegate with 422 (MegaTask memory Bug 3).
  • orchestrator.py and release.py use two different _require_ceo implementations (HMAC header vs agent.role==CEO from context) — keep their semantics aligned.
  • WS endpoints live on /ws/* (separate router in websocket.py), not under /api; the bridge subscribes to StreamEventBus and forwards per resource-id.

Drift from CLAUDE.md

  • CLAUDE.md lists pr_pass/pr_fail under pr_reviewer verbs and the in-path gate; code matches (flow_pr_reviewer.py exposes claim_gate_review, pr_pass, pr_fail). No drift found.
  • CLAUDE.md says agent comms use say/dm/notify via do_server; code matches (v1/do.py exposes all three). No drift.
  • CLAUDE.md lists sync_branch as a developer verb; present in flow_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/:

  • 15effce0 Chore: 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.