Commit Graph
15 Commits
Author SHA1 Message Date
Renn F 4fdde2b082 fix(gateway): evidence/QA/doc paths populate files_changed from git
Bug:
    ContentActions.evidence() hard-coded files_changed=[] and diffed
    against HEAD~1 instead of the branch's parent. QA's _build_qa_claim_
    evidence (and doc/_impl mirrors) sourced files_changed from
    work_session.files_modified, which the gateway commit() never
    populates (no add_files_modified plumbing). Result: QA / docs / PM
    reviewers saw an empty change list on real PRs and only the latest
    commit's delta — flagged in smoke-9 when PR #20 showed the README
    change on GitHub but evidence() reported empty.

Fix:
    Added GitService.list_changed_files (git diff --name-only against
    parent branch). evidence(), _build_qa_claim_evidence,
    _claim_doc_evidence, and _build_i_am_done_ok all source files_changed
    from this — git is the authoritative source. evidence() also drops
    the HEAD~1 base so the diff is the full PR.

    Wired EvidenceRepo into ContentActionsDeps so evidence() returns
    journal_highlights too, matching the QA/doc shape.
2026-05-15 06:31:38 +02:00
Renn F 1bd6eb3372 fix(gateway): journal task_id auto-injection works from blocked/paused
Smoke-5 root cause. Agents wrote 5 decisions / 8 reflections / 1 struggle
during the run — every single entry persisted with task_id=NULL. The C8
tracing gate then never saw them and PMs spiraled forever on
'missing: journal:decision' while their decisions sat orphaned.

Cause: ContentActions.note/say/dm/notify called
TaskService.get_active_task_for_agent for task_id auto-injection. That
helper filters to _DEV_ACTIVE_STATUSES = {claimed, in_progress,
verifying, awaiting_qa, awaiting_documentation}. BLOCKED, PAUSED, and
NEEDS_REVISION fall outside that set — so the moment an agent gets
stuck (which is exactly when they journal), auto-injection returns None
and the entry persists without task_id.

Fix:
- New TaskService.get_journal_context_task_for_agent — same shape as
  get_active_task_for_agent but the status set
  _JOURNAL_CONTEXT_STATUSES adds BLOCKED, PAUSED, NEEDS_REVISION.
- ContentActions.note/say/dm/notify use the new lookup.
- ContentActions.commit keeps the narrow get_active_task_for_agent —
  can't commit from blocked, so the dev-active set is correct there.

Tests:
- tests/unit/services/test_journal_context_lookup.py — 5 tests pinning
  the two queries: journal-context INCLUDES blocked/paused/needs_revision,
  dev-active EXCLUDES them.
- Existing content-actions tests updated to stub the new method
  alongside the old one.

This alone may be 70% of what was killing smoke runs end-to-end.
2026-05-14 04:21:07 +02:00
Renn F 6550d69b75 fix(gateway): B4 decision/reflect remediate includes literal call example
Smoke run 3 showed Main PM taking 7 attempts to satisfy the decision-note
required-fields contract — the remediate listed which fields were
missing but didn't show what a fully-formed call looks like. The LLM
pattern-matches examples better than field-list prose; each retry it
dropped a different field.

Added a literal note(scope='decision', ...) / note(scope='reflect', ...)
call template to the rejection remediate so the agent sees the canonical
shape with named-keyword args and example values. The missing-fields
list stays — both pieces of information are useful, but the example is
what actually drives convergence.

Spec ref: docs/superpowers/specs/2026-05-12-post-smoke-3-fixes-design.md
section B4.
2026-05-12 04:35:13 +02:00
Renn F ce92829385 fix: clear 55 pre-existing test failures uncovered after Wave A landed
Three classes of failure, all surfaced once Wave A's plan-required gate
and the migration 013 went in. Per project standing rule: pre-existing
errors are not a free pass — fix them.

1. Wave A1 ordering (32 lifecycle parity failures + 1 full-pipe test).
   _pm_sub_tasks_gate fired BEFORE _claim_plan_start_gate, so wrong-state
   PMs got `incomplete_input` (the gate's verdict) when the spec's
   lifecycle gate should have returned `invalid_state` first. Swapped:
   re-entry check → spec lifecycle gate → sub_tasks gate → claim_plan_run.
   Parity test now sees the spec's verdict as expected.

2. E2 enum naming (2 migration_013 failures + ripple).
   _str_enum in roboco/db/tables.py didn't pass name=… to SQLAlchemy
   Enum(...), so Base.metadata.create_all in test setup inferred
   `role` from the Python class `Role` while the alembic migrations
   declare `agentrole`. Tests saw two enums for the same class and
   hit `agentrole = role` operator errors. Fixed: default name to
   lower(class_name) (matches every migration), override `Role` →
   `agentrole`. One dict entry; no class-by-class registration needed.

3. _MockContentActions.note() signature drift.
   Wave 2 G4 added `structured` kwarg to ContentActions.note().
   The integration mock at tests/integration/v2/test_full_pending_to_completed.py
   didn't accept the new kwarg → 1 test failed on the very first call
   from the v2 do/note route. Added `structured: object = None` and
   left it unused (the test asserts lifecycle, not journal rendering).

Plus three ruff E501 line-length fixes in the test files I touched.

Quality: ruff + mypy clean. pytest 6690 passed / 0 failed / 274 skipped.
2026-05-12 04:22:03 +02:00
Renn F 62d1084a0c fix(gateway): notify_list/get/ack call NotificationDeliveryService (not Service)
Wave 1 wired notify_list/get/ack into ContentActions but pointed them at
`self.notifications` (which is NotificationService — sender side, with
send_blocker_notification / send_qa_ready_notification / etc.). The
read methods (list_for_agent, get_for_recipient_and_mark_read,
acknowledge) live on `NotificationDeliveryService` instead.

Smoke run 2026-05-11 surfaced this immediately:
  AttributeError: 'NotificationService' object has no attribute 'list_for_agent'

Fixes:
- roboco/api/deps.py — import NotificationDeliveryService and wire it
  in as a new ContentActionsDeps field `notification_delivery`.
- roboco/services/gateway/content_actions.py — add notification_delivery
  to ContentActionsDeps (Optional with `None` default for back-compat
  with any tests that don't supply it). Point notify_list, notify_get,
  notify_ack at self._deps.notification_delivery.
- tests/unit/gateway/test_content_actions.py — _make_deps adds a default
  AsyncMock for notification_delivery so existing tests continue to pass.

Quality: ruff + mypy clean. 505 unit tests pass.
2026-05-11 08:41:46 +02:00
Renn F 72e01a7f13 feat(gateway): Wave 2 pre-gateway parity — structured note, sub_tasks, channels
Three Wave 2 gaps from the 2026-05-11 pre-gateway parity design:

G4 — note() decision/reflect now require structured fields at the gateway
(pre-gateway `Field(...)` parity). Returns `incomplete_input` envelope
with field-by-field hints when any required field is missing.
  - decision: context (str), options (list[{name,pros,cons}] min len 2),
    chosen (str), rationale (str). `consequences` and `next_steps` are
    now list[str] (was str). Renderer emits each option as a "### Name
    + Pros / Cons" block instead of a bullet — matches the pre-gateway
    DecisionOption sub-shape exposed in `roboco/mcp/schemas/__init__.py`
    at `254cc93`.
  - reflect: what_done, what_learned, what_struggled (each non-empty
    str). next_steps stays optional.
  - Bumped tests/unit/gateway/test_content_actions.py with explicit
    pass-with-N-options coverage (≥2 floor; 3-option case green).

G5 — i_will_plan now persists sub_tasks alongside approach / risks /
open_questions / technical_considerations. The Plan tab's Sub-Tasks
section was empty because the verb didn't accept the field. Choreographer
server-assigns id + order to each sub_task (pre-gateway build_plan_data
parity) and normalizes every list entry to the EXACT shape
`panel/src/types/index.ts::TaskPlan` consumes:
  - SubTask: {id, title, description, completed:false, order,
    estimated_hours:null, notes:null}
  - Risk: {description, mitigation, severity:null} — accepts the
    {risk, mitigation} pre-gateway shape too
  - OpenQuestion: {question, answer:null, answered_by:null,
    answered_at:null} — accepts a bare string fallback
The normalization lives in three small module-level helpers
(_normalize_sub_task / _normalize_risk / _normalize_open_question)
called from _build_panel_shaped_plan, keeping i_will_plan's branch
count under PLR0912.

G6 — new `channels()` verb returns the agent's readable + writable
channel slugs from foundation.policy.communications. Stops invented
slugs ("backend-dev", "backend") that we kept seeing in smoke runs.
Added to every role's manifest including auditor (read-only access).

Wired through:
- roboco/api/schemas/v2/do.py — list-typed consequences/next_steps,
  dict-typed options, ChannelsRequest
- roboco/api/schemas/v2/flow.py — IWillPlanRequest.sub_tasks
- roboco/api/routes/v2/do.py — /channels endpoint
- roboco/api/routes/v2/flow_*.py — pass sub_tasks through
- roboco/services/gateway/content_actions.py — channels() method;
  _check_scope_required_fields enforces decision/reflect structure;
  _render_option_block emits per-option markdown blocks
- roboco/services/gateway/choreographer/_impl.py — _build_panel_shaped_plan
  helper used by i_will_plan
- roboco/services/gateway/role_config.py — _CHANNEL_DISCOVERY tuple
  on every role
- roboco/mcp/do_server.py — channels() tool + note() signature with
  options as list[dict[str,str]]
- roboco/mcp/flow_server.py — i_will_plan signature with sub_tasks

Frontend: no code change. panel/src/types/index.ts already declares
the exact shape we now write; panel/src/components/tasks/task-detail/
{tab-plan,tab-progress,tab-sessions,tab-notes}.tsx already reads it.
The empty panels we observed were a backend write-side problem, not
a frontend read-side problem — Wave 1 + Wave 2 close it.

Quality: ruff + mypy clean. 505 unit tests pass (added 2 new tests on
decision-scope requirements, updated 3 existing tests to fit the
pre-gateway-parity contract).

Spec ref: docs/superpowers/specs/2026-05-11-pre-gateway-parity-design.md
2026-05-11 05:57:57 +02:00
Renn F 9aa30fb945 100% Coverage 2026-05-06 21:02:31 +02:00
Renn F 4829f93a68 fix(gateway): unblock task claim; full Phase 0/1/2 remediation
Resolves the 100% claim-failure rate introduced by the gateway rewrite
  (commit 62bda0c plus 78 follow-ups). Live smoke runs hit
  `404 /api/v2/flow/developer/...` on every dev verb plus a manifest
  fallback that silently exposed off-role verbs to PMs — confirmed
  firing simultaneously in NAS agent logs (be-dev-1, be-pm, main-pm).

  Audit reports under docs/internal/audit_2026_05_04/ catalogue 49
  defects across gateway, services, prompts, MCP transport, substrate,
  and tests (8 detail reports + master synthesis). Six smoking guns;
  three proven in production logs.

  Phase 0 — unblock claim:
  - URL prefix /api/v2/flow/dev → /developer; slug-map board roles
    (product_owner, head_marketing) → /board (D-01)
  - _i_will_work_on AttributeError on None across pending /
    needs_revision / claimed re-entry branches (D-02)
  - Seed last_heartbeat_at in _qa_or_doc_claim (D-03)
  - Drop misleading i_have_committed verb; dev flow uses commit() (D-04)
  - Manifest mount via compose; flow_server + do_server fail loud
    instead of exposing all-verbs fallback (D-12)
  - MCP _post() surfaces envelope body on 4xx so agents see remediate
    hints (D-13)
    on git failure so retries aren't blocked by half-state (S-01)

  Phase 1 — lifecycle stability:
  - _resolve_skill falls back to AgentTable.capabilities (D-06)
  - main_pm_complete uses kwargs for escalate_to_ceo (D-07)
  - i_am_done auto-runs submit_verification when in_progress (D-08)
  - active_claimant_id wired in claim/unclaim paths — single-claimant
    invariant now functional (D-05)
  - qa_pass/qa_fail assert claimed_by parity with qa_agent_id (D-18)
  - Prompt-drift sweep: fail() shape, i_am_done(task_id, notes),
    subtask cap (12 hard / 8 soft), error-code symbology rewritten in
    base.md + per-role anti-patterns (D-10/11/29/30/31, D-37)

  Phase 2 — invariants + architecture:
  - Real-DB integration test exercising claim → in_progress → commit
    → submit_for_qa → i_am_done → awaiting_qa (P2-1)
  - choreographer.py → package; 3 of 6 role mixins extracted
    (board, doc, qa). _impl.py 2,526 → 2,080 lines (-18%). Continuation
    plan in docs/internal/audit_2026_05_04/p2_2_decompose_plan.md (P2-2)
  - Closure guards consolidated via _subtasks_not_terminal_envelope (P2-3)
  - TaskService.unclaim_for_reaper routed through canonical
    _validate_and_set_status; in_progress → pending added to
    VALID_TRANSITIONS (P2-4)
  - Dead code removed: i_am_done_with_catchup verb, _run_catch_up helper
    (P2-5)
  - 6 state-machine invariants asserted via property test (P2-6)
  - attempt_id (uuid4) stamped on every gateway.rejected audit row (P2-7)
  - _reconcile_orphan_claims_on_startup rolls back tasks left CLAIMED
    with branch_name=NULL from prior crashes (P2-8)
  - scripts/regenerate_verb_tables.py introspects Pydantic schemas +
    role_config; compose_prompt injects per-role tables as a layer.
    Eliminates the prompt-drift class structurally (P2-9)

  Other:
  - D-48: orchestrator mounts host's ~/.claude.json when present so
    agents don't boot from backup recovery on every spawn
  - D-49: dev dispatcher rejects role-mismatched spawns (e.g. doc task
    assigned to dev agent)

  Tests: 553 pass · ruff + mypy clean. Live NAS smoke verification
  pending — needs the stack brought back up.
2026-05-04 23:43:55 +02:00
Renn F cfdd389c43 test(gateway): regression for ContentActions.commit role gate
Pin: PM, QA, Board cannot call commit; developer + documenter can.
Smoke 2026-05-03 saw main-pm reach the git layer with a 'commit' call
attempting to author a fix to the very gateway bug we were hitting.
2026-05-03 23:01:44 +02:00
Renn F c61a3bf81e fix(gateway): role-gate ContentActions.commit (developers + documenters only)
Smoke 2026-05-03 saw main-pm reach the git layer with a 'commit' call,
trying to author 'fix(gateway): allow claimed status in i_will_plan
preflight'. That should never have been possible — main_pm/cell_pm/
board/auditor/qa manifests all exclude commit. Reaching the verb body
means either the MCP manifest filter mis-routed, or the agent hit the
v2 do.py route directly.

Mirror Task 16 notify pattern: server-side role check in the verb body
rejects with not_authorized + 'PMs delegate, do not commit' remediate.
Defense-in-depth — manifest is still the primary gate.
2026-05-03 22:57:28 +02:00
Renn F 3a2498a609 feat(gateway): notify(target, text, scope?) for PMs and Board
Pre-gateway PMs/Board sent formal notifications (require ack); gateway
had say/dm only. Now PMs and Board can issue ack-required notifications
via NotificationService through the standard envelope path.
2026-05-03 09:19:08 +02:00
Renn F 68466e9bf0 fix(gateway): enforce [task-id] prefix on every commit
ContentActions.commit was stripping user-supplied prefixes but never
re-adding the canonical one. Dev prompt promised auto-prefix; code
delivered nothing. Now every gateway commit lands with [task-id-short].

Format choice: simple [task_id[:8]] (8-char), matching the dev prompt
("Auto-prefixes [task-id]") and CLAUDE.md's documented commit format.
The legacy templates/git/commit.py uses the richer
[root_short:task_short] for the commit_for_task API path with full
traceability metadata; the gateway commit path is intentionally simpler
and stays aligned with the prompt-level promise.
2026-05-03 06:23:43 +02:00
Renn FandClaude Opus 4.7 855cd24477 feat(gateway): restore Gate Set D content-tool ownership guards
When a caller passes an explicit task_id to commit / note / say / dm /
evidence, ContentActions now verifies task.assigned_to == caller_agent_id
before allowing the side effect. Auto-fill from get_active_task_for_agent
is implicitly self-owned and does not need a re-check.

evidence() additionally allows assigned_to=None (post-handoff transient
state) so QA / documenter can inspect tasks between reassignments.

Pre-gateway, agents could not even see tasks they didn't own because the
MCP handlers resolved task from session context. The gateway exposes
task_id parameters across multiple verbs, so the explicit ownership
gate is required.

Exception: say() and dm() with NO task_id are exempt — used for channel
announcements and off-task A2A. The strict guard only applies when the
agent supplies a task_id parameter.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 03:31:10 +02:00
Renn F 249e9c2c59 fix(gateway): align content_actions with actual service method names
content_actions.note/dm/say/commit/evidence called write_entry/send/
post_to_channel/git.commit/git.diff(base=)/fetch_branch_for_inspection,
none of which existed on JournalService/A2AService/MessagingService/
GitService/WorkspaceService. Live smoke threw AttributeError on every
content tool. Add the matching gateway-shaped adapters on each service
(scope-string -> JournalEntryType for note; channel-by-slug -> default
group -> active session for say; UUID-or-slug recipient resolution for
dm; branch-name commit + diff(base=) for commit/evidence; project-aware
fetch_branch_for_inspection on workspace). Surfaced live.
2026-05-02 21:48:07 +02:00
62bda0c497 Gateway/full (#9)
* chore(gateway): scaffold gateway package and test layout

* feat(config): add gateway feature flags, coordination thresholds, commit-validator settings

* feat(gateway): add standardized response envelope with ok/error variants

* feat(gateway): add remediation hint catalog for tracing-gap and invalid-state errors

* feat(gateway): add per-role flow/do tool catalog with developer, qa, doc, pm, board configs

* feat(db): add gateway columns — active_claimant_id, heartbeat, pre_block snapshot, acceptance_criteria_status, qa_evidence_inspected

* feat(db): create gateway_triggers table for dispatcher decision logging

* feat(db): align canonical skill set; substitute qa_review -> code_review across agent seeds

* fix(db/008): make skill alignment in-place + idempotent; preserve column and existing custom skills

* feat(gateway): add claimant_lock for single-active-agent invariant with heartbeat staleness

* feat(gateway): add trigger_filter with stale-cleanup, claimant-queue, and cooldown rules

* feat(gateway): add tracing_gate with plan, progress, journal, acceptance_criteria, qa requirements

* refactor(gateway): drop per-file ruff ignores; refactor tracing_gate with dispatch table + GateContext

* feat(gateway): add merge_chain to resolve PR target by branch hierarchy depth

* feat(gateway): add commit_validator with min-length, banned-words, and conventional-shape hints

* feat(gateway): add evidence_builder for verb-response evidence and capped context_briefing

* feat(gateway): add Choreographer skeleton with per-phase verb signatures and DI protocols

* feat(runtime): add spawn_manifest builder for per-role pre-loaded tool registration

Introduces SpawnInputs dataclass + build_for_role(inputs) + write_manifest()
in roboco/runtime/spawn_manifest.py; reads role_config for allowed verbs/tools,
emits JSON manifest that SDK shim reads at container startup to eliminate ToolSearch.

* feat(runtime): wire gateway pre-spawn check (trigger_filter + claimant_lock) into orchestrator behind ROBOCO_GATEWAY_ENABLED flag

- Add GatewayTriggerTable SQLAlchemy ORM model to roboco/db/tables.py
  (matches existing table from migration 007_gateway_triggers_table)
- Add module-level gateway_pre_spawn_check() + helpers to orchestrator.py
  (gated: returns ("spawn", "gateway disabled") immediately when flag is False)
- Wire gateway check into _safe_spawn() — the single dispatcher choke-point
  for all agent spawns; QUEUE or DROP outcome logs and returns None (no spawn)
- ROBOCO_GATEWAY_ENABLED defaults to False; legacy behaviour is unchanged

* feat(agent_sdk): load tool-manifest.json at startup behind ROBOCO_GATEWAY_ENABLED flag (no agent-visible change yet)

Adds load_tool_manifest() to the SDK server that reads env at call-time
so gateway-enabled agents can obtain their pre-registered tool list at
startup; returns None when the flag is off, leaving the legacy briefing
path completely unchanged.

* fix(optimal_brain): skip indexing when source ID is None to eliminate roboco://journals/None spam

- Add `build_doc_source(kind, id_)` module-level helper in indexes/base.py that
  returns None when id_ is None instead of producing a "roboco://journals/None" URI
- Update abstract `build_source_uri` return type to `str | None` so subclasses
  can legitimately signal a missing ID
- Short-circuit `ingest()` and `_prepare_docs_for_batch()` in BaseIndexPlugin
  when `build_source_uri` returns None (debug log, no push to vector store)
- Fix JournalsIndexPlugin.build_source_uri: `kwargs.get("entry_id")` returns the
  kwarg value even when it is None, so fall back to doc_id before calling
  build_doc_source
- Fix ConversationsIndexPlugin.build_source_uri: return None when session_id is
  None rather than producing "roboco://conversations/None-unknown"
- Add 9 unit tests with a piragi-free conftest that stubs sys.modules

* fix(agent_sdk): inject X-Agent-ID header on notification-poller requests

Both `_check_pending_a2a` and `_auto_ack_a2a_notifications` in
`roboco/mcp/a2a_server.py` were calling the main API without identity
headers, causing orchestrator `Missing X-Agent-ID header` warnings on
`GET /api/v1/notifications/pending-a2a` and the ack-a2a POST.

Add module-level `AGENT_ROLE` constant (mirrors the existing `AGENT_ID`
pattern) and pass `{"X-Agent-ID": AGENT_ID, "X-Agent-Role": AGENT_ROLE}`
on both requests.

* fix(git): use ROBOCO_PUBLIC_BASE_URL for commit-trailer Links instead of hardcoded localhost

* fix(test_runner): call uv run pytest/ruff directly; add make to orchestrator Dockerfile as backstop

FileNotFoundError was propagating as a raw 500 when a project had `make test`
configured but make was not installed in the orchestrator container.

Two fixes:
1. Catch FileNotFoundError in _run_command and re-raise as ValidationError (400)
   with a clear message telling the operator to reconfigure the project command
   (e.g. replace 'make test' with 'uv run pytest').
2. Add `make` to the orchestrator Dockerfile runner-stage apt-get so projects
   that legitimately use make targets continue to work without reconfiguration.

* fix(api/git): resolve project by slug or UUID in git_log endpoint

Add _resolve_project_slug() helper to git routes that tries UUID
lookup first and falls back to slug, matching the pattern already
used in project routes. Apply to all four read-only git endpoints:
status, log, branches, diff.

* fix(a2a): auto-create conversation when conversation_id absent; reject empty IDs in URL builder

* fix(agent_sdk): default subagent model to parent agent's model from spawn manifest, not hardcoded haiku

Inject CLAUDE_CODE_SUBAGENT_MODEL env var into every agent container at
spawn time.  Claude Code ≥2.1.x reads this variable to override the
default Task (Agent) subagent model, which otherwise hard-codes
claude-haiku-4-5-20251001.  When the parent runs on a non-Anthropic
provider (e.g. Ollama Cloud / minimax-m2.7:cloud) that Anthropic model
is unreachable, so subagent dispatch fails.

The value follows the same provider-aware translation already used for
the --model CLI flag: Anthropic short names go through MODEL_MAP, and
non-Anthropic identifiers are passed verbatim.  To avoid calling the
class by name inside a @staticmethod, the shared translation logic is
extracted to the module-level _resolve_agent_cli_model() helper;
_resolve_cli_model() now delegates to it.

Verified: CLAUDE_CODE_SUBAGENT_MODEL is present and honoured in the
Claude Code 2.1.123 binary (grep confirmed the env-var lookup pattern
`if(process.env.CLAUDE_CODE_SUBAGENT_MODEL) return KK(…)`).

* chore(makefile): add quality and quality-fast targets composing every PR gate

* chore(quality): add import-linter dependency and gateway boundary contract

* test(property): scaffold tracing-completeness assertion (filled in Phase 4)

* Format test file to pass ruff check

* fix(gateway): drop Protocol scaffolding from choreographer skeleton; del-statements on unused stub args; clear vulture whitelist

* linting

* feat(gateway): Phase 1 dev cutover — ChoreographerDeps + give_me_work

Add ChoreographerDeps frozen dataclass (7 deps: task, work_session, git,
a2a, journal, audit, evidence_repo), refactor Choreographer.__init__ to
accept the bundle, implement give_me_work + _briefing_for via
evidence_builder.build_context_briefing, and add property accessors for
all deps. All Phase 2-4 stubs gain del-statements and still raise
NotImplementedError. 3 tests added and passing; mypy/ruff/vulture clean.

* feat(gateway): implement i_will_work_on handling pending, claimed, and needs_revision recovery

* feat(gateway): implement i_have_committed with plan-required precondition

Replaces the NotImplementedError stub with the real implementation: looks up
the agent's active task, enforces plan presence before recording, calls
task.add_progress, and returns a structured Envelope. Adds 3 unit tests
(records progress, no active task → invalid_state, no plan → tracing_gap).

* feat(gateway): implement i_am_done with smart catch-up and skill resolution

* feat(gateway): implement i_am_blocked (struggle + escalate) and i_am_idle (with unread soft-block)

* feat(gateway): add ContentActions for commit, note, say, dm, evidence with auto-inject and validation

* feat(api/v2): add /api/v2/flow/dev/* endpoints delegating to Choreographer

Six intent-verb endpoints (give_me_work, i_will_work_on, i_have_committed,
i_am_done, i_am_blocked, i_am_idle) under /api/v2/flow/dev/, each a thin
handler that delegates to Choreographer. Includes Pydantic request schemas,
EvidenceRepo Phase 1 stub (all methods return []), get_choreographer FastAPI
dep wired with all 7 service deps, and 8 unit tests (all passing).

* feat(api/v2): add /api/v2/do/* endpoints for commit, note, say, dm, evidence

* feat(mcp): add roboco-flow MCP server for intent verbs (Phase 1: dev verbs implemented)

* feat(mcp): add roboco-do MCP server for smart-wrapped content tools

* feat(runtime): mount per-agent tool-manifest.json on developer-container spawn; gateway flag enabled for devs only

* docs(prompts): rewrite developer role prompt for gateway-only verbs (~15 lines vs 49)

* chore(mcp): confirm dev manifest excludes legacy task/journal/notify/a2a tools (Phase 1 cutover; servers retired in Phase 4)

* feat(gateway): implement claim_review with inline evidence (kills #15) and qa_evidence_inspected tracking

* feat(gateway): implement pass_review with qa_notes/learning/evidence tracing gates

* feat(gateway): implement fail_review with issue list, tracing gates, and dev A2A handoff

* feat(api/v2): add /api/v2/flow/qa/* endpoints (claim_review, pass, fail, give_me_work, i_am_idle)

* feat(mcp): add QA verbs (claim_review, pass, fail) to roboco-flow MCP server

* docs(prompts): rewrite QA role prompt for gateway verbs; explicitly warn against grep-the-commit anti-pattern

* feat(runtime): enable gateway flag for QA-role spawns (Phase 2 cutover)

* feat(gateway): implement claim_doc_task and i_documented with file-list and notes-min-chars gates

* feat(gateway): implement triage (cell PM) and triage_all (main PM) with priority order

* feat(gateway): implement unblock with pre_block_state restoration (kills #23)

* feat(gateway): implement cell_pm_complete with auto-merge to parent branch (kills #22 for cell scope)

* feat(gateway): implement main_pm_complete (open master PR + escalate to CEO)

* feat(gateway): add complete() dispatcher routing to cell_pm_complete or main_pm_complete by role

* feat(gateway): implement escalate_up routing by role.escalation_target

* feat(api/v2): add /api/v2/flow/{documenter,cell_pm,main_pm}/* endpoints

* feat(mcp): add Doc + PM verbs to roboco-flow MCP server (claim_doc_task, i_documented, triage, triage_all, unblock, complete, escalate_up)

* docs(prompts): rewrite Doc, Cell PM, and Main PM role prompts for gateway verbs

* feat(runtime): enable gateway flag for Doc, Cell PM, and Main PM roles (Phase 3 cutover)

* test(integration): full pending->awaiting_ceo_approval test through dev/QA/doc/cell-PM/main-PM gateway path

* chore(tests): rename unused args to _args in flow_server tests

Cleared RUF059 lint blocker for Phase 3 closeout. The destructured args was only consumed in URL-asserting tests; one variant only checks kwargs["json"], so its args is now _args.

* chore: untrack docs/superpowers/ + add to .gitignore

Plans + spec were inadvertently swept into commits 5d41a4b and de0c5b5 by subagent 'git add -A' calls. Removed from index and gitignored going forward; files remain on disk for ongoing reference. They still exist in history of those two commits — invoke a follow-up filter-repo if a full purge is desired.

* feat(gateway): implement Board escalate_to_ceo with role allow-list

Allows main_pm, product_owner, and head_marketing to escalate tasks to
CEO. Enforces awaiting_pm_review state and journal:decision tracing gate.
Closes Phase 4 Task 1.

* feat(gateway): implement board_triage prioritizing strategic root tasks

Adds Choreographer.board_triage and TaskService.list_strategic_for_board.
PO and Head Marketing get curated lists of strategic-nature root tasks
in awaiting_pm_review. Closes Phase 4 Task 2.

* feat(gateway): implement auditor_triage surfacing long-running blocked-task anomalies

Adds Choreographer.auditor_triage and TaskService.list_long_running_blocked.
The Auditor surfaces tasks blocked >30min as anomalies for reflect-note
observation. Closes Phase 4 Task 3.

* chore(tests): add return + arg type annotations to gateway tests

All gateway test functions now have -> None and parameter annotations. Cleared 63 mypy [no-untyped-def] errors that pre-existed since Phase 1. Mypy now clean across tests/unit/gateway/.

* feat(api/v2): add /api/v2/flow/{board,auditor}/* endpoints

Board: triage, escalate_to_ceo, i_am_idle.
Auditor: triage, i_am_idle (read-only role).
Adds EscalateToCeoRequest schema with reason min_length validation.
Closes Phase 4 Task 4.

* feat(mcp): add Board + Auditor verbs to roboco-flow MCP server

Adds escalate_to_ceo MCP tool used by Board (PO + Head Marketing) and Main PM. Updates the implemented set in _validate_role_compatibility. Auditor uses the existing triage tool with role-routing in URL.

Closes Phase 4 Task 5.

* docs(prompts): rewrite Board (PO, Head-Marketing, Auditor) prompts for gateway verbs

All 3 board identity files + roles/board.md now use the slim, gateway-aware shape (no ToolSearch directive, no state-tool table). Auditor is explicit about its read-only scope. Closes Phase 4 Task 6.

* feat(runtime): enable gateway manifest for ALL roles (Phase 4 cutover)

Adds product_owner, head_marketing, auditor to GATEWAY_ENABLED_ROLES. Every spawned agent now gets a gateway manifest mounted at /app/tool-manifest.json. The legacy briefing path is dead. Closes Phase 4 Task 8.

* test(property): implement tracing-completeness assertion across smoke-test batch

Replaces Phase 0 stub. Asserts the 6 tracing-contract requirements on every
completed task: audit_log agent_id non-null per state-transition row,
DEVELOPER:TASK_REFLECTION journal entry, QA:LEARNING journal entry,
CELL_PM/MAIN_PM:DECISION_LOG journal entry, acceptance_criteria_status
covering every criterion with a referencing_artifact_id, and
qa_evidence_inspected = true.

Uses an in-memory ephemeral Postgres test DB (`roboco_test_<pid>_<rand>`)
provisioned per pytest session, not SQLite — the production schema relies
on Postgres-only types (UUID, ARRAY) the SQLite dialect cannot compile.
Tests requesting db_session/smoke_test_batch are auto-skipped when no
Postgres is reachable on localhost:5432; ROBOCO_TEST_DB_HOST/PORT/USER
override the endpoint.

Schema is built via Base.metadata.create_all + manual ALTER for the
acceptance_criteria_status / qa_evidence_inspected columns, NOT via
`alembic upgrade head`. This sidesteps two pre-existing layer-drift items
that block any fresh migration run today:

  1. Migration 001 declares the agentrole Postgres enum with lowercase
     values (qa, developer, ...) but the SQLAlchemy ORM binds
     Enum(AgentRole) to the StrEnum's uppercase NAMES — production DBs
     mask this by being bootstrapped via create_all and stamped at 001.
  2. Migration 008 runs UPDATE agents SET skills WHERE id over an
     agents.skills column that no migration in this chain ever creates.

Documented in conftest.py so a future migrations cleanup can find them.
Also notes that acceptance_criteria_status/qa_evidence_inspected are in
the DB schema (per migration 006) but are NOT mapped on the ORM TaskTable
nor on the Pydantic Task model — services that read them via
`task.qa_evidence_inspected` rely on those values being set on raw rows.
The property test uses raw SQL to read the columns directly, matching the
DB-level contract.

Closes Phase 4 Task 11.

Side change: pyproject.toml — adds asyncpg.* to the existing
[[tool.mypy.overrides]] ignore_missing_imports list (asyncpg ships no
py.typed marker), matching the convention used for redis, anthropic,
piragi, etc.

Test count: 1; backend: Postgres (localhost test DB).

* style(mcp/flow_server): single-line _post call after format pass

* fix(db): map 7 gateway columns from migration 006 to TaskTable + Task model

active_claimant_id, last_heartbeat_at, pre_block_state, pre_block_assignee, pre_block_metadata, acceptance_criteria_status, qa_evidence_inspected: present in DB since migration 006 but absent from the ORM mapping. Gateway code (tracing_gate, choreographer, claimant_lock) reads these via task.<attr>; without the mapping, runtime would AttributeError. Closes PHASE4-BUG-A.

* fix(db): repair alembic chain — neutralize 008, add 009 enum reconcile, ORM uses values_callable

Three coordinated changes that close PHASE4-BUG-B:

1. roboco/db/tables.py — introduce _str_enum() helper that wraps Enum() with values_callable=lambda obj: [m.value for m in obj]. Apply to all 23 StrEnum-typed mapped columns. ORM now serializes by .value (lowercase) to match alembic 001's declared enum values; default Enum() was using .name (uppercase) which never matched.

2. alembic/versions/008_align_skills.py — replace with documented no-op. The original migration referenced agents.skills, a column that has never existed in any migration (the agents table has capabilities, not skills). The substitution intent (qa_review -> code_review) was already satisfied statically in roboco/agents_config.py.

3. alembic/versions/009_enum_reconcile.py — new migration that:
   - Adds missing enum values: agentrole.system, team.fullstack, taskstatus.quarantined.
   - Detects uppercase drift from a Base.metadata.create_all bootstrap and rebuilds agentrole/team/taskstatus enums with lowercase members + USING lower(col::text)::enum on every column referenced. No-op if already lowercase.

Tests stay green: 281 passed.

* feat(services): backfill 36 gateway-shaped methods for Choreographer

The gateway Choreographer was wired to call methods that the underlying
services did not expose. This adds them as thin wrappers + queries (most
alias canonical methods; a handful are gateway-specific variants).

TaskService — 26 methods: aliases (submit_verification, submit_qa,
list_blocked_for_team, list_blocked_all_teams,
list_awaiting_pm_review_for_team, list_assigned_for_agent), agent
queries (agent_for, qa_agent_for_team, documenter_for_team,
cell_pm_for_team, get_active_task_for_agent, list_paused_for_agent),
triage queries (list_awaiting_main_pm_all, all_subtasks_terminal),
state setters (set_plan, mark_evidence_inspected, mark_agent_idle),
QA/Doc claim variants (qa_claim, doc_claim, qa_pass, qa_fail),
PM completion (cell_pm_complete with merge_commit), unblock with
state restore (unblock_with_restore), and escalation
(escalate, escalate_up_to_role). Also adds GatewayAgentView
dataclass that unifies DB and config-derived agent attributes.

JournalService — 4 methods: existence checks (has_decision_for_task,
has_learning_for_task, has_reflect_for_task) + write_struggle.

GitService — 4 methods: branch-keyed entry points (create_pr,
pr_merge, pr_target, diff) plus push_branch helper. Each derives
project + workspace from the task that owns the branch / PR.

WorkSessionService — 2 methods: files_changed + has_unpushed_commits.
PR existence is the proxy for pushed (no per-commit push column).

Choreographer: switched git.push(branch_name) call to push_branch()
to dispatch to the new gateway-shaped helper.

* test(services): unit tests for 36 gateway-backfill methods

Adds happy-path + edge tests for every method added in the prior
backfill commit. Total 61 new tests across:
  - tests/unit/services/test_task.py (36)
  - tests/unit/services/test_journal.py (8)
  - tests/unit/services/test_git.py (10)
  - tests/unit/services/test_work_session.py (7)

Each test mocks at the session boundary (no DB) and stubs adjacent
service methods via a dynamic _bind helper to avoid mypy
[method-assign] noise without resorting to type:ignore comments.

* test(gateway): switch dev catch-up assertion to push_branch

The Choreographer's catch-up sequence was renamed from git.push(branch)
to git.push_branch(branch) when GitService got a gateway-shaped helper
in the prior commit. This updates the existing assertion to match.

* feat(mcp): add roboco-git-readonly server with status/log/diff/branches

Slim FastMCP server exposing the four read-only git tools every role
needs (status, log, diff, branch_list) by forwarding to /api/v1/git/*
on the orchestrator. Replaces the read-only half of the legacy
roboco-git server; write operations now go through gateway verbs in
roboco-flow / roboco-do.

The endpoint shapes mirror the panel-facing API (project_slug,
include_remote, staged/file_path) so the same backend handlers serve
both human and agent traffic.

* refactor(mcp): delete legacy task/journal/notify/a2a/message/project servers

Phase 4 cutover: agents now reach every state-changing surface through
the gateway (roboco-flow intent verbs + roboco-do content tools), with
roboco-git-readonly + roboco-optimal + roboco-docs covering reads. The
seven legacy MCP servers + their handler trees are dead code from the
agent side, so they're removed:

  roboco/mcp/task_server.py            (1020 LOC)
  roboco/mcp/journal_server.py         (512 LOC)
  roboco/mcp/notify_server.py          (440 LOC)
  roboco/mcp/a2a_server.py             (790 LOC)
  roboco/mcp/message_server.py         (682 LOC)
  roboco/mcp/project_server.py         (667 LOC)
  roboco/mcp/tasks/  (handlers+utils)  (~4300 LOC)
  roboco/mcp/test/   (in-container runner; replaced by gateway evidence
                      + manual smoke)
  roboco/mcp/git/    (full server; read-only half migrates to the new
                      slim roboco-git-readonly module, write half is
                      owned by gateway verbs)

Orchestrator updates:
  - _generate_mcp_config registers only roboco-flow, roboco-do,
    roboco-git-readonly, roboco-optimal, and (for docs roles) roboco-docs.
    No more per-role legacy fan-out.
  - base_allow flips to mcp__roboco-flow__*, mcp__roboco-do__*,
    mcp__roboco-optimal__*, mcp__roboco-git-readonly__*. Role-specific
    allow lists are reduced to file IO scoping, since gateway verbs
    enforce role policy server-side.
  - TRACEABILITY_TRIGGER_TOOLS rewritten in terms of the gateway servers
    (mcp__roboco-flow__* / mcp__roboco-do__*) instead of the now-deleted
    per-tool list.

Test fix: tests/unit/services/test_a2a.py imported _handle_send_chat_message
from the deleted a2a_server. The four MCP-layer URL-builder tests (empty
conversation_id guard) are dropped — the equivalent boundary now lives
in /api/v2/do/* which has its own integration coverage. The two
service-layer nil-UUID guard tests are kept; they exercise A2AService
directly and remain meaningful (the panel still uses the v1 chat surface,
where a buggy caller could pass the nil UUID).

Net: ~9600 LOC removed from roboco/mcp/. quality-fast green:
338 tests pass, mypy clean, ruff clean. No /api/v1/* router changes —
those endpoints stay live for the panel UI which still uses every
lifecycle action; agents have no prompts that name them so the path is
dead code from the agent side.

* docs(claude.md): replace legacy MCP listing with gateway/verb-surface section

Phase 4 cutover: agents go through roboco-flow + roboco-do (gateway), not the deleted task/journal/notify/a2a/message/project servers. Document the verb surface per role + the Envelope response shape so future Claude Code sessions land in the correct mental model. Closes Phase 4 Task 13.

---------

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-05-02 03:11:49 +02:00