mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
c0a2a0c871d184c9c0e4d35407536c5c7b6404f4
164
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
666f261a1a | fix(kimi): spawn's own prepared instance no longer counts against the concurrency cap (#714) | ||
|
|
95e7d5df7c |
fix(kimi): cap concurrent Kimi agents to protect the shared auth chain
Every Kimi container redeems the same rotating refresh-token chain; Moonshot rotates with a short reuse grace, so two containers refreshing near-simultaneously fork the chain and a later stale redemption revokes the whole family - fleet-wide re-login (observed twice in production, each after paired spawns). With one consumer at a time refreshes are strictly sequential and the chain stays coherent, so the spawn gate now skips-and-retries Kimi spawns past ROBOCO_KIMI_MAX_CONCURRENT (default 1), sharing the provider-parked bail path. The compose files also gain the four Kimi tunables their environment blocks silently dropped - documented .env overrides never reached the orchestrator container. |
||
|
|
6374bbbed0 |
feat(kimi): Kimi K3 provider on the official kimi-code CLI (#713)
* feat(kimi): Kimi K3 provider on the official kimi-code CLI (Wave 1) ModelProvider.KIMI routes through KimiCliProvider driving Moonshot's kimi CLI on a Kimi subscription (OAuth device-code, no metered key). One-shot delivery roles only (V1), interactive ban wired in both guard lists. Auth: one shared RW auth mount; containers symlink credentials/ and oauth/ (the CLI's cross-process refresh-lock dir) into a container-local KIMI_CODE_HOME so every container and the host redeem the SAME rotating refresh chain - live-verified that per-copy chains cross-invalidate after the reuse-grace window. No orchestrator refresh daemon; an expires_at preflight exits 78. Config renderer mirrors the login-managed provider/model blocks field-for-field (live-captured; the model value is the CLI-side name, never the raw API id), plus per-role deny rules and the bash-guard as a PreToolUse hook via a wrapper script (an env key on a hooks entry makes the CLI silently drop ALL hooks - live-verified). Usage capture sums wire.jsonl usage.record 4-bucket events; sniff classifies rate-limit/auth from structured error text only, mapped to the shared 75/78 park contract. Image installs the CLI latest-at-build (no version pin, by policy) with the resolved version stamped as provenance, binary split to /usr/local away from mutable state. Migrations 090 (enum) + 091 (provider seed); catalog, pricing, routing mode, and orchestrator park/usage wiring mirror the codex integration. * feat(kimi): surface sweep + fleet-wide pin drop (Wave 2) Compose x3 gain the agent-kimi-image service and the orchestrator's read-write ~/.kimi-code mount + kimi-usage dir; .env.example documents the Kimi block. Panel mirrors ModelProvider.KIMI and adds the kimi routing mode (catalog filter, mode button, mix-picker group, badge) with tests; provider routes gain the kimi remediation entry. CLAUDE.md and docs/map document the runtime. Per the no-pins policy, agent-grok/ gemini/codex Dockerfiles drop their version pins for latest-at-build with resolved-version provenance stamps (grok resolves 0.2.112 vs the old 0.2.56 pin - verified by real builds of all four images). --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
42f8a5d18e |
fix(board): nothing_to_propose exit for Board Program explorers + PR checks on slave-based PRs (#712)
* fix(board): give Board Program explorers a nothing_to_propose exit Every propose_* verb requires at least one item, so an explorer that legitimately found nothing — Barfly with no worthwhile X conversations, Coroner with no autopsy subject — had no way to close its exploration task. It declined, called i_am_idle(), and the task stayed PENDING forever: the dispatcher re-matched it every tick and respawned the board agent (~$0.61 a spawn, ~3 per 5-minute respawn-breaker cooldown window, indefinitely), and BoardProgramEngine's one-open-cycle dedup wedged that whole program shut, since the ledger row only closes once its exploration task goes terminal. nothing_to_propose(task_id, reason) is the explicit exit. task_id is required rather than inferred: one explorer role owns several independently-cadenced programs (head_marketing owns six) and each assigns its exploration task to the same agent, so several are open at once by design and guessing "the caller's oldest" completes the WRONG cycle — stamping its reason onto an unrelated program's ledger while the task actually being worked stays wedged. Resolution validates the named task exists, carries a registered program source, is assigned to the caller, and is non-terminal, then gates on the program's declared explorer role from the registry, so a program registered later needs no edit here. The reason lands on board_program_cycles (migration 089) and renders into the next cycle's LEARN context, replacing a bare "proposed 0, approved 0" with why. That write runs in its own savepoint: it flushes on the same session as the completion, and a bare try/except around a same-session flush leaves the transaction pending-rollback, so a DB blip there would discard the completion at the post-response commit while the verb reported success. All fourteen exploration prompts offer the exit, pinned by a registry-parametrized test that fails when a future program is unwired. * ci: fire PR checks on slave-based PRs, not master alone All five gating workflows declared `pull_request: branches: [master]`, but every fleet PR targets slave — cell->root, root->slave, and the CEO's own. So `pull_request` never fired for any of them, and their only coverage was the `push` trigger, which is gated on branch PREFIX (feature/bug/chore/docs/hotfix). A branch named anything else got zero checks — not a red run, an absent one — and a PR with no required check present merges on a false green. PR #711 shipped that way on a `fix/` branch. Basing on the branch a PR merges INTO rather than what its head is named makes coverage independent of branch naming, so a non-conforming prefix can only ever cost the redundant push run, never the whole gate. The same five also omitted slave from `push` (ci.yml aside, which added it for the release gate's fail-closed CI read), so the panel suite, both CodeQL analyses, and the e2e smoke never ran on the trunk master is cut from. --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
66f0287d11 |
[3e7a5064] Eval harness: wire the real-spawn path so python -m roboco.eval run works end-to-end (#703)
* [431e73b7] Wire the real-spawn path: OrchestratorStageSpawner + disposable MCP config (#701)
* [431e73b7] Wire the eval harness real-spawn path: OrchestratorStageSpawner + disposable MCP config
_generate_mcp_config now prefers settings.api_url when set (both
PROJECT_HOST_PATH branches), so spawned MCP servers resolve to the
harness's disposable orchestrator URL instead of the real production
hostname or 127.0.0.1:port. OrchestratorStageSpawner.__init__ replaces
the NotImplementedError with a real AgentOrchestrator() constructed the
same way the production dispatcher builds it. The runner module
docstring + __main__.py docstring/run-subparser help drop the
NOT-YET-FUNCTIONAL wording. A new unit test pins the no-production-reach
guarantee: with settings.api_url patched, the MCP config's
ROBOCO_API_URL/ROBOCO_ORCHESTRATOR_URL point at the disposable URL (not
production), and the agent UUID is the real fixed UUID from
foundation.identity.AGENTS.
* [431e73b7] docs(eval): reflect the wired real-spawn path in tests map, CLAUDE.md, and CHANGELOG
---------
Co-authored-by: Backend Developer 1 <be-dev-1@roboco.tech>
Co-authored-by: Backend Documenter <be-doc@roboco.tech>
* [5cc75f71] Fix disposable orchestrator container-reachability + document real-UUID isolation design (#705)
* [5cc75f71] Fix disposable orchestrator container-reachability + document real-UUID isolation
* [5cc75f71] docs(eval-harness): document container-reachability fix + real-UUID isolation in map docs
---------
Co-authored-by: Backend Developer 1 <be-dev-1@roboco.tech>
Co-authored-by: Backend Documenter <be-doc@roboco.tech>
* [d96ec059] Rewrite stale eval-spawner pinning test to assert wired behavior (test-only, CI-green for PR #703) (#707) (#708)
* [d96ec059] test(eval): assert OrchestratorStageSpawner constructs a real AgentOrchestrator
Rewrite the stale pinning test that asserted the PRE-wiring
NotImplementedError (removed by commit
|
||
|
|
a36a180b73 |
fix(orchestrator): let a failed Board Program exploration retry (#706)
`_board_dispatched` is an in-memory, never-expiring set of (agent_slug, task_id). The solo exploration dispatchers consulted it, so an exploration whose propose verb rejected was never retried for the life of the process — Periscope, Sentinel, Scales and Barfly each spawned once on 2026-07-25, failed, and sat PENDING until the stack restarted. The guard was written for the two-reviewer board REVIEW pass, where it is correct: a reviewer has no verb to advance the task, so a respawn can only loop. An explorer is the opposite — `propose_*` is exactly such a verb, so a respawn can and should advance it. Drop it from the 15 `_dispatch_*_exploration` functions. Bounding falls to `_pm_respawn_should_gate`, which is what actually bounds a loop: DB-persisted, reset by a status change, and cooled down so a deploy that fixes the cause lets the work resume. `_dispatch_board_reviewer` keeps the set (its `_board_review_complete` reads it as a has-run signal) as does vault curation (same-process race guard behind its own durable marker). The 14 `*_dispatch_is_one_shot` tests asserted the old contract on the false rationale that board roles have no progression verb; they now assert a second tick re-attempts. Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
401f8a2cc9 |
feat(board): Board Programs — the complete twelve-program catalog (Phases 1-3) (#699)
* feat(board): Pest Control — the first project-scoped Board Program The Product Owner hunts latent defects (what the org records but nobody reads): a weekly cycle — accelerated off-schedule when the trailing-7-day rework rate crosses pest_rework_threshold, with the cheap dedup/scope gates evaluated before the metrics queries — opens one held exploration task against the least-recently-explored opted-in project (deterministic round-robin; opted_in_projects gains a stable ORDER BY), with server- assembled evidence in the spawn prompt (rework hotspots, recurring-findings and waived-minor ledger aggregates, all capped) plus prior-cycle LEARN context. The PO calls the new PO-only propose_bug_hunt verb once: ≤5 items, evidence required per item, targets validated against pest_control participation. CEO decides per item — approve materializes a BACKLOG task (source pest_control, never auto-starts), reject records the reason; both feed the LEARN ledger by exploration task id; all-terminal completes the cycle. Telegram queue pushes carry working Approve/Reject handlers mirroring the roadmap kind. Doctrine: board.md Pest Control section + product-owner verb entry + regenerated verb tables. * feat(panel): Pest Control review queue Command Center gains the pest review queue (per-item approve/reject with reason, mirroring the roadmap queue); the Programs card and the project settings participates-in checkboxes pick the new program up registry-driven — the settings section renders for the first time now that a project-scoped program exists. * feat(board): Periscope — HoM market-research brief program Weekly org-scoped cycle: a solo HoM spawn researches the market (web research with mandatory source URLs — uncited findings are rejected) and files one structured brief via the new HoM-only propose_market_brief verb: headline, cited findings, threats/opportunities, positioning note, all soup-checked and screened through the injection guard at persist time (web-derived text later reaches prompts; flags recorded, content never dropped). A brief is a report, not a proposal: the verb completes the exploration in the same call (the x_feature asymmetry), the cycle ledger auto-closes, and the CEO gets a best-effort notification with no approve/reject surface (periscope deliberately never joins Telegram's action kinds). The latest brief is injected into the roadmap exploration prompt — Periscope feeds Printer, the first cross-role program input. * feat(panel): Market Briefs tab (read-only) Business page gains a Market Briefs tab listing Periscope briefs — headline, cited findings, threats/opportunities — read-only by design; a report has nothing to approve. * feat(board): Coroner — event-triggered Auditor postmortems The first EVENT program: no cron — three best-effort hooks open an autopsy when a task bounces to its 3rd revision (the audit chokepoint), is cancelled after work started, or is budget-blocked; all gated on arming + one-open-autopsy dedup, none can fail the underlying transition. A solo Auditor spawn reads the incident (server-assembled findings + transition context) and files one propose_postmortem: incident summary, root cause, failed stage (validated against the real status vocabulary), and ONE process change — a playbook-kind change drafts via PlaybookService directly into the normal pending-curation queue; the briefed draft_playbook manifest grant was deliberately NOT added, preserving the existing 'auditor curates but never drafts' invariant test. Complete-at-propose (report asymmetry), cycle ledger auto-closes, CEO notified link-only. Integrated as a union with Periscope across the shared program surfaces. * feat(panel): Coroner postmortems card Read-only postmortems list under Business → Programs — incident, root cause, failed stage, process change; nothing to approve, the process-change artifact (a draft playbook) rides the existing curation queue. * feat(board): Sentinel — Auditor drift-watch quality reports Weekly org-scoped cycle: a solo Auditor spawn receives a server-assembled drift context (waived-findings trend, open findings by severity, conventions-violation hotspots, top spend — all capped, pure ORM) and files one propose_quality_report: headline, 1-7 area-validated items with evidence and suggested actions, overall assessment. Report semantics — complete-at-propose, cycle auto-closes, CEO notified display-only (never on Telegram's approve/reject surface); items are structured so a later convert-to-task control is cheap. Integration adopts Sentinel's module- level dict-dispatch for board-program routing (xenon-driven), folding all prior programs in; app router mounting extracted to a helper for the same budget. * feat(panel): Quality Reports tab (read-only) Business page gains the Sentinel quality-reports tab — headline, per-area observations with evidence and suggested actions; read-only, a report has nothing to approve. * feat(board): Spackle — gap-fill audit program Biweekly project-scoped PO cycle over the half-shipped surface area: API routes without panel surfaces (and vice versa), armed flags without docs, docs promises the code doesn't keep, dead-end tabs — the inventory diffing is the PO's own read-tool work, ordered by the spawn prompt with file:line citations required; the server injects only prior-cycle LEARN and the rotation target. Rotation is now a shared module-level helper (pick_rotation_target, parameterized by source) both project-scoped engines use — pest_control delegates to it, behavior-identical, with a cross-pollution test proving the two programs' rotations stay independent. propose_gap_fill mirrors the bug-hunt verb (≤5 items, two-sided evidence required, participation gate); per-item CEO decide materializes BACKLOG source=spackle tasks; full Telegram kind incl. approve/reject handlers. All seven program routers now mount from one helper. * feat(panel): Spackle gap-fill review queue Command Center gains the gap-fill queue mirroring the pest-control one — per-item approve/reject with the two-sided gap evidence rendered. * feat(board): Scales — monthly portfolio rebalance Org-scoped PO cycle over the stale backlog: the spawn receives a capped stale-task snapshot (BACKLOG/PENDING unclaimed >30 days) plus the charter and prior-cycle LEARN, and files one propose_rebalance — 1-7 items, each a resolvable task_ref with action reprioritize (validated new priority) or cancel, rationale required. Per-item CEO decide: approve EXECUTES the action (audited priority update, or the normal cancel path) — the first program whose materializer mutates existing tasks instead of creating them; reject records the reason; LEARN by exploration task id; all-terminal completes the cycle. Full Telegram decide-kind wiring. Integrated as the eight-program union (registry, dict dispatch, routers helper, teardown enumerations). * feat(panel): Scales rebalance review queue Command Center gains the rebalance queue — per-item approve/reject with the action, target task, and rationale rendered. * feat(board): Mirror — quarterly positioning audit Project-scoped HoM cycle over messaging surfaces: README claims vs shipped reality, docs-site promises vs code, charter alignment — the audit is the HoM's own read-tool work with citations required; the server injects the charter, prior-cycle LEARN, and the shared rotation target. propose_ messaging_fixes mirrors the gap-fill verb (≤5 items, drift evidence naming claim + contradicting reality, participation gate); per-item CEO decide materializes BACKLOG source=mirror documentation tasks; full Telegram decide-kind wiring. Nine-program union across the shared surfaces. * feat(panel): Mirror messaging-fixes review queue * feat(board): Megaphone — HoM standing editorial calendar Cron cycle (3 days, org-scoped, gated on X credentials — drafting content nobody can post is pointless): the HoM receives a shipped-this-week digest plus Unreleased changelog bullets and files one propose_editorial_post (angle-validated, ≤280, brand voice) that materializes a held x_editorial draft through the SAME X-queue origination chokepoint release posts use — zero new approval surface, notifications and CEO decide for free. Complete-at-propose; cycle auto-closes. Ten-program union. * feat(panel): x_editorial source labels in the X queue surfaces * feat(board): Librarian — proactive playbook mining Biweekly org-scoped Auditor cycle: mines recurring non-private learning journals (≥2-count grouping with a recency fallback) against the existing playbook-title inventory and files one propose_playbook_drafts — 1-3 drafts, each with the repeated-pattern evidence that justifies it, duplicate titles rejected in-batch and against the live store. Drafts are created via PlaybookService directly (the Coroner precedent — the 'auditor curates but never drafts' do-verb invariant stays intact and tested) and land in the normal pending-curation queue the Auditor's own triage already surfaces; no new panel surface. Complete-at-propose; display-only CEO notification. Eleven-program union. * feat(board): War Room — release campaign planning EVENT program with a REAL originator (unlike coroner's stub): a release publish hooks a campaign brief beside the release-post seam, and the CEO's run-now originates on demand — the cron loop never fires it. The HoM designs a 2-6 post arc (teaser → launch → follow-up → spotlight; 280-cap, future strictly-ascending publish_after, stage vocabulary) and one propose_campaign call materializes each post as a held x_campaign draft through the X-queue chokepoint. V1 is manual-cadence by design: publish_ after renders as queue guidance and the CEO approves each post at its moment — nothing auto-posts, ever; the auto-schedule upgrade is a documented ceiling. Twelve-program union: full registry complete. * feat(panel): x_campaign labels + publish-after guidance in the X queue * feat(board): Barfly — adjacent-conversation replies Cron cycle (2 days, org-scoped, X-credentials gated): the engine searches X for conversations where RoboCo is relevant but unmentioned (new OAuth- signed search_recent on the client; queries + candidate cap configurable), screens every fetched tweet through the injection guard (stored unclamped — a clamp was truncating the candidate under the envelope, caught by the dev's own tests), dedupes via the existing x_seen_mentions ledger (no migration; also prevents double-drafting against the mentions poll), and opens one held HoM exploration carrying the screened candidates. propose_ conversation_replies enforces candidate-id-only replies (≤5, 280-cap); each materializes a held x_barfly draft through the X-queue chokepoint, threaded via a new in_reply_to seam on post_tweet that only x_barfly drafts use. The X redraft machinery is now dict-dispatch over per-source extractors with reply-ref carry for x_barfly. Thirteen-program registry. War Room's test fakes gained the new abstract search_recent stub. * feat(board): Dogfood — the PO walks the product The fourteenth and final registry entry, completing the catalog. EVENT program (release-publish hook beside the war-room hook + CEO run-now, both through the same real originator; the cron loop never fires it), project- scoped with shared rotation. The permission surface is the careful part: the PO's dogfood spawn — and ONLY that spawn — gets the Playwright MCP mounted, via a task-scoped fail-closed probe mirroring the video-authoring precedent (a PO spawned for roadmap/pest/scales never sees browser tools; tested both ways); the PM agent image bakes chromium unconditionally like the ux image, the mount stays task-gated in code. The walk targets the rotation target's live surfaces (panel_base_url only when the target is the org's own project, honest degradation otherwise); propose_friction_ fixes files ≤5 walked-path-evidenced items; per-item CEO decide materializes BACKLOG source=dogfood tasks; full Telegram decide kind. Also: megaphone/librarian/war_room arming keys restored to the settings validator — their panel toggles would have been rejected (dropped in earlier unions; the same silent-arming class the drill killed once already). * feat(panel): Dogfood friction review queue * chore(board): final whole-branch sweep fixes The night's closing adversarial pass over the integrated fourteen-program registry found ONE functional defect — the war-room test fakes' post_tweet predated Barfly's in_reply_to_tweet_id kwarg (LSP violation, the only red in an otherwise fully green gate) — plus doc/test drift, all fixed: the source-parity test completes to fourteen (spackle/mirror were silently absent while its neighboring comment claimed full coverage), the PO identity doc gains its missing Dogfood verb, the auditor quick-list gains propose_postmortem, three stale comments corrected (rotation docstring, panel registry header, X source enumerations), the dogfood release-hook gains the exception-swallow test its four sibling hooks already had, and the CHANGELOG's Unreleased section documents the whole Board Programs train. Full make quality: exit 0, all gates green. * docs: full documentation sweep for the Board Programs train CLAUDE.md's roadmap-engine entry superseded by the Board Program registry entry (all fourteen programs, arming, scoping, LEARN, guardrails) with the role verb tables and playwright row refreshed; docs/rag gains the agent- facing architecture doc plus full propose_* call-shape sections in the three board role docs, and corrects the strategy-engine section to shipped reality (only idle→roadmap is wired); docs/map covers the registry + all twelve engines with flags, gotchas, and drift notes. The 0.27.0 reference inventory confirmed only the release-executor's canonical set carries the version — left for the 0.28.0 cut. * feat(board): human titles + descriptions on every program surface Raw registry keys rendered as bare panel labels — an operator reading x_feature had no idea what enabling or running it does. The registry dataclass gains title/description (test-enforced non-empty for every entry, unique titles), the API passes them through, and every surface renders title-with-description-tooltip instead of the key: the Programs card (label, toggle hint, run-now toast), and the project settings participates-in/excluded-from checkboxes. --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
e77c3b7a63 |
feat(board): Board Program registry — Phase 1 (engine, LEARN ledger, per-project scoping, panel) (#689)
* feat(board): Board Program registry — generic trigger/dedup/originate/LEARN engine
One registry (foundation/policy/board_programs.py) + one BoardProgramEngine +
one orchestrator loop replace the bespoke roadmap/spotlight loops, behavior-
preserved: same sources, dispatch routing, one-open-cycle dedup (ledger rows
auto-close when their exploration task goes terminal, so x_feature's
complete-at-propose flow can't wedge), and live per-program interval
overrides with the tick capped at 1h.
program_armed() is the single arming chokepoint: the settings-store
board_program.<key>.enabled override when present, else the legacy flag —
routed through BoardProgramEngine, RoadmapEngine.run_cycle, and XEngine's
spotlight gate, so the panel toggle can never be a silent no-op against a
legacy boot flag.
LEARN: board_program_cycles (migration 087) accrues per-item CEO decisions
(exact attribution by exploration_task_id where the caller holds it) and
feeds the last closed cycles back into both exploration prompts. The
strategy engine's idle signal now opens a roadmap cycle (enabled+dedup
respected) instead of only nudging.
Per-project scoping (migration 088, projects.board_programs, dual polarity):
plain keys opt a project INTO project-scoped programs; "!key" opts it OUT
of an org-scoped program's outputs (default eligible — parity). Enforced at
propose_roadmap (names the excluded project) and defensively at materialize;
validation rejects unknown keys and meaningless polarity both directions.
API: GET /api/board-programs + POST /api/board-programs/{key}/run-now
(CEO-gated); settings keys for both migrated programs.
* feat(panel): Board Programs card + per-project program controls
Business page gains a Programs tab: per-program rows (role, trigger, scope,
open-cycle badge), enabled switch on the settings-store key, Run now
(disabled while a cycle is open). The edit-project dialog gains the
program controls next to the CI-watch/video toggles: participates-in
checkboxes for project-scoped programs, excluded-from checkboxes for
org-scoped outputs.
* test(board): full-gate hermeticity — mypy casts + shared-DB purge fixtures
make quality runs one pytest process over all suites against the shared
persistent DB: integration collects before unit, so the board-programs API
test's committed run-now state (settings-store overrides, an open cycle row,
its board_roadmap task) poisoned 13 downstream unit tests that pass in
isolation. The polluter now purges its own committed state in fixture
teardown, and the four consumer files get an autouse per-test purge
(board_program.% settings keys, ledger rows, open exploration tasks) so
they are hermetic regardless of collection order. Also the four
cast("UUID", ...) sites the tests-scope mypy run requires.
* feat(panel): re-home per-project program controls onto the settings page
Wave C deleted the edit-project dialog these controls originally landed in;
they now live on the project settings page's budget/ops card next to the
CI-watch/video toggles — participates-in switches for project-scoped
programs, excluded-from switches for org-scoped outputs, dual-polarity
tooltips, order-independent dirty tracking. Nine makeProject test fixtures
gain the required board_programs field the rebase left behind.
---------
Co-authored-by: Renn F <rennf93@users.noreply.github.com>
|
||
|
|
902fc9a78c |
chore(models): finish the opus-5 replacement — no stale opus-4 fixtures remain
Every fixture, comment example, and panel mock that presented an Opus 4.x id as current now carries claude-opus-5 (or the current sonnet/haiku ids in the panel usage mocks). The only deliberate claude-opus-4 survivors are the pricing-table fragment and its tests — they price the historical usage rows, which would otherwise re-read as $0. |
||
|
|
3c87596685 |
chore(models): move the opus alias to claude-opus-5
Claude Opus 5 released today — same $5/$25 sticker, 1M context; Opus 4.8 moves to legacy. The pricing table gains a dedicated claude-opus-5 fragment (the claude-opus-4 substring doesn't cover it, so without the row the fleet's Opus usage would silently cost-track as $0 — exactly what test_opus_is_priced now guards). |
||
|
|
803638e8e9 |
chore(models): upgrade the opus alias to claude-opus-4-8
MODEL_MAP["opus"] moves off claude-opus-4-6 to the newest Opus tier at the same price; pricing already matched via the claude-opus-4 fragment, and a new test_opus_is_priced guard keeps the alias priced. Fixtures, panel mocks, and docs follow. |
||
|
|
987eb09c78 |
fix(workspace): role-aware worktree refresh at every spawn (#692)
* fix(workspace): refresh a present per-task worktree at every respawn ensure_worktree_self_heal treated an already-present worktree as a pure no-op (venv-link + chown only), so a worktree created once at first claim or first claim_review stayed frozen at that commit across every later respawn even as new commits landed on origin — the root mechanism behind a live multi-round QA/PR-gate bounce loop, where the reviewer kept re-examining its own stale round-1 checkout. _ensure_worktree_before_spawn now classifies the caller's role (WORKTREE_AUTHOR_ROLES: developer/documenter, mirroring the gateway commit tool's RBAC) and _refresh_present_worktree compares local HEAD against origin/<branch>: behind-or-equal fast-forwards for every role (never discarding an author's uncommitted edits to do it); strictly ahead is always left alone; diverged only resets for a pure reader, whose local history can never be anything but a stale prior-round checkout. conventions_check_for_task's list-vs-content gap (list from git objects, content from the physical worktree) is closed as a side effect: the reviewer's worktree is now current as of spawn, and the branch under review gains no further commits while it sits in awaiting_pr_review. * fix(workspace): refresh re-added worktrees; fail the dirty guard toward preservation - A pruned worktree re-added from a surviving local ref now runs the same fetch-and-classify refresh as a present one, so an evicted reviewer worktree cannot resurrect a stale checkout. - A failing git status reads as dirty, never clean: the guard that protects an author's uncommitted edits fails toward preservation. - The hard reset verifies the worktree is actually on the task branch first; a detached or drifted worktree is left alone with a warning. - The conventions-check docstring states the remaining second-claim ceiling instead of claiming full closure. --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
eb0dcb6ecb |
fix(orchestrator): task-scoped oscillation breaker for escalate/unblock ping-pong (#685)
* fix(orchestrator): task-scoped oscillation breaker for escalate/unblock ping-pong An escalation ping-pong oscillates a task between two agents (cell PM escalate_up -> BLOCKED -> main PM unblock -> restored -> respawn -> escalate again). The per-(agent, task) respawn gate never trips on it: the restored side is dispatched by _dispatch_claimed_without_agent, which consults no respawn counter at all, so one side of the round trip always has fuel regardless of the other's strikes — and even a tripped main-PM counter only stalls the task silently at blocked instead of surfacing the oscillation. - Strikes are counted task-scoped at the unblock() chokepoint (agent-agnostic; legitimate needs_revision rework never calls unblock, so it structurally cannot trip this), durable in the existing orchestration_markers column — no migration. - Progress between round-trips (commits / revision_count advancing) resets the count: real forward motion is not an oscillation. - On trip: the task is blocked with a HUMAN resolver (the budget-breach posture), both dispatchers stop respawning onto it, further unblock() refuses until an admin override clears the marker, and the CEO notification names both agents and the cycle count. - _notification_has_live_work now treats a HITL-blocked related task as no live work, closing the same loop for the admin-route escalation path. * fix(orchestrator): wire the oscillation trip to the dispatchers and make recovery reachable - TaskResponse serializes blocker_resolver_type: the dispatchers' HITL-blocked skip and the notification-path live-work check now actually fire over the wire instead of only against in-process rows. - The oscillation marker clears on every human transition out of BLOCKED (snapshot or not), and the human unblock route treats a tripped task as the requested intervention: clears the marker and proceeds, while the agent gateway verb keeps refusing. - The progress fingerprint includes the terminal-children count, so a coordination root whose children advanced between escalations resets instead of accruing toward a false trip. --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
f8b4a6755c |
fix(pr-review): fleet PRs are ours by branch ownership, not author identity (#668)
With a GitHub App bound, fleet PRs are authored by <app-slug>[bot] whose author_association is NONE — the inbound classifier's author heuristics read that as an outsider and ingested the org's own dev-stream PR as external_pr for adversarial review (2026-07-23 live: PR #667). The repo-owner author check only ever covered the PAT era. _ingest_pr_if_reviewable now skips any same-repo PR whose head branch an active task owns BEFORE the author-based classification, and active_task_owns_branch widens from the single polled project to every project sharing its git_url (the poll collapses a monorepo's cell-projects to one canonical project, so a sibling cell's ownership must count — the same sibling scope external_review_task_exists already uses, now shared via _repo_sibling_project_ids). A deleted-fork head (GitHub sends head.repo=null) now classifies as fork, failing closed to review instead of risking a silent ownership skip on a branch-name collision. Residual, documented: an org PR whose task went terminal with the PR left open falls through to the author heuristics. Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
a036c97985 |
fix(tg): cockpit data correctness — real GLM pricing, display timezone, agent activity tracking (#666)
* fix(tg): cockpit data correctness — real GLM pricing, display timezone, agent activity tracking
Three root causes behind the Mini App/bot showing wrong numbers:
Pricing: glm-5.2 gets a grounded per-token rate (z.ai published pricing,
$1.40/$4.40/$0.26 per 1M, source+date in the table comment) so a GLM
fleet day stops reporting $0.00 for half a million tokens; ungrounded
Ollama-Cloud models render "subscription (untracked)" instead of a bare
zero (is_ollama_cloud_model, consumed directly by the cockpit). Side
effect, intended and documented: honestly-priced GLM now trips the
downgrade-only comparator for new qa/documenter complexity pins.
Display timezone: the cockpit bucketed days in UTC for a GMT+2 operator.
New pure foundation module display_time (resolve_zone/local_date/
trailing_dates/day_bounds_utc, DST-correct with tests for the 23h/25h
days) + ROBOCO_DISPLAY_TIMEZONE (IANA-validated, default UTC); the
cockpit's spend/velocity series bucket raw session/completion rows by
the display zone. The UTC-keyed rollup table and the main dashboard are
deliberately untouched.
Agent activity: AgentTable.status was never set to ACTIVE and
current_task_id was never written anywhere — "active: 0, working: []"
was structurally permanent. Every claim path now marks the claimant
ACTIVE with rollback symmetry (_finalize_claim for dev/PM claims,
_qa_or_doc_claim for QA/doc/PR-gate claims, pr_review_claim for external
review) and every release path clears it (pass/fail QA, pr_pass/pr_fail,
complete_review, advance-to-PM-review, reaper unclaim, voluntary
unclaim, reassign retarget, pool divert, admin transitions, unblock
restore-to-in-progress). The bot's /status shares the cockpit's fleet
derivation so the two surfaces can't disagree. Known ceiling, commented:
one current_task_id column shows a multi-root coordinator PM's most
recent claim only.
Drill: sonnet develop -> sonnet adversarial (refuted the original
chokepoint coverage claim; QA/doc/reviewer paths were unwired) ->
correction round (wired them all + restored a dropped assertion, deleted
a dead helper and the dead subscription_billed field) -> review.
* fix(db): post_update on AgentTable.current_task breaks the flush cycle
agents.current_task_id and tasks.assigned_to reference each other, so a
flush touching both rows — every claim now marks its agent ACTIVE — is
an instance-level circular dependency SQLAlchemy cannot topologically
sort. The e2e smoke's full verb paths (12 tests) hit it; the unit and
integration suites never flush both dirty rows with relationships
loaded. post_update emits the FK as a second UPDATE, the canonical fix
for mutually-referencing rows.
* fix(budgets): enforce only explicitly-set budgets — no per-TaskType defaults
The per-TaskType default cap table blocked an unbudgeted coordination
root one opus planning turn in ($1.50 PLANNING default vs. real
coordination spend) — a false positive by design the moment the fleet
runs a priced model. Budgets are now explicit-input only:
effective_task_budget_usd returns None for an unset budget_usd, the
budget sweep skips enforcement (and never prices spend) on None, and
the unblock re-check passes on None so clearing the budget field is
itself a valid resolution. The project monthly cap stays as the
explicit-input fleet-wide backstop. Panel copy tells the truth
("No cap" placeholder; empty = uncapped), and the TaskType default
table plus its resolver are deleted.
---------
Co-authored-by: Renn F <rennf93@users.noreply.github.com>
|
||
|
|
d4b7e1e7b8 | fix: post-finale completeness sweep — routing surface, provider config, budgets, compose env, interactive exemption (#661) | ||
|
|
21d6730400 |
feat(providers): Gemini CLI provider — ModelProvider.GEMINI (#660)
* feat(providers): Gemini CLI provider — ModelProvider.GEMINI Mirrors the grok blueprint with source-verified divergences (all facts pinned against google-gemini/gemini-cli @ 9681621c): no refresher daemon — Google's refresh tokens are reusable, so the RO host mount is COPIED to a writable container-local ~/.gemini and each container refreshes in-process independently (the write-back crash risk on RO never triggers); settings.json renders security.auth.selectedType 'oauth-personal', experimental.enableAgents=false (subagent ban), autoConfigureMemory=false with a bounded heap; tool scoping rides the tiered TOML Policy Engine (deny-only rules that yolo mode structurally cannot beat); gemini -p with --output-format stream-json; usage parsed from the run's own stdout stats — the adversarial pass caught the parser reading the json-mode nested shape while the entrypoint runs stream-json's FLAT shape (every real run would have priced $0 forever, hidden by fixtures sharing the assumption) — now flat-primary with the nested shape as cited fallback; rate-limit classified from structured error.type only (model-echo immune), native exit 41 auth passthrough; per-model pricing for the three GA models; migrations 084 (enum) + 085 (seed) complete the 082-085 finale chain. V1 excludes interactive intake/secretary. Stack-merge required two behavior-preserving complexity refactors in the shared park/usage plumbing (a park-pair loop; a usage-reader dispatch dict). * fix(providers): route gemini usage read through the containment barrier Mirrors the codex/grok fix — _gemini_usage_json now delegates to _read_usage_json_contained, so CodeQL's path-injection alert on the gemini read is resolved by the same resolve-and-contain guard. --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
c70ff3cf9a |
feat(providers): Codex CLI provider — OpenAI via ModelProvider.OPENAI (#659)
* feat(providers): Codex CLI provider — OpenAI via ModelProvider.OPENAI Mirrors the grok blueprint end to end: CodexCliProvider (RO ~/.codex mount, ANTHROPIC_* blanked), an orchestrator-side codex_auth.py refresher (JWT-exp staleness, atomic rewrite, lock-serialized single-use rotation, --check backstop; the CLI's own in-process refresh write no-ops on the RO mount by design — margins keep the orchestrator ahead of the CLI's 5-minute window), config.toml rendering with required=true gateway MCP servers, execpolicy deny rules (forbidden-only), per-role --sandbox (developer=workspace-write, review/doc roles read-only), codex exec --json with pinned ROBOCO_CODEX_CLI_MODEL (gpt-5.3-codex), usage summed from typed turn.completed events priced via the real 4-bucket split, dedicated image + entrypoint, registry/park/finalize/ compose/release wiring. V1 excludes interactive intake/secretary. Per adversarial review: migration 083 seeds the openai provider row enabled=True (without it every routing path 404'd — the whole feature was operationally dead code; grok needed the same seed in 039), the panel picker gained the OpenAI catalog group it silently lacked, and exit classification is structural — only stderr and error.message fields from error events are sniffed (word-boundaried patterns, exact auth phrases, bare 'login' dropped), so the model echoing on-topic words can never false-park the provider fleet-wide, proven by a benign-transcript test. Known open risk flagged, not claimed: whether codex's workspace-write OS sandbox excludes /app is unverified, and no hook mechanism exists to port the bash-guard defense-in-depth. * fix(providers): containment barrier on usage.json reads (code scanning) CodeQL flagged the codex usage read as path injection — correctly: os.path.basename does not neutralize '..', and the upstream segment validator isn't in CodeQL's taint model. The grok/codex reads collapse into one _read_usage_json_contained helper that resolves the built path and refuses anything outside the resolved usage root — a hostile id can never escape regardless of upstream drift. Traversal + containment regression tests added; a stray noqa in the test file replaced with a named constant per repo rule. * fix(providers): use realpath+startswith containment CodeQL recognizes The is_relative_to() guard was a real barrier but not in CodeQL's py/path-injection sanitizer model, so the alert persisted. Switch to the canonical os.path.realpath + startswith(root + os.sep) form, which CodeQL recognizes as a path-traversal barrier; behavior is identical (refuse any candidate resolving outside the usage root). * fix(providers): regexp-allowlist the usage-id segment (CodeQL barrier) Neither is_relative_to nor realpath+startswith was recognized by CodeQL's py/path-injection sanitizer model across the str->Path->open flow. Sanitize the tainted component at the source instead: the id must fullmatch a strict slug token ([A-Za-z0-9][A-Za-z0-9._-]*, no separators, no '..'), which CodeQL recognizes as a path-injection barrier; the realpath+startswith containment stays as defense-in-depth. * fix(providers): standalone regexp guard so CodeQL recognizes the barrier The sanitizer was one disjunct of a compound 'or' condition, which CodeQL's guard analysis does not trace as a barrier. Split the regexp fullmatch into its own single-condition guard (the redundant '..' check is dropped — the required alphanumeric first char already excludes it). --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
165892dc62 |
feat(routing): cost-tiered complexity routing + saved presets (#656)
The 08-31 lever: model_assignments gains one compound rung —
AGENT_SLUG > ROLE('{role}:{complexity}') > ROLE > GLOBAL — so a
low-complexity task can route to a cheaper tier while coordinators stay
pinned. Structurally opt-in: zero rows means byte-identical routing
(pinned by a named test across every precedence case), the cost_tiered
apply-mode (seeds developer:low→haiku) is reachable only from the
explicit PM-gated endpoint — verified no startup path can apply it.
Overrides are downgrade-only (input-price comparator), allowlisted to
{developer, qa, documenter} — cell_pm excluded per the org's own
coordinator definition and its documented weak-model incidents — and
validated at write time (disabled/unconfigured provider rejected with
remediation; cross-provider-family overrides warn explicitly).
Per adversarial review: the four mode-switch applies now spare compound
rows exactly like agent pins (the 2026-07-17 unscoped-wipe class, new
victim, same fix extended via one shared wipe helper) with panel cache
invalidation + truthful confirm dialogs; preset apply validates the
entire payload BEFORE the wipe (validate-all-first), with a savepoint
crash test proving rollback.
Presets (CEO request): routing_presets table (migration 082) snapshots
the full mix — mode, per-agent overrides, complexity rows — with
save/apply/delete endpoints and a panel preset bar; applying skips
since-removed models with per-entry notes, never silently.
Task complexity threads task_id through _resolve_agent_route at both
call sites; taskless spawns unchanged. 235 backend + 23 panel tests.
Co-authored-by: Renn F <rennf93@users.noreply.github.com>
|
||
|
|
7c8453e210 |
feat(budgets): per-task and per-project cost budgets (flag-gated) (#654)
* fix(notifications): exponential backoff + CAS claim for expired-unacked re-escalation The sweep re-escalated every expired unacked ack-required notification on every ~60s tick, forever — the live incident: 3 fresh blocker escalations + Telegram DMs per minute from a static stale pile. Now each notification carries reescalation_count / last_reescalated_at / reescalation_delivered_count (migration 079): first fire at expiry, then doubling intervals from 1h capped at 24h, hard stop after ROBOCO_NOTIFICATION_MAX_REESCALATIONS (default 5) with one permanent log carrying attempts-vs-delivered so 'seen and ignored' is distinguishable from 'route never worked'. The due/wait/capped decision is a pure function in foundation/policy/communications.py. Per adversarial review, the attempt slot is claimed by compare-and-set (UPDATE ... WHERE reescalation_count = :n) BEFORE delivery — the previous draft leaned on the 60s dedup window, which never engages for BLOCKER_ESCALATION (_LOOP_PRONE_TYPES excludes it), so concurrent sweeps would have double-delivered. A lost claim skips delivery outright. Legacy rows read as count=0 and keep today's first-fire semantics. 61 tests incl. a two-session CAS race and a real alembic upgrade/downgrade round trip. * feat(budgets): per-task and per-project cost budgets (flag-gated) tasks.budget_usd + projects.monthly_budget_usd (migration 080, chained on 079; adds ix_agent_spawn_sessions_task_id since both enforcement seams filter on bare task_id). Behind ROBOCO_TASK_BUDGETS_ENABLED (default off, feature-flags card) — verifiably inert when off. Claim-time: a project-month-spend guard applies to WORK-STARTING claims only (i_will_work_on / i_will_plan) — per adversarial review, review/ doc/gate/inbound-PR claims are exempt so in-flight work can always finish reviewing and merging at cap. Spend counts closed sessions' estimated_cost_usd PLUS open sessions priced live from token snapshots (the original closed-only sum read parallel long sessions as $0). Sweep-side: the existing budget sweep also prices the active task's spend vs budget_usd (TaskType defaults when null); on breach the task is BLOCKED (HUMAN resolver, budget marker) BEFORE the graceful stop so the unclaim no-ops and the dispatcher never respawns onto it, and the CEO notification names both recovery steps. unblock on a budget-blocked task re-checks live spend and refuses while still over — no silent re-breach loop. Panel: budget inputs in both dialogs (0 rejected — a zero budget silently blocks everything), spend logic consolidated in TaskService.task_spend_usd. 42 new tests incl. a real-DB spend-query suite and a two-tick non-refire sweep test. --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
b91229f487 |
fix(orchestrator): break the notification-driven respawn loop (#643)
The escalation/approval dispatchers spawn a notification's recipient every cooldown window for as long as it stays pending. These spawns carry no task_id, so the PM respawn breaker never sees them — a single wedged alert/escalation whose recipient never resolves it respawns that recipient forever. Observed live: fe-pm's unacked alerts kept main-pm/fe-pm spawning every ~2-3 min for 6+ hours. Two guards, both gating the spawn after the existing cooldown: - A hard per-(agent, notification) attempt cap (notification_spawn_max_attempts, default 5): once a notification has respawned its target that many times without being acknowledged, stop and log once. The count is id-scoped and survives map pruning (re-stamp), so a fresh escalation is unaffected. - A live-work check before spawning: skip when the notification has expired, is stale past notification_spawn_max_age_seconds (default 6h — wedged or reloaded from before a restart), or its related task is already terminal. Fail-open — a failed task fetch or unparseable field never suppresses a real escalation. Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
17de29545a |
[6788ce7f] Silent bug sweep: concurrency, state integrity, engine edge-cases, panel data freshness (#638)
* [943d8c4d] Frontend data freshness and approval-queue reliability audit (#631)
* [233a8b0f] WebSocket reconnect message-loss audit and fix (#625)
* [233a8b0f] fix(panel): add REST catch-up to useNotificationStream on WS reconnect
connection.ts has no message buffering/replay, so a notification published
while the CEO bell's socket was down (disconnected/reconnecting) was lost
forever instead of merely delayed. Add a reconnect-triggered GET
/notifications?unread_only=true catch-up folded into the existing
notification_id dedup so a notification delivered both via catch-up and
live WS is never double-counted, and make clearMessages drop the held
catch-up batch too. use-a2a-live.ts and use-rate-limit-websocket.ts were
audited and already have working reconnect-triggered REST fallbacks
(verified via a2a/page.tsx, rate-limit-banner.tsx, usage-overview-panel.tsx
and their existing F083 tests) so no fix was needed there.
* [233a8b0f] docs(panel): add comprehensive WebSocket hooks reference and reconnect architecture guide
Add panel/docs/frontend/hooks.md with full API reference for useWebSocket, useNotificationStream (with new REST catch-up behavior), useAgentStream, useA2ALiveStream, and useConnectionStatus. Include examples, best practices, and testing guidance.
Add panel/docs/architecture/websocket-reconnect.md documenting the message-loss mitigation pattern: Strategy 1 (REST catch-up for events, used by useNotificationStream) and Strategy 2 (REST invalidation for state, used by A2A/rate-limit consumers), plus the dedup logic ensuring no notification is double-counted on reconnect.
---------
Co-authored-by: Frontend Developer 1 <fe-dev-1@roboco.tech>
Co-authored-by: Frontend Documenter <fe-doc@roboco.tech>
* [d5315683] fix(frontend): add distinct toast feedback for silently-swallowed x-post and release-proposal statuses, plus regression tests for all 4 approval queues (#626)
Co-authored-by: Frontend Developer 2 <fe-dev-2@roboco.tech>
* [cd953838] Data-hook null-guard audit and API client 429 retry-by-method fix (#630)
* [cd953838] fix(panel): gate 429 retry by HTTP method, add hook null-guard regression tests
* [cd953838] chore(conventions): waive test-fixture wrapper in hooks null-guard test
* [cd953838] docs(frontend): document API rate-limit retry behavior and null-guard audit results
Added `docs/frontend/api-rate-limiting.md` to document the 429 retry strategy: GET/PUT auto-retry, POST/PATCH/DELETE require X-Idempotency-Key header. Updated `docs/frontend/hooks.md` to confirm the data-hook null-guard audit found all hooks already have correct `enabled` guards and include a regression test suite for the board-review poll on/off behavior and enabled-guard assertions.
---------
Co-authored-by: Frontend Developer 1 <fe-dev-1@roboco.tech>
Co-authored-by: Frontend Documenter <fe-doc@roboco.tech>
---------
Co-authored-by: Frontend Developer 1 <fe-dev-1@roboco.tech>
Co-authored-by: Frontend Documenter <fe-doc@roboco.tech>
Co-authored-by: Frontend Developer 2 <fe-dev-2@roboco.tech>
* [4534c71a] Backend concurrency, state-machine, and engine audit (#634)
* [41de844a] fix(lifecycle): sync CLAIM_RULES with runtime + clear stale claimant on PM hand-off (#627)
Two confirmed state-machine gaps found while auditing lifecycle.py,
task_lifecycle.py, the _ESCALATABLE_TO_BLOCKED bypass, and every
_REVIEW_QUEUE_STATES entry point:
- lifecycle.py's CLAIM_RULES/claim-ActionSpec/StatusTransition table
did not grant CELL_PM/MAIN_PM re-claim of AWAITING_PM_REVIEW even
though task.py's runtime _ROLE_CLAIM_STATUSES already granted it
and claimed the spec agreed -- the two tables had silently drifted,
breaking i_will_plan re-claim on an awaiting_pm_review task.
- docs_complete's _maybe_advance_to_pm_review pre-assigns a specific
owning PM via assigned_to but left claimed_by/active_claimant_id
pointing at the outgoing documenter, unlike every sibling transition
into a review-queue state. A stale active_claimant_id makes
content_actions.py's _active_claim_violation wrongly reject the
newly-assigned PM's own content writes before it formally claims.
Reassign claimed_by + active_claimant_id to the owning PM alongside
assigned_to.
Adds a regression test asserting the documenter's stale claim does not
survive the docs_complete -> awaiting_pm_review hand-off.
Co-authored-by: Backend Developer 2 <be-dev-2@roboco.tech>
* [0c46f666] Engine dedup race + sequencing.py edge-case audit (#628)
* [0c46f666] fix(sequencing): dedup race audit + collision-edge fallback bug
Audited the list-open-then-originate dedup pattern across six engines:
RoadmapEngine, XEngine.run_cycle, DepUpdateEngine, and CIWatchEngine each
run inside exactly one sequential orchestrator-loop asyncio task (no other
call site invokes run_cycle), so they cannot race with themselves; their
in-cycle dedup sets/keys are correctly built before any commit. SelfHealEngine
is the same shape. VideoEngine.open_video_task is genuinely different: it is
reachable from the release-publish hook, the feature-spotlight hook, and the
on-demand POST /video/request route, so two overlapping calls for the same
occasion can both pass the "no open task yet" check before either commits.
Fixed by wrapping the check+insert in a short-lived Redis mutex (reusing
HeartbeatMutex) keyed by occasion, mirroring XPostService's existing
lock pattern, with a regression test proving only one of two concurrent
calls creates a task.
Verified ReleaseExecutor's half-landed retry path (release_commit_sha):
apply_version_bumps and write_changelog_entry both run as uncommitted
working-tree edits before commit_and_push's single `git add -A` + commit,
so a bumped-version-without-changelog state can never reach origin (and
therefore can never be observed by a fresh retry clone) - confirmed correct
with a real-git-repo regression test, no fix needed.
Fixed sequencing.py's dev_task_collision_edges: the `if edges: return edges`
short-circuit dropped the same-assignee-lane fallback entirely whenever ANY
surfaced sibling pair produced a collision edge, even for a completely
unrelated same-assignee pair with no declared surface. Now the fallback
always runs, skipping only pairs the analyzer already ordered (so the two
mechanisms can never disagree on direction for the same pair).
Verified sequencing.py rule 3 (all-shared batch generates no edges): correct
by inspection (_shared_last_edges skips every pair when both are shared) and
confirmed with a regression test - no fix needed.
* [0c46f666] docs(reference): concurrency audit summary - engine races, fixes, verified patterns
---------
Co-authored-by: Backend Developer 2 <be-dev-2@roboco.tech>
Co-authored-by: Backend Documenter <be-doc@roboco.tech>
* [8f7f167a] Redis mutex pre-lock write audit (#629)
* [8f7f167a] Redis mutex pre-lock write audit: add cross-session regression test for XPostService.approve
Audited x_post_service.py, video_post_service.py, release_proposal.py, and
heartbeat_mutex.py for the pre-lock DB-write anti-pattern (a session write
that happens before the SET NX / HeartbeatMutex acquire returns a token,
letting a losing racer's stale write clobber a winner's committed state).
XPostService.approve, VideoPostService.approve, and
ReleaseProposalService.approve/reject already implement the correct
validate-pure-pre-lock, apply-under-lock pattern (the XPostService fix
already shipped per CHANGELOG.md: "X edited_body write deferred into the
single-flight lock (M5)"). HeartbeatMutex holds no AsyncSession at all, so
the anti-pattern is structurally inapplicable there.
Adds a genuine cross-session concurrency regression test to
test_x_post_service.py (a real second DB connection, not an in-process
mock) mirroring VideoPostService's existing cross-session test, proving a
concurrently-committed post survives and the CEO's edited body never lands
on the just-posted row.
* [8f7f167a] Remove redundant inline comments flagged by QA in cross-session regression test
Both comments restated what the surrounding docstrings already say
explicitly, per QA findings F-dbadd8f0 (line 294) and F-27ac051e (line
631) — no behavior change, tests re-verified green against a sandbox
Postgres.
* [8f7f167a] Remove inline trailing comments flagged by QA (correct file this time)
QA findings F-e6f3e6a6 and F-24189858 cited tests/unit/services/
test_x_post_service.py:294 and :631 across 5 revision rounds, but that
file never contained the flagged comment text — a repo-wide grep for
the exact quoted strings shows both comments actually live in the
mirrored tests/unit/services/test_video_post_service.py file, in its
own cross-session concurrency regression tests (the caption-edit and
tiktok-skip tests). Removed both there:
- "# externally visible to the "concurrent" session below" on the
db_session.commit() call
- "# never attempted without credentials" on the tiktok_poster.calls
assertion
Both restated what the surrounding docstrings/test names already say;
no behavior change. Verified with the full make quality gate against a
sandbox Postgres/Redis: 13,717 passed, 94.41% coverage, clean except
one pre-existing unrelated failure in tests/unit/api/test_cloud_auth.py
::test_login_route_parses_oauth2_form_not_query_params, which connects
to the app's default localhost:5432 Postgres (not the db_session
sandbox fixture) and is unreachable in this sandboxed environment —
structurally unrelated to the auth subsystem this task never touches.
* [8f7f167a] Redis mutex pre-lock write audit (round 7): add cross-session regression tests for reject() lock protection
Round-7 QA findings F-7eb9fbcb, F-06f39a2e, and F-4d56e49b claim
XPostService.reject(), ReleaseProposalService.reject(), and
release_executor._await_proc() lack lock protection / a CancelledError
handler — but their cited line ranges (255-267, 429-454, 241-257)
describe a pre-fix, shorter version of these functions that predates
commit
|
||
|
|
c8f55be904 |
fix(notifications): task titles and agent slugs replace raw UUIDs (#616)
* fix(notifications): task titles and agent slugs replace raw UUIDs
Notification producers interpolated raw task/agent UUIDs into subjects and
bodies ('Task 68e1e4db-... unblocked', 'handed back to 00000000-...-0004').
A tiny notification_text helper (task_display: title-first with a #id8
fallback; agent_display: identity-map slug first, DB lookup fallback) now
feeds every producer: all 13 NotificationService methods, the 7
delivery-service bodies whose subjects were already title-based, the
substitute-PM ad-hoc insert, and the orchestrator/choreographer callers,
which thread the task row's title one call deeper. Fixes the literal
'cell_pm' role string sent as an agent slug in the merge-conflict
notification. Tool-call examples like unblock('<uuid>') keep the raw id on
purpose — agents need it.
* test(notifications): board-review subject assertion matches the humanized format
---------
Co-authored-by: Renn F <rennf93@users.noreply.github.com>
|
||
|
|
5b27a443e9 |
chore(agnosticism): close the audit residue — B6/B8/B10 + three MAJORs (#587)
Thread the deployer's product name through the X reply + feature-spotlight prompts (B6 leftover; release/video paths shipped in #570); make the docs-site repo/URL config (ROBOCO_DOCS_SITE_*, defaults unchanged) instead of a roboco-website hardcode (B8); de-assert our repo from the Main PM prompt (B10); derive PR labels from the real target branch instead of literal to-master/to-slave; drop the stale headcount from base.md; and make the bash-guard's Makefile check require an actual quality/gate/lint/ test target before denying raw package-manager commands (no more false-remediation loop on Go/Rust Makefiles). Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
0f1e60fb95 |
fix(gateway): no model self-attribution in agent commits
Two layers: generated agent settings now set includeCoAuthoredBy: false (never set anywhere before, so the CLI nudged models into appending 'Co-Authored-By: Claude ...' to commit messages), and the commit verb strips AI-attribution lines deterministically at the chokepoint every provider routes through. |
||
|
|
796ab05d3d | test(runtime): drain fixture uses the real engine-slot initializer, not a stale copy | ||
|
|
9fbec78126 |
feat: Telegram V2 — inbound commands + actionable approve/reject from chat (#551)
* feat(telegram): V2 inbound — command router, actionable approve/reject keyboards, chat-gated poll loop * fix(release,x,video,telegram): terminal-state guards on approve/reject; sender-identity check * docs(map,rag): Telegram V2 inbound surfaces and terminal-state approve/reject guards --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
fd621f0dba |
feat(motion,runtime): wire the three craft capabilities to the video-authoring dev (#544)
Playwright, the taste-skill design bar, and hyperframes were all implemented — and each stopped one hop short of the hands doing video work: playwright reached only fe-qa/ux-qa, the design bar's web-UI dials actively pointed a video task at 'dense product UI -> motion 2-3', and hyperframes' agent-facing doctrine never reached any agent. Three wires: - vendor the official HyperFrames agent skills (hyperframes-core, -keyframes, -creative) under motion/skills/ at a pinned upstream commit (Apache-2.0, attribution headers; prose reflowed to house style, re-vendor note in each header); README and the dev video prompt block point at them - register the playwright MCP for a ux-dev spawned onto a source=video task (_is_video_authoring_spawn: fail-closed role/team/task-source probe) so the composition author can watch their HTML live in a real browser between renders — gating-only, agent-ux already bakes the browser; QA gating unchanged, be-qa/ordinary ux-dev still excluded - design bar video-mode override in the ux_ui team prompt: video tasks are films, the web dials do not apply — use the cinematography bar and the vendored doctrine instead Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
aa15dc40cc |
Feature/video artifact verification (#537)
* fix(release): CI wait polls the prod rung; escape the header tooltip apostrophe get_latest_ci_conclusion defaults to the ladder's head rung, so wait_for_ci searched slave for a release commit that lives on master and timed out after 40 minutes with the run already green. The wait now passes the prod branch explicitly. Also fixes the react/no-unescaped-entities error that turned master's Panel CI red. * fix(panel,video): dead dialog triggers behind tooltips; dotted composition ids render HelpTip nested inside a Dialog/AlertDialog trigger puts the trigger's click handler on the Tooltip root, which renders no DOM — the agents Spawn item and the KB Reindex-All / Delete-index confirms were dead. Tooltips now wrap the triggers. The video renderer accepts interior single dots in composition ids (release-0.25.0) with '..' still unrepresentable, and propose_video refuses an unrenderable id at authoring time. * fix(dispatch): restart-safe PM review turns A leaf task in awaiting_pm_review had no periodic pickup: the closure dispatcher bailed on childless tasks and skipped PR-bearing review tasks as already-promoted, assuming the submit-time PM session was still alive — an assumption every restart breaks. Proven live on the docs-sync leaf after the 0.25.0 redeploy, which also dependency-blocked its sibling dev task. Childless awaiting_pm_review tasks now flow to the PM's review turn, and the merge turn respawns its PM when none is active. * feat(video): verify the rendered artifact, not the source The 14s release-0.25.0 cut shipped with only one of four scenes visibly registering: the dev authored DOM, the smoke asserted DOM, QA read code — nobody consumed the rendered MP4 before the CEO did. Close that loop, and the reject loop behind it: - sidecar frames mode: POST /render with frames=1..32 renders the cut, ffprobes the REAL duration, extracts midpoint-sampled keyframe PNGs (timestamps in filenames), streams a tar.gz back with X-Video-Duration - request_render do-verb (developer/QA, request_sandbox's shape): renders the caller's ACTUAL composition — dev's own worktree (head_sha/dirty provenance), QA a read-only git-archive export of the assembled branch — extracts frames to the container-shared .previews/ path, stamps the render_preview marker, returns the paths as envelope evidence - gate: i_am_done on a source=video task refuses without a stamped render_preview (Requirement.RENDER_VERIFIED; canonical source string moved to foundation as markers.VIDEO_TASK_SOURCE; mirrored in the possibilities-matrix fast path so it cannot bypass the check) - QA claim_review evidence carries video_context (composition id, the dev's preview, a re-render instruction) so review checks output - dev spawn prompt block + a 4th authoring AC order Read-every-frame verification before submitting - reject -> re-author: a CEO reject with a reason opens a fresh authoring task carrying the verbatim feedback + a revise-in-place pointer at the existing composition (best-effort, never fails the reject) — rejection feedback no longer dies on the cancelled draft E2E: rendered the committed release-0.25.0 composition through the new frames mode locally — the returned keyframes show exactly the reported failure (blank frame at 5.8s, only 'Env ladder' by 12.8s), the check the fleet was missing. --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
797847e379 |
[1dae04a7] Video: release 0.25.0 (revision) (#536)
* fix(release): CI wait polls the prod rung; escape the header tooltip apostrophe get_latest_ci_conclusion defaults to the ladder's head rung, so wait_for_ci searched slave for a release commit that lives on master and timed out after 40 minutes with the run already green. The wait now passes the prod branch explicitly. Also fixes the react/no-unescaped-entities error that turned master's Panel CI red. * fix(panel,video): dead dialog triggers behind tooltips; dotted composition ids render HelpTip nested inside a Dialog/AlertDialog trigger puts the trigger's click handler on the Tooltip root, which renders no DOM — the agents Spawn item and the KB Reindex-All / Delete-index confirms were dead. Tooltips now wrap the triggers. The video renderer accepts interior single dots in composition ids (release-0.25.0) with '..' still unrepresentable, and propose_video refuses an unrenderable id at authoring time. * fix(dispatch): restart-safe PM review turns A leaf task in awaiting_pm_review had no periodic pickup: the closure dispatcher bailed on childless tasks and skipped PR-bearing review tasks as already-promoted, assuming the submit-time PM session was still alive — an assumption every restart breaks. Proven live on the docs-sync leaf after the 0.25.0 redeploy, which also dependency-blocked its sibling dev task. Childless awaiting_pm_review tasks now flow to the PM's review turn, and the merge turn respawns its PM when none is active. * [1dae04a7] Revise release-0.25.0 composition to 40s scene-based pacing with four feature cards * [1dae04a7] docs(motion): update release-0.25.0 README section for 40s four-card revision --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com> Co-authored-by: UX/UI Developer 1 <ux-dev-1@roboco.tech> Co-authored-by: UX/UI Documenter <ux-doc@roboco.tech> |
||
|
|
85ac6422ff |
fix(gateway): possibilities-matrix fast-path hardening + collision-context guards
The W7 fast path now rejects empty/trivial notes (its sole compensating control for the skipped journal gates), pushes the branch before the behind-base check, and pairs the local-gate fallback with the toolchain guard; the WORK_ALREADY_DONE prompt no longer promises a fast path to verifying tasks the gate routes elsewhere. build_collision_context now degrades gracefully at all three call sites instead of breaking the gate review, PM briefing, or collision-map route. |
||
|
|
d80dfb8bbe |
feat(env-branches): per-project ordered environment ladder (replaces default_branch) (#534)
* [env-bran] EnvSyncEngine: orchestrator-side prod→dev cascade (default-off) - EnvSyncEngine mirrors CiWatchEngine: cascade ladder_pairs top-down via GitHub merges API; clean→auto-push lower rung, conflict→one sync PR + tracked MAIN_PM task + stop. Never pushes prod (lower rung is never prod by construction). - GitService.sync_env_branch (merges API) + open_sync_pr (idempotent) + _env_merge_status/_post_sync_pr helpers (constants for 201/204/409). - TaskService.ENV_SYNC_SOURCE + list_open_env_sync_tasks (per-repo dedup). - config env_sync_enabled/_interval_seconds(1800)/_max_open_tasks(3)/_max_per_cycle(1). - Orchestrator 4-touch registration + _load_env_sync_set (ladder+token opt-in). - Feature-flags card + settings FEATURE_FLAGS entry for ROBOCO_ENV_SYNC_ENABLED. * [env-bran] Panel: environment ladder editor + types + validation - EnvironmentRung type + environments on Project/ProjectCreate/ProjectUpdate. - EnvironmentLadderEditor (plain useState, add/remove/up-down reorder, head/ prod labels) reused by create + edit project dialogs. - validateLadder (non-empty name+branch, no duplicate branches) shared, toast.error on submit; empty editor => null => inherits default_branch shim. - default_branch input kept with override-hint; API client passthrough. - 6 unit tests for validateLadder. * [env-bran] Tests + gate green: env ladder, EnvSyncEngine, promotion chain - tests/unit/models/test_env_branches.py: shim, head/prod, ladder_pairs, promotion_chain, normalize (20 tests) - tests/integration/services/test_env_sync_engine.py: cascade clean/conflict/ missing_ref/tokenless/degenerate/caps/dedup/disabled (9 tests, DB) - tests/integration/test_migration_env_branches.py: 073 defaults null + round-trip - tests/unit/services/test_release_executor*.py: add env_chain=[] to _ReleaseContext constructions (promotion_chain field is now required) - tests/unit/runtime/test_orchestrator_shutdown_drain.py: register _env_sync_task in the stop()-drain fixture (new named background loop) - roboco/services/git.py: revert _project_head_branch rename back to _project_default_branch (modify-in-place per plan); the rename in the consumers commit broke ~15 unit-test mocks that bind the original name - roboco/services/env_sync_engine.py + models/env_branches.py: ruff format - roboco/api/schemas/project.py: trailing-newline format Backend gate green (13013 passed / 439 skipped), mypy clean, ruff clean. Panel gate green (typecheck/lint/522 tests). * [env-bran] fix: add env_chain to _ReleaseContext in e2e smoke (CI red) The release-executor promotion_chain change made _ReleaseContext.env_chain required. I fixed the three unit/release test files but missed the construction in tests/e2e_smoke/test_background_engines.py:98 — my local gate ran 'mypy roboco/' (excludes tests/) and I skipped 'make e2e-smoke', so CI's mypy-on-tests + the e2e runtime job caught it instead of me. Verified locally with the CI-equivalent gates: uv run mypy roboco/ tests/ -> 1170 files, clean ROBOCO_E2E_SMOKE=1 uv run pytest tests/e2e_smoke -> 50 passed, 1 skipped * [env-bran] fix: extract _ensure_prod_fetched to clear xenon rank C (CI red) _production_assess grew past xenon --max-absolute B (rank C) when the env-branches prod-tip fetch added an if/try/except branch. Extracted the fetch-with-fallback into _ensure_prod_fetched (degan+fetch paths), moved _run_git to the module-level import. Local make quality green (all gates incl xenon/vulture/deptry/import-linter/foundation-check). --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
f2e5676198 |
W7: Possibilities matrix (work-already-done fast path) (#522)
* [W7] Add possibilities_matrix_enabled feature flag (default off) * [W7] Add _work_appears_done predicate (status+commits+PR+ACs+no-open-findings) * [W7] Add CI-green quality proxy for the fast path (local fallback on no-CI) * [W7] Add work-already-done fast path in i_am_done (slimmed gates, no rich plan) * [W7] Add WORK_ALREADY_DONE prompt state * [W7] Make fast path mypy-clean (cast to helpers for _resolve_ci_status; typed mock locals) * [W7] Extract _all_criteria_addressed to bring _work_appears_done under xenon B --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
ba9c9d69d8 |
fix(dispatch): prefilter sequence-held dev tasks before spawn (#519)
* fix(dispatch): prefilter sequence-held dev tasks before spawn _spawn_pending_dev booted a full dev container for a pre-assigned pending task that the assignee-blind sequence guard would refuse at the claim chokepoint (a non-terminal lower-sequence same-parent sibling — not a declared dependency). _blocked_by_earlier_lane_sibling is narrower (same dev's lane) and _validate_task_for_spawn checks declared deps, not sequence siblings, so the container spawned, the first claim hit _claim_blocked_by_sequence and was refused, and the agent exited only to be re-spawned next tick — pure churn until the predecessor went terminal. Mirror the PM path's _pending_claim_blocked prefilter (the exact claim-gate predicate, fails open) at the top of _spawn_pending_dev, before the narrower per-dev lane probe. Reuses the helper so it can't drift from the chokepoint. * fix(dispatch): prefilter sequence-held dev tasks before spawn _spawn_pending_dev booted a full dev container for a pre-assigned pending task that the assignee-blind sequence guard would refuse at the claim chokepoint (a non-terminal lower-sequence same-parent sibling — not a declared dependency). _blocked_by_earlier_lane_sibling is narrower (same dev's lane) and _validate_task_for_spawn checks declared deps, not sequence siblings, so the container spawned, the first claim hit _claim_blocked_by_sequence and was refused, and the agent exited only to be re-spawned next tick — pure churn until the predecessor went terminal. Mirror the PM path's _pending_claim_blocked prefilter (the exact claim-gate predicate, fails open) at the top of _spawn_pending_dev, before the narrower per-dev lane probe. Reuses the helper so it can't drift from the chokepoint. --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
a9dee3b34e |
feat(agents): force agents to the Makefile — deny raw uv/pip/conda/poetry (CEO #15) (#518)
* fix(prompts): point agents at Makefile, drop raw uv run instructions backend.md:23-26 literally instructed raw uv run ruff/mypy/pytest (copied from the human-facing CLAUDE.md), so agents bypassed the Makefile's UV_NO_SYNC=1 + private UV_CACHE_DIR venv-corruption guard. Replace with make targets across backend/developer/qa/cell_pm + a universal rule in base.md. Regenerate verbs.md from the updated regen script (baked instruction now make foundation-check) and align the Makefile drift message. Ships with the bash-guard deny in the next commit so agents don't loop fighting the guard. * feat(bash-guard): deny raw uv/pip/conda/poetry, point at Makefile When a Makefile is present, deny raw uv run/uv pip/uv lock/add/remove, pip/pip3 install/uninstall, conda install/create/run, poetry run/install/add and remediate to make quality/gate/lint/test. Skipped when no Makefile (Makefile-less projects not blocked). ROBOCO_GUARD_SKIP_PM=1 (grok path) nudges exit 0 instead of the run-canceling exit 2. Overrides the prior bare-uv-run-allowed stance by CEO direction; the /app-targeted blocks above keep priority. * feat(grok): deny raw uv/pip/conda/poetry via native --deny + PM-skip nudge Add _RAW_PM_DENY (uv run/pip install/lock/add/remove, pip/pip3 install, conda install/create/run, poetry run/install/add) to _deny_rules so grok's graceful native --deny blocks raw package-manager commands (model adapts to make, run continues — unlike a hook deny which cancels the run). The bash-guard hook keeps the compound-command fallback (cd x && uv run) and nudges exit 0 there via ROBOCO_GUARD_SKIP_PM=1 in the grok hook env, never canceling. * test(bash-guard): align existing tests with W1 Makefile-gate policy Raw uv run / pip install are now Makefile-gated (W1, CEO item #15), so two existing bash-guard invariants reverse: - test_allows_pytest_even_if_suite_uses_requests keeps its HTTP-injection allow-path intent but uses bare `python -m pytest` (raw `uv run` is now denied); the deny case is covered by test_bash_guard_makefile_guardrail. - test_allows_pip_install_in_workspace -> test_denies_pip_install_when_makefile_ present: a workspace clone carries a Makefile, so bare pip install is now denied -> agents use `make` / `uv sync --extra dev`. Makefile-less skips stay covered. Gate: 12994 passed, 439 skipped, 94.81% cov (DB env :55432 user renzof); the lone flaky integration error passes in isolation (DB-state race, not W1). --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
1c63c88cbf |
test(audit): guard await_args against None for mypy union-attr
CI mypy (which checks tests/, unlike the targeted source-only run that missed it) flagged ack_mock.await_args.args[1] — await_args is _Call | None. Assert it is not None first, matching the spawn_call pattern above. |
||
|
|
6fe0067f73 |
fix(orchestrator): stop auditor alert-spawn rotation — ack as auditor on dispatch
The auditor respawned every ~3 min on the same stale rework alerts. Root cause: _dispatch_audit_work's alert path fetched the SYSTEM-wide "not fully acked" view (list_system_notifications), but the auditor is read-only (no ack verb) and auditor_triage never acks — so once an alert existed the CEO was the only party who could clear it, and the CEO hadn't acked. The per-alert cooldown (PR #499) only paced a rotation through the N un-acked alerts; it was a damper, not a fix. Fix: fetch the auditor's OWN pending-ack view (GET /notifications authed as the auditor -> list_for_agent, which filters acked_by for the auditor) and ack the alert as the auditor on dispatch. Each alert is now a one-shot, DB-persistent: the next tick cannot respawn on an alert the auditor already observed — even one the CEO hasn't acked. Authed as the auditor (not the system identity) so the route selects the per-recipient view; HTTP rather than DB-direct so it shares the orchestrator's loop in prod and stays loop-safe in the e2e harness (which runs _dispatch_audit_work in its own asyncio.run loop, away from the app's DB engine). e2e now asserts the alert is in acked_by for the auditor after dispatch — the rotation-stopper itself, not just the spawn. |
||
|
|
1f769f6315 |
[sandbox-ext] fix: drop dynamic verify SQL (bandit B608) — static query + Python membership
CI bandit -ll flagged B608 at sandbox.py:264 (f-string ANY(ARRAY[...]) with interpolated feature names). Root-cause fix: verify_step now runs a static 'SELECT extname FROM pg_extension' and verify_ok checks set membership against the installed extnames — no interpolation, no string- built-SQL surface, and a more correct check (membership vs count). The enable_step CREATE EXTENSION stays (identifiers can't be parameterized; allowlist-validated upstream, the containment). Tests updated from the count-based exec_out to the extname-list exec_out. |
||
|
|
e7d7311636 |
[sandbox-ext] Phase 3: parameter surface — schema + project field + verb override + cache-by-features
Migration 072 adds projects.sandbox_extensions (jsonb null): a per-service
extension/module map a venture declares up front (e.g. {"postgres":
["vector","postgis"],"redis":["search"]}). Additive + nullable so
existing opted-in projects stay byte-for-byte bare — no default set, opters
set the extensions they need explicitly (TimescaleDB out unless asked).
Project model validates the map against SANDBOX_ENGINE_FEATURES: unknown
service keys and unallowed features are rejected at the model boundary with
the allowlist named (plpython3u — superuser-RCE — excluded by construction),
empty feature lists drop to bare, order normalized + deduped. The allowlist
is the security containment, not privilege. Mirrors sandbox_services: not on
ProjectCreate, only Project + ProjectUpdate.
request_sandbox gains an extensions arg; _sandbox_features_scope unions a
per-call override with the project's standing set (trusted), bounds it to the
opted set + allowlist, rejects a non-opted service or unallowed feature with
the allowlist named in remediate — scope-first priority preserved by
rej_scope or rej_features. ensure_sandbox threads features through to
provision(); cache-by-features: a cached entry satisfies a new call iff
services are a subset AND every requested feature per service is already
cached — a feature superset re-provisions (rotates creds), mirroring the
services-superset case. available_extensions rides the evidence payload so an
agent doesn't guess what was activated.
Gate: ruff clean, mypy clean (9 modules), 51 tests pass (incl. migration
round-trip).
|
||
|
|
3838d64eaa |
sandbox: kitchen-sink images, feature-aware selection (Phase 2)
Phase 1 made the provisioner able to activate allowlisted extensions post-ready but kept the bare upstream images. Phase 2 ships the images that actually carry the extension/module files, and selects them only when a venture requests features — bare sandboxes stay on the light upstream image (no heavier pull, honoring the 'existing opters stay bare' decision). - _PostgresEngine / _RedisEngine gain kitchen_sink_image + image_for(features): bare (no features) -> the light image; features requested -> the kitchen-sink image. The provisioner runs engine.image_for(features), not engine.image, so the bare path is byte-for-byte unchanged. Mongo inherits the base image_for (returns its image regardless — no activatable features). - docker/sandbox-pg.Dockerfile: pgvector/pgvector:pg16 (ships vector) + postgis apt install; contrib (pg_trgm/citext/uuid-ossp) inherited from the official postgres base. Built at deploy via the sandbox-pg-image compose one-shot (mirrors the agent-image builders); the provisioner's _ensure_image finds the local tag and never pulls. Published by release.yml; pulled in registry compose. The verify step fails loudly if an extension's files are missing. - _RedisEngine kitchen-sink image: redis/redis-stack-server:latest (headless; ships search/json/bloom as loadable-but-unloaded modules — no custom build). - Extended the sandbox image-tag ghost-tag guard (the mongo:8-alpine regression test) to also cover kitchen_sink_image: skips locally-built roboco-* images, uses the namespaced Docker Hub endpoint for redis/redis-stack-server. Image-specific package names / module .so paths are verified at the CEO's NAS deploy (the spec's NAS smoke); the unit tests with the fake runner remain the CI bar, and the verify step is the fail-loud safety net for a wrong build. |
||
|
|
b015cde9ad |
sandbox: post-ready extension/module activation + allowlist (Phase 1)
Parameterized sandbox dev DBs — groundwork for 'extensions on the fly'
(docs/internal/specs/2026-07-13-sandbox-extensions-on-the-fly.md). A
venture declares the extensions/modules it needs; the provisioner activates
them post-ready via docker exec, never via bind-mounts or initdb scripts.
Phase 1 (behavior-preserving scaffolding — no image, no schema, no caller
passes features yet):
- Allowlists SANDBOX_PG_EXTENSIONS / SANDBOX_REDIS_MODULES are the ONLY
extensions/modules the system will ever activate — the security
containment, not privilege. plpython3u & co. (superuser-RCE vectors)
are excluded by construction.
- SandboxEngine ABC gains enable_step / verify_step / verify_ok. pg:
CREATE EXTENSION IF NOT EXISTS via psql, verified by a pg_extension
count. redis: MODULE LOAD per module, verified by MODULE LIST. mongo:
no-op (server is batteries-included).
- SandboxProvisioner.provision takes features={service: [names]},
allowlist-validates before any container runs, runs enable then verify
after the base readiness probe; a failed enable or a short verify (image
missing the extension files) is fatal — an agent never receives creds
for a db missing what it asked for. Empty features = bare = the
existing path, byte-for-byte unchanged.
- SandboxConnection gains features; as_payload surfaces
available_extensions / available_modules so the agent doesn't guess.
13 new unit tests (fake docker runner): enable/verify argv per engine,
allowlist rejection of plpython3u before any run, failed-enable + failed-
verify fatality, bare-provision unchanged, payload surfacing.
|
||
|
|
a3524da5f8 |
[90c9474c] Auditor revival: scheduled audit trigger and reactive alert producers (#499)
* [927e64d5] Backend slice: auditor scheduled trigger and reactive alert producers (#496) * [1f2cdb4b] Reactive alert producers at QA-fail and rework (#492) * [1f2cdb4b] feat(services): add auditor-targeted rework alert producers at QA-fail and rework chokepoints * [1f2cdb4b] test(services): fix mypy typing in auditor alert producer unit tests * [1f2cdb4b] docs(backend): document reactive auditor rework alert producers in map and role docs --------- Co-authored-by: Backend Developer 2 <be-dev-2@roboco.tech> Co-authored-by: Backend Documenter <be-doc@roboco.tech> * [5173415f] Scheduled audit trigger, config, and sweep prompt (#493) * [5173415f] Add scheduled audit trigger, interval config, sweep prompt, and focused tests * [5173415f] Allow ROBOCO_AUDIT_INTERVAL_SECONDS=0 to disable scheduled sweeps * [5173415f] docs(audit): document scheduled auditor sweeps and ROBOCO_AUDIT_INTERVAL_SECONDS --------- Co-authored-by: Backend Developer 1 <be-dev-1@roboco.tech> Co-authored-by: Backend Documenter <be-doc@roboco.tech> * [a26c18b9] E2E smoke test for auditor triggers (#495) * [a26c18b9] Add e2e smoke test for auditor scheduled and reactive triggers * [a26c18b9] docs(tests): add e2e smoke test catalog and changelog entry for auditor triggers --------- Co-authored-by: Backend Developer 2 <be-dev-2@roboco.tech> Co-authored-by: Backend Documenter <be-doc@roboco.tech> * [3bc47cdc] Fix _fresh_orchestrator state for auditor trigger e2e tests (#497) * [3bc47cdc] fix(tests): initialize orchestrator state in _fresh_orchestrator helper * [3bc47cdc] docs(changelog): add _fresh_orchestrator test harness fix entry --------- Co-authored-by: Backend Developer 1 <be-dev-1@roboco.tech> Co-authored-by: Backend Documenter <be-doc@roboco.tech> * [8323cd50] Fix e2e smoke regression on assembled cell PR #496 (#498) * [8323cd50] fix(e2e_smoke): repair auditor-trigger smoke tests and harden harness * [8323cd50] docs(tests): document e2e smoke harness hardening for PR #498 --------- Co-authored-by: Backend Developer 1 <be-dev-1@roboco.tech> Co-authored-by: Backend Documenter <be-doc@roboco.tech> --------- Co-authored-by: Backend Developer 2 <be-dev-2@roboco.tech> Co-authored-by: Backend Documenter <be-doc@roboco.tech> Co-authored-by: Backend Developer 1 <be-dev-1@roboco.tech> * [37e6d999] Backend: repair failing CI checks on auditor revival PR #499 (#503) * [6e79bada] Triage and fix Python quality gate and Analyze (python) failures (#501) * [6e79bada] fix(task): replace type ignore with forward-reference cast for SQLAlchemy Mapped UUID in get_all_descendants * [6e79bada] fix(notification_delivery): add generic type arguments to dict return types in get_ack_status and get_delivery_summary * [6e79bada] docs(changelog): add Python quality gate type-hygiene fixes to Unreleased --------- Co-authored-by: Backend Developer 1 <be-dev-1@roboco.tech> Co-authored-by: Backend Documenter <be-doc@roboco.tech> * [50e7e104] Triage Analyze (javascript-typescript) failure on backend-only diff (#500) * [50e7e104] Split CodeQL workflow so JS/TS analyzer only runs on panel changes * [50e7e104] docs(backend): document split CodeQL workflow triggers and branch protection notes --------- Co-authored-by: Backend Developer 2 <be-dev-2@roboco.tech> Co-authored-by: Backend Documenter <be-doc@roboco.tech> * [203c426b] Triage and fix e2e lifecycle smoke (scripted agents) failure (#502) * [203c426b] fix(orchestrator): pre-initialize _instances in __new__ so __init__-bypass tests survive _dispatch_audit_work; allow audit_interval_seconds=0; mount /api/notifications in e2e harness * [203c426b] fix(e2e_smoke): restore ROBOCO_AGENT_TOKEN isolation and clarify /api/notifications mount comment * [203c426b] docs(map): document orchestrator __new__ pre-init and e2e harness token isolation for auditor-revival smoke fix --------- Co-authored-by: Backend Developer 1 <be-dev-1@roboco.tech> Co-authored-by: Backend Documenter <be-doc@roboco.tech> * [48cb05c2] Fix remaining e2e lifecycle smoke (scripted agents) failure on auditor-revival PR #503 (#504) * [48cb05c2] Harden AgentOrchestrator __new__ pre-init for auditor dispatch state * [48cb05c2] Document auditor-dispatch pre-init rationale in AgentOrchestrator __new__ * [48cb05c2] docs(orchestrator): extend __new__ pre-init docs for auditor-dispatch state --------- Co-authored-by: Backend Developer 1 <be-dev-1@roboco.tech> Co-authored-by: Backend Documenter <be-doc@roboco.tech> --------- Co-authored-by: Backend Developer 1 <be-dev-1@roboco.tech> Co-authored-by: Backend Documenter <be-doc@roboco.tech> Co-authored-by: Backend Developer 2 <be-dev-2@roboco.tech> * [90c9474c] intake: ambient workspace note + dedupe scope clones by git_url Two intake follow-ups folded into 90c9474c's spec Notes: (a) _resolve_intake_ambient now prepends a workspace note so the intake agent knows its cwd holds clones of every project in the scope (the primary at cwd, siblings alongside under /data/workspaces) and drafts against the real trees via Grep/Glob/Read, not from memory. (b) _clone_intake_scope dedupes slugs by git_url before cloning. A multi-project scope can list several projects pointing at one repo (a monorepo's cell-projects share a git_url); cloning each produced redundant identical workspaces. Mirrors CI-watch's per-git_url dedupe: keep the first slug per non-empty git_url; a project with no/empty git_url is never collapsed onto another so distinct local repos still clone. The dedupe is a pure static helper (_dedupe_slugs_by_git_url) with unit coverage. --------- Co-authored-by: Backend Developer 2 <be-dev-2@roboco.tech> Co-authored-by: Backend Documenter <be-doc@roboco.tech> Co-authored-by: Backend Developer 1 <be-dev-1@roboco.tech> Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
ba7135ba50 |
feat(gateway): carry intake technical depth down the chain + widen review coherence scope (#491)
Two structural issues flagged by the CEO:
1. Task technical-depth dilution — intake's rich analysis (file:line
targets, code examples, rationale) was getting lost as it traveled
umbrella -> root-subtask -> cell -> dev. The detail IS preserved in
Task.description; the dilution was in delegation (PMs re-authoring)
and the intake prompt not demanding depth.
Fixes:
- evidence_repo: ancestor_context_for_task walks the parent chain
(cycle-guarded, depth-capped 16, desc-clipped 1500) and surfaces it
as parent_context in the evidence payload, so a leaf dev finally
sees the upstream intake analysis instead of a bare title.
- evidence_builder: Task.description now rides in the payload;
EvidencePayload gains description + parent_context (omit-when-empty
so no null noise).
- orchestrator: _description_body (capped 4000) injects the
description into the dev spawn prompt + SessionStart briefing.
- role prompts (main_pm/cell_pm/developer/prompter): teach pass-the-
torch, don't-dim-it; prompter now demands file:line/code-examples
in the_work/notes (reconciled with the no-code-level-ACs-on-roots
rule). main_pm's brief-not-a-spec scoped: not-a-spec applies to the
solution only, facts forward verbatim.
2. PR-review/QA scope too narrow — they only checked the AC checklist,
not whether the change is coherent with project structure/intent.
Fixes:
- qa.md + pr_reviewer.md: Coherence & intent rule (intent via
description+parent_context, coherence with project patterns,
standards). Criterion-less major findings allowed for intent drift
(Finding.criterion is optional).
- parent_context + description wired into the gate/QA/inbound-PR
evidence builders (fail-open, logged).
Skipped per YAGNI: a technical_spec JSONB column (detail is already in
description) and a criterion_kind enum (criterion is already optional).
All gates green: ruff, mypy (1152), pytest (12883 passed, 94.82% cov),
xenon, vulture, bandit, pip-audit, deptry, alembic, import-linter,
foundation-check.
Co-authored-by: Renn F <rennf93@users.noreply.github.com>
|
||
|
|
cea3e56628 |
feat(lifecycle): revision findings ledger — structured failure feedback, persisted and delivered down the chain (#486)
* feat(lifecycle): revision findings ledger — structured QA/PR/PM/CEO failure feedback, persisted and delivered down the chain Every bounce used to survive only as flattened prose: rounds overwrote each other in notes_structured, request_changes persisted nothing, two raw dev_notes appends were silently destroyed by the next handoff note, and the dev prompt pointed at fields (qa_notes via evidence(), pm_notes) the API never delivered. Agents re-interpreted and re-discovered every failure before they could start fixing it. - task_review_findings (migration 071, append-only): file/line/severity/ criterion(AC-id-validated)/expected/actual/fix/evidence per finding, with origin (qa|pr_gate|pm|ceo), round, and an open->addressed->verified lifecycle (waived reserved); new tasks.pm_notes + PmReviewContent give request_changes a structured home - producers: fail_review/pr_fail/request_changes take findings=[...] (prose issues shimmed+merged for one release, deprecation-logged); ceo_reject validates its reason (no 500), lands an origin=ceo finding, and bumps round+audit on branchless coordination roots; guardrails at the verb chokepoint (nudge >5, hard reject >10, field caps, traversal-safe file); the dev_notes data-loss appends are removed; new task.request_changes + task.ceo_reject audit events close rework attribution - delivery: qa_notes/pr_reviewer_notes/pm_notes carry the deterministic [F-id8] rendering; claim briefings, evidence(), the REVISION_REQUIRED spawn prompt, PM triage bounced-blocks, and A2A bodies deliver open findings; round-N+1 QA and gate reviewers get the full prior ledger; panel Findings tab + bounced-xN chip; metrics pm_rejects/ceo_rejects + findings counts; vault task notes render a Findings section (fail-open) - resolution closes for every origin: i_am_done and submit_up/submit_root take resolved_findings gated by FINDINGS_ADDRESSED (owner-gated so a stale non-owner PM can never mutate the ledger); pass_review/pr_pass/ complete verify-stamp same-transaction; ceo_approve stamps best-effort - 24 real-DB integration tests drive the full loop through the real choreographer; full suite 12856 green * docs: revision findings ledger sweep — CLAUDE.md, map, RAG corpus - CLAUDE.md: new ledger section + corrected request_changes row - docs/map/review-findings.md (new subsystem map) + surgical updates to task-service/pr-gate-review/metrics-observability/vault/panel maps - docs/rag: producers' findings contract across qa/pr-reviewer/developer/ cell-pm/main-pm/ceo role docs (the PM docs were missing request_changes entirely), verb references, and a new architecture/review-findings.md disambiguating ledger findings from convention findings * test(e2e): resubmit resolves the pr_fail finding per the ledger contract The scripted pr_fail revision loop resubmitted submit_up without resolved_findings — correctly rejected now that FINDINGS_ADDRESSED gates the PM resubmit verbs (green locally, red only in CI since the e2e suite skips without ROBOCO_E2E_SMOKE=1). The scripted PM now reads the open ledger row pr_fail persisted (new open_finding_ids arc helper) and resolves it on resubmit, asserting the open set drains — exercising the coordinator half of the new contract end to end. --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
d03181ab48 |
feat(vault): Obsidian vault V2 — janitor, archival, weekly report, KB ingest, Bases + sync runbook (#482)
* feat(vault): V2 — create-seam + drift janitor, archival, weekly org-report, KB ingest, Bases views + sync runbook Implements the vault V2 canonical spec end to end (the splice guard shipped separately and is reused at KB-ingest time): - materialize-on-create: TaskService.create writes each task's note best-effort from the moment it exists; the transition-touch stops no-oping on live work - drift janitor (services/vault_janitor.py + hourly _vault_janitor_loop): daily changed-task re-projection, random drift sample, archival pass — restart-proof via RoboCo/_meta/.janitor_state.json, 200/cycle caps, per-item isolation, processed-only resume markers, self-repairing state file - archival: vault_archive_days (30, 0=off) moves old terminal tasks' notes to RoboCo/Archive/<year>/Tasks/<project>/ — one write_task code path for janitor and rebuild, id8 lookup across Tasks/+Archive/, alias links keep moves safe - weekly org-report: VaultWriter.write_org_report renders Reports/<ISO-week>.md from MetricsService/UsageService (numbers duplicated into frontmatter for trend queries), once per ISO week, with a best-effort CEO notification - KB ingest: IndexType.VAULT_NOTES + VaultNotesIndexPlugin + _vault_kb_loop embed the CEO's RoboCo/Notes into the RAG corpus — injection guard as a hard gate (flagged notes quarantined with an idempotent callout), traversal- and symlink-contained at both config and engine layers, content-hash dedup, 50-ingest/cycle cap, frontmatter stripped; reaches roboco_kb_search, the mentor default domain, claim-time briefings (kind vault_note), and the panel KB browser; no migration (chunks table auto-creates; migration 030's CHUNK_TABLES tuple appended per the chunks_playbooks precedent) - Bases views (Task Board.base, Reports.base — schema verified against the Obsidian docs) + the Mac sync runbook vault asset - config/flags/compose: vault_archive_days, vault_report_enabled (flags card), vault_kb_enabled (flags card; NAS compose arms it, registry ships it off), vault_kb_dirs (+ overlap/traversal validator), vault_kb_interval_seconds - e2e smoke (tests/e2e_smoke/test_vault_v2.py): real create-seam, real janitor cycle incl. archival + state, real KB engine + real guard * docs: vault V2 sweep — map, RAG corpus, CLAUDE.md - docs/map/vault.md: V1+V2 — janitor/archival/report/KB data flows, new files, config, health posture - docs/map/orchestrator.md + task-service.md: the two new loops, the create seam, the three janitor queries - docs/rag/architecture/obsidian-vault.md: agent-facing what-changed (notes from creation, archive link-safety, CEO notes retrievable, weekly report) - docs/rag/architecture/config-reference.md: the five new settings - CLAUDE.md: vault paragraph covers V1+V2; flags-card list mentions the vault report/KB flags --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
20110debab |
fix(ci): fleet-branch push triggers + dispatcher claim prefilter (#463)
* fix(ci): fleet-branch push triggers close the absent-check gap; dispatcher claim prefilter PROVEN with API receipts: when the PM squash-merges a subtask PR into a branch that is itself another PR's head (GitService.merge_pull_request → GitHub's Merge API), the pull_request synchronize webhook fires unreliably (1 of 3 in the live sample) while plain push events fired 100% — so PR heads sat with ABSENT required checks that three review rounds mistook for green. CI, CodeQL, e2e-smoke, and panel-ci now also trigger on push to the fleet's branch types, deduped by a concurrency group keyed on head_ref||ref_name so a branch that is also a PR head never double-runs. Dispatcher churn: _route_unassigned_pm_task consults the claim guards' own predicate (TaskService.is_pending_claim_blocked, a public wrapper — no duplicated SQL) before routing, so dependency- or sequence-held tasks skip the tick with zero HTTP claim round-trips; fails open so a DB hiccup degrades to the old behavior. * chore(docs): reflow hard-wrapped prose inherited from the six-PR merge train * chore(foundation): regenerate lifecycle artifacts; reflow inherited prose --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
f0f09b2204 |
[1197c975] Re-add Playwright chromium to FE/UX QA images and add browser-verification prompt guidance (#406)
* [3e552255] Re-add Playwright chromium to QA images + prompt guidance (#395) (#405)
* [3e552255] feat(docker): re-add Playwright chromium-headless-shell to QA images
* [3e552255] ci(docker): add Playwright QA image build + headless smoke check workflow
* [3e552255] fix(ci): scope agent-image-smoke.yml trigger to paths only, add PR comment
The workflow was gated by `branches: [master]` on both push and
pull_request, but this repo's task-hierarchy PRs open against nested
parent feature branches, not master, until root->master assembly - so
the workflow never fired on a dev-level PR and produced zero evidence.
Drop the branch filter (path scoping is sufficient) and post the
size-delta table + smoke-check output as a PR comment via
actions/github-script, since no agent role has gh CLI or GitHub API
read access to pull check-run output directly.
* [3e552255] fix(ci): post agent-image-smoke PR comment even on step failure
The 'Post results as a PR comment' step only had
`if: github.event_name == 'pull_request'`, which GitHub implicitly ANDs
with success() — so if the docker build or headless-launch smoke check
failed, the PR comment (the only evidence-delivery path QA/PM has, since
no agent role can read the Checks tab) silently never posted. Added
always() so a partial report always lands on the PR.
This commit also re-lands the branches-filter removal + PR-comment step
from
|
||
|
|
7f138d3bf5 |
[e4ed92d6] Video pipeline per-project requests, re-render action, composition preview (#403)
* [7f2c881a] Project-scope video pipeline + re-render + preview proxy (#386) (#396) * [7f2c881a] feat(video): scope on-demand video requests + render loop to project_id Require project_id on VideoRequestBody (404 when unresolvable or not opted into the video engine), thread it through VideoEngine.open_video_task via a shared resolve_authoring_project helper, and resolve the render loop's motion/ workspace from the authoring task's own project_id instead of the hardcoded self_heal_project_slug. * [7f2c881a] fix(video): cast task.id to UUID before VideoEngine.rerender calls mypy flagged task.id as sqlalchemy.sql.sqltypes.UUID[Any] rather than uuid.UUID in the three rerender tests; cast to UUID per the codebase's established idiom (cast("UUID", obj.id)) used elsewhere for the same SQLAlchemy Mapped-attribute inference gap. * [7f2c881a] docs(video): API endpoints for project-scoped requests, re-render, and preview proxy Add comprehensive API documentation for the new project-scoped video engine endpoints: - POST /api/video/request: on-demand video authoring scoped to project_id (breaking change) - POST /api/video/pipeline/{task_id}/rerender: CEO-triggered re-render with idempotency key clearing - GET /api/video/preview/{task_id}/{file_path}: CEO preview proxy with path-traversal confinement Document project-scoping architecture: authoring tasks and render loop now resolve from task's own project_id instead of hardcoded self_heal_project_slug. Add migration guide covering breaking change to VideoRequestBody schema (project_id now required), error handling changes (404 on unresolvable/non-opted-in projects), and client migration steps. --------- Co-authored-by: Backend Developer 1 <be-dev-1@roboco.tech> Co-authored-by: Backend Documenter <be-doc@roboco.tech> * [8f959c3b] docs(ux_ui): add project picker, re-render control, and composition preview panel spec (#381) (#398) Co-authored-by: UX/UI Developer 1 <ux-dev-1@roboco.tech> * [1fb5b5cb] Project picker, re-render button, and composition preview panel (#397) (#402) * [1fb5b5cb] feat(video): project picker, re-render button, and composition preview panel * [1fb5b5cb] docs(video): add comprehensive guide for project picker, re-render button, and composition preview panel --------- Co-authored-by: Frontend Developer 1 <fe-dev-1@roboco.tech> Co-authored-by: Frontend Documenter <fe-doc@roboco.tech> * [a512f364] Add video_engine_enabled to ProjectSummaryResponse (#412) (#414) * [a512f364] feat(api): surface video_engine_enabled on ProjectSummaryResponse * [a512f364] docs(api): document video_engine_enabled on ProjectSummaryResponse --------- Co-authored-by: Backend Developer 1 <be-dev-1@roboco.tech> Co-authored-by: Backend Documenter <be-doc@roboco.tech> * [03607ab9] Fix re-render control gating/placement and project picker filter (#434) * [f2f3e89f] Fix RerenderControl gating/placement across queue and strip views (#431) * [f2f3e89f] feat(video): widen RerenderControl gating and share it across queue/strip views Extracts RerenderControl into a shared panel/src/components/dashboard/ video-rerender-control.tsx component, widens its gate from render_status === 'failed' to source_task_id + composition_id present (matching what the backend rerender endpoint actually requires), adds a confirm dialog before firing the mutation, and wires the same component into video-pipeline-strip.tsx for still-in-flight rendering/render_failed rows. * [f2f3e89f] docs(video): enhance RerenderControl JSDoc with gating logic and usage examples Add comprehensive JSDoc to the RerenderControl component covering its purpose, gating logic (render for any source_task_id + composition_id, regardless of render_status), three visual button states (idle/loading/ error), confirm-dialog guard behavior, and usage examples for both video-post-queue.tsx and video-pipeline-strip.tsx contexts. Explains why the backend's rerender endpoint doesn't require a failed render and how the component prevents accidental re-renders. --------- Co-authored-by: Frontend Developer 1 <fe-dev-1@roboco.tech> Co-authored-by: Frontend Documenter <fe-doc@roboco.tech> * [404d8ed3] Filter project picker to video-engine-enabled projects (#432) * [404d8ed3] feat(panel): filter video-request project picker to opted-in projects Add video_engine_enabled to the client ProjectSummary type, give ProjectSelector a videoEngineOnly filter prop, default RequestVideoDialog's picker to the current video-enabled project with a friendly empty-state when none exist, and cover the filter with a new project-selector test. * [404d8ed3] docs(panel): add ProjectSelector component API reference with videoEngineOnly filter Document the reusable ProjectSelector component with its props, filtering behavior, and new videoEngineOnly filter for video-engine-enabled projects. Follows the existing component documentation pattern from page-refresh-provider. --------- Co-authored-by: Frontend Developer 2 <fe-dev-2@roboco.tech> Co-authored-by: Frontend Documenter <fe-doc@roboco.tech> * [519a4088] fix(panel): import missing RerenderControl in video-post-queue and correct stale doc (#436) Co-authored-by: Frontend Developer 1 <fe-dev-1@roboco.tech> --------- Co-authored-by: Frontend Developer 1 <fe-dev-1@roboco.tech> Co-authored-by: Frontend Documenter <fe-doc@roboco.tech> Co-authored-by: Frontend Developer 2 <fe-dev-2@roboco.tech> * [8e912c3e] Reflow hard-wrapped video UX design doc to pass quality gate (#439) * [ccfe2015] docs(ux_ui): reflow video request composition-controls spec to one line per paragraph (#438) Co-authored-by: UX/UI Developer 1 <ux-dev-1@roboco.tech> * [99c3ed9c] docs(backend): reflow hard-wrapped prose in video-engine-endpoints.md and video-project-scoping.md (#442) Co-authored-by: UX/UI Developer 1 <ux-dev-1@roboco.tech> * [c2e98fc0] docs(backend): strip stray trailing whitespace in video-engine-endpoints.md fence (#451) Co-authored-by: UX/UI Developer 1 <ux-dev-1@roboco.tech> * [9c7bc11a] Reflow all 3 hard-wrapped docs on this branch and verify quality gate (#457) * [9c7bc11a] test(scripts): guard reflow_md.py --check wiring into make quality * [9c7bc11a] docs(standards): document markdown reflow quality gate workflow and verification Added comprehensive guide explaining the one-logical-unit-per-line markdown prose standard, how the reflow check integrates into make quality, the three reflowed files (video-engine-endpoints.md, video-project-scoping.md, composition-controls spec), and the regression test added to ensure wiring stability. This task verifies all three ACs are satisfied: reflow_md.py --check exits 0, make quality passes (non-DB portions), and the three files are whitespace-only reflowed. --------- Co-authored-by: UX/UI Developer 1 <ux-dev-1@roboco.tech> Co-authored-by: UX/UI Documenter <ux-doc@roboco.tech> --------- Co-authored-by: UX/UI Developer 1 <ux-dev-1@roboco.tech> Co-authored-by: UX/UI Documenter <ux-doc@roboco.tech> * [002f0cdd] docs(rag): document reflow-check zero-diff troubleshooting path (#459) (#460) Co-authored-by: Backend Developer 1 <be-dev-1@roboco.tech> * [e4ed92d6] fix rerender missing-task test — assert the empty queue it creates The test never seeds; the trailing assertion expected a phantom video post. Broken since the branch's first commit but never executed — every earlier CI run short-circuited at a pre-pytest gate step. --------- Co-authored-by: Backend Developer 1 <be-dev-1@roboco.tech> Co-authored-by: Backend Documenter <be-doc@roboco.tech> Co-authored-by: UX/UI Developer 1 <ux-dev-1@roboco.tech> Co-authored-by: Frontend Developer 1 <fe-dev-1@roboco.tech> Co-authored-by: Frontend Documenter <fe-doc@roboco.tech> Co-authored-by: Frontend Developer 2 <fe-dev-2@roboco.tech> Co-authored-by: UX/UI Documenter <ux-doc@roboco.tech> Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
15a3e87a2f |
feat(vault): Obsidian vault V1 — projection core, Auditor narrative, input loop (#458)
* fix(gateway): gate review diffs against the task's real parent branch (#444) The in-path PR-review gate's evidence diff (claim_gate_review) and the pr_pass conventions guard derived their diff base via parent_branch_for string surgery, which reuses the child branch's own team segment — wrong for every cross-team hop (a frontend child of a main_pm root derives a ref that never existed) and silently falls back to the repo default branch, so the reviewer judged the entire inherited base-branch content as the task's own work and failed acceptance criteria the task never touched. Bounced a live goals-tab fix three times, unfixable by branch surgery. The gate now resolves the base via resolve_parent_branch (the parent task's recorded branch_name, cross-team correct) and threads it as a new preferred_parent override through git.diff / list_changed_files / conventions_check_for_task — consulted only when no explicit base is given, so the pinned literal-base contract (base="HEAD~1") and every other diff caller (QA, doc, content) are byte-identical. Parent lookup fails open (derived-base fallback) like the other resolve_parent_branch call sites, and is skipped entirely while the conventions flag is off. Also excludes .uv-cache/ and .claude/ (agent worktrees, private uv cache) from the markdown prose scanner — both are repo-local tool dirs whose vendored/generated files tripped make reflow-check. Co-authored-by: Renn F <rennf93@users.noreply.github.com> * feat(vault): Obsidian vault V1 — projection core + input loop The vault is a rebuildable projection of the DB (never a source of truth), default-off behind ROBOCO_OBSIDIAN_VAULT_ENABLED + ROBOCO_VAULT_PATH. Projection core: VaultWriter materializes tasks/journals/A2A/agents as wikilinked markdown (id-suffixed stable filenames, alias-based links so renames never break, is_private journals excluded like the RAG corpus); event seams materialize on journal write and A2A send and touch task-note frontmatter at the status-transition chokepoint — all best-effort, a vault failure never blocks a verb. Shipped .obsidian config (Dataview, Kanban, team/status graph groups) + _meta dashboards; python -m roboco.vault rebuild/relocate (rebuild preserves the narrative section). Auditor narrative duty: curate_vault content verb (auditor-only, playbook-curation pattern) spawned by a dedicated root-completion hook with its own cooldown — fully separate from _dispatch_audit_work, whose scheduled-sweep/alert-producer revival belongs to the queued fleet task. Input loop: VaultIntakeEngine (ROBOCO_VAULT_INTAKE_*) watches the intake folder for #roboco-tagged notes and materializes each as ONE held draft (confirmed_by_human=False, Secretary-owned, source=vault_note, excluded by the dispatchers via _is_held_ceo_source) with local-model extraction and a deterministic fallback; vault_seen_notes ledger (migration 070) keyed on path+content-hash (the CEO-feedback callout is stripped before hashing so the engine's own append never self-triggers); per-cycle and open-draft caps. Nothing auto-starts. * fix(vault): integrate with master — re-chain migration 070 onto 069, mypy-clean tests The vault branch was cut from slave before the sequence-gate promotion, so migration 070 chained from 068 while master already carried 069 — two heads on merge. Master is now merged in and 070 revises 069. Vault test files also get mypy-clean mock idioms (monkeypatch.setattr over method assignment; await_args narrowed before access). * fix(scripts): dedupe SKIP_DIRS again after the master merge Master still carries the twin-merge duplicate (its dedupe hotfix is an unmerged PR); the merge re-imported it here. * fix(vault): reflow hard-wrapped prose in the vault asset templates * chore(config): exclude .uv-cache and .claude from deptry's scan scope Same repo-local tool dirs the prose scanner skips; the standalone deptry target walks the repo root and drowned in the cache's unpacked wheels. * fix(vault): board-review activation path for vault drafts + relocate graft The input loop's held-artifact posture was a dead end: vault_note drafts were unconditionally held by _is_held_ceo_source, owned by the verbless Secretary, hidden from the panel's approval surfaces by team, and the open-drafts cap counted them forever — the engine self-bricked after ten notes. Vault drafts now ride the intake board-review path instead: a tagged note becomes a PENDING Product-Owner-assigned Board draft (the exact confirm_live_draft board shape), the board reviews it, and only the CEO's approve_and_start makes it deliverable — never-auto-starts now rests on the board gate, proven by tests against the real dispatchers. The cap counts only drafts still awaiting the CEO (team==BOARD, non-terminal), so approval and cancellation both free it. relocate into an existing personal vault now grafts old_root/RoboCo as a direct child (refusing loudly if RoboCo/ already exists there) and adds only absent .obsidian/_meta files — a personal vault's config is never clobbered. An absent destination keeps the whole-tree move. --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
8f3f4236c0 |
feat(tasks): sequence is the bar — strict sibling ordering at the claim chokepoint (#452)
* feat(tasks): enforce sibling sequence order at the claim chokepoint A task with a parent and effective sequence N (COALESCE(sequence, 0)) can no longer be claimed while any sibling with a strictly lower effective sequence is non-terminal — assignee-blind, independent of and stricter than dependency_ids, enforced in _validate_claim_preconditions so both claim paths (gateway verbs and the dispatcher's raw REST claim) cross it. Ties run parallel; cancelled siblings never block; sequence 0 and parentless tasks are unaffected. Live failure this guards: a PM delegated revision subtasks sequenced 0..3 with no dependency edges and seq 2 started alongside seq 0 — sequence was advisory-only. set_sequence's contract updated accordingly. New e2e smoke case drives the refusal and the post-completion claim through the real gateway. * chore(scripts): skip .uv-cache and .claude in the prose scanner Repo-local tool dirs (private uv cache, agent worktrees) carry vendored and generated markdown that tripped make reflow-check. * fix(tasks): wave-derived delegation sequences + claim-gate hardening Three fixes from the adversarial review of the sequence claim gate: Delegation no longer stamps a raw per-sibling ordinal (deterministic merge-order bookkeeping) as sequence — under the strict gate that serialized ALL delegated work, including fully independent cross-dev and cross-cell siblings. Sequences are now wave-derived post-wiring (stamp_wave_sequence: 1 + max same-parent dependency sequence, 0 when independent), so independent siblings tie and run parallel while colliding/ordered work ascends. The cross-cell UX wiring restamps instead of writing relative ux+1 values (a relative write could invert a collision-derived stamp), and the dispatch merge/lane barriers gain a created_at tiebreak for wave-tied siblings so shared-branch merge order stays deterministic. PM-authored sequences are never rewritten. The guard now also fires on reclaims from needs_revision (a lower- sequence sibling delegated after the first claim was invisible), and tasks.parent_task_id gains an index (migration 069) — the guard's sibling probe ran as a Seq Scan on the hottest verb. --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com> |