Commit Graph
4 Commits
Author SHA1 Message Date
8f6dde9a50 feat(sandbox): pluggable per-engine registry (postgres/redis/mongo) (#324)
* feat(sandbox): pluggable per-engine registry (postgres/redis/mongo)

Replaces the hardcoded postgres+redis branches in the provisioner and the
env emitter with a registry of SandboxEngine specs (image, run args,
readiness probe, connection, ROBOCO_TEST_* env) in a pure low module
(roboco/models/sandbox.py). VALID_SANDBOX_SERVICES is derived from the
registry — single source of truth — and the provisioner + orchestrator
iterate it, so adding an engine is one class + one registry line, not
another branch. Adds a mongo:8-alpine engine (ROBOCO_TEST_MONGO_*) as the
third service alongside postgres/redis.

Also fixes the cold-pull loop that stranded v0.19.0 board agents with
empty error strings: docker run pulled inline under a 20s deadline, so a
NAS cold pull was killed, cancelled, and re-pulled from scratch forever.
_ensure_image now inspects + pulls (300s) before run; provisioning errors
log type+message so a bare TimeoutError no longer shows as "".

Panel edit-project dialog: postgres/redis toggles -> a Set<string>
multi-select driven by a SANDBOX_SERVICES catalog, so new engines appear
in the UI by adding to the catalog.

Tests: engine parity (allowlist==registry, unique slugs/images, no None
leak in env, SandboxInfo aggregates every engine), mongo provision + env
injection, plus the existing postgres/redis provision/env/spawn/janitor
suite updated to the registry shape. 821 unit / 5 skip green; ruff + mypy
(360 files) clean.

* docs(sandbox): reflect pluggable engine registry + mongo across docs

CHANGELOG (0.19.0): Added entry for the pluggable sandbox engine registry
(postgres/redis/mongo) + Fixed entry for the cold-pull loop/empty-error
strand that boarded v0.19.0 board agents.

docs/map (9 files): sandbox subsystem blurbs, SandboxProvisioner rows,
_maybe_provision_sandbox/_append_sandbox_env rows, feature-flag rows, the
migration-057 row + v0.17.0 delta, and the models.md VALID_SANDBOX_SERVICES
note — all retitled to DB/Redis/Mongo via the engine registry
(roboco/models/sandbox.py), with the one-class-one-line extension story and
the _ensure_image cold-pull fix. Production-network (roboco_data) lines left
as postgres+redis — mongo is sandbox-only, not a prod service.

docs/rag (3 files): sandbox-db.md rewritten around the registry (engine list,
generic _provision_engine, image pre-pull, ROBOCO_TEST_DB_*/REDIS_*/MONGO_*
incl. MONGO_AUTH_DB=admin, single emit_env); config-reference sandbox flag
row + subsection retitled; db-network-isolation framing broadened to
postgres/redis/mongo. preconditions-and-rejections left untouched (its hit
was an unrelated gateway see-also link).

* test(e2e): harden umbrella close terminal reads with bounded wait-for-state

The MegaTask umbrella close test flaked once on CI (ceo-approve returned
200 but the re-fetch saw awaiting_pm_review) then passed on re-run. The
production path is deterministic: complete -> main_pm_complete ->
submit_pm_review -> escalate_to_ceo -> ceo_approve -> commit, all on one
session, all awaited; the fire-and-forget completion hooks are isolated
(own session, best-effort, never touch task.status or the request session).
20 local runs could not reproduce it.

The one real surface is the read pattern: the e2e stack commits on the
uvicorn thread's loop and reads via a separate loop (run_db -> asyncio.run
with a fresh engine), so a terminal single point-read can race a
still-draining completion hook on a contended runner. Replace the two
terminal point-reads with a bounded wait_for_status poll. Strictly better
than a one-shot read: absorbs the transient, and a genuine state bug still
surfaces via the timeout branch asserting against the last-read state.

---------

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-07-07 13:59:53 +02:00
Renn F da0fa2e33a test(e2e): feature-spotlight end-to-end scenario (catches the unregistered-verb gap)
Drives the Head of Marketing calling propose_feature_spotlight through the real
do_server -> /api/v1/do route -> ContentActions -> XEngine, then asserts a held
x_feature draft is created (confirmed_by_human=False) and the exploration task
completes. Red-then-green verified: reverting the do_server registration
(3f90d382) makes this fail with the exact live symptom. This is the e2e coverage
the wiring gap should have had (standing rule: gaps become e2e smoke).
2026-07-04 09:30:09 +02:00
48f2944086 MegaTask umbrella e2e scenario + batch root-subtask completion fix; comms dead-code deletion (#296)
* chore(panel): delete the five dead comms components

The comms audit found communications-view, channel-sidebar,
channel-item, message-list, and message-item exported but rendered by
no page — the live /communications page and the session detail render
their own inline content and import only MessageComposer and
MessageTypeBadge, which stay. Verified zero consumers outside the dead
cluster before deletion; panel gates green (tsc, lint, 187 tests).

* feat(tests): e2e scenario 4 — MegaTask umbrella; fix batch root-subtask completion wall

Scenario 4 seeds an umbrella + two dependency-linked root-subtasks:
sequencing hold proven (unmet_dependency on RS2's i_will_plan while RS1
lives), RS1 completed through the entire real chain to a master merge,
hold lifts, RS2 completes, umbrella closes branchless via ceo-approve
and never carries a PR.

Product fix it surfaced on first run: _main_pm_complete_guard and
escalate_to_ceo refused ANY parented task as 'not a root', but a batch
root-subtask is parented (the umbrella) BY DESIGN — both sites now
consult is_batch_root_subtask, plain subtasks stay refused. Live
root-subtasks previously needed CEO god-mode to close. Regression
tests added; built subagent-driven (Sonnet 5) and reviewed.

---------

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-07-02 22:02:39 +02:00
d1cf6ecbf3 Wave 1: PR-gate turn cut, task search, trace timestamps, Secretary edits + e2e scenarios 2–3 (#295)
* feat(tests): e2e scenario 2 — the PM merge chain through the PR gate

Shared arcs extracted (arcs.py: canonical-company seeding + dev/qa/doc
segments); scenario 2 seeds a root->cell->dev hierarchy mid-flight, rides
the child through the scenario-1 arc into the cell branch (real squash
via the fake GitHub), then submit_up -> claim_gate_review/pr_pass ->
dispatcher re-claim (mirrored) -> PM complete merging cell->root. This is
the exact PM->reviewer->PM turn sequence the wave-1 turn cut shortens —
the BEFORE-net. Learned seams scripted: commit-subject validator (>=20
chars), reviewer learning-note gate, pr_pass clears ownership by design.

* feat(runtime): PR-gate turn cut — assembled parents auto-submit to the reviewer

When every child of an assembled parent is terminal, the closure
dispatcher now runs the real submit_up/submit_root through the internal
API as the owning PM (_try_auto_submit) instead of spawning the PM for
that turn — the submit's substance is deterministic gate code. Any gate
refusal falls back to the classic PM closure spawn; pr_fail routing and
the PM's final merge turn are unchanged; umbrellas never auto-submit.
ROBOCO_PR_GATE_AUTO_SUBMIT_ENABLED default-on; task.auto_submitted audit
row per cut. Proven by e2e scenario 2b (real API, real gates, real git)
against scenario 2 as the before-net.

* feat(notes): structured note sections carry a written_at trace stamp

Sections are overwrite-in-place, so without a stamp there was no way to
reconstruct WHEN a dev/qa/doc/reviewer note landed (CEO reMarkable item:
trace TIMESTAMPS). apply_structured_note stamps ISO written_at beside
the model fields; the panel notes tab renders it next to each card
title (pre-stamp rows render nothing). Progress updates, commits, and
journal entries already carried timestamps — this was the one gap.

* feat(tasks): server-side task search — title, details, and id prefix

The task list's search box only matched titles client-side, and the
trimmed summary payload deliberately carries no description — so
keyword/details/id search was impossible in the browser by design.
GET /tasks/summary gains q (ILIKE over title+description, id-prefix
match, composed with team/status and the view-permission scoping);
the panel debounces the box into the summary fetch and drops the
title-only client filter that would have hidden description matches.

* feat(wave-1): trace timestamps, real task search, Secretary task edits

- apply_structured_note stamps written_at per section; the panel notes
  tab shows it (the one trace surface without a timestamp).
- GET /tasks/summary?q= searches title+description+id-prefix server-side
  (summaries carry no description by design); panel debounces into the
  fetch and drops the title-only client filter.
- Secretary control_task gains a CEO-gated edit action over the content
  allowlist, and GET /secretary/tasks?q= resolves task names to ids for
  the chat. PM-side expansion deferred per the CEO's 'not that much'.

* fix(workspace): dep-update probe scrubs the inherited venv pin

Under uv run the orchestrator's process tree carries VIRTUAL_ENV, and a
uv-based dep_update_command in the throwaway probe clone would target
that venv instead of the clone's — the same hazard _uv_subprocess_env
already guards on the install path.

* build: private per-repo uv cache — isolate from machine-wide uvx servers

Root cause of the recurring rich/pip/bandit rot, with evidence: uv cache
clean timed out on the ~/.cache/uv lock ('is another uv process
running?') — three uvx mcp-server-fetch processes (Claude Code fetch MCP,
one alive since Wednesday) share that cache and race repo syncs on it;
poisoned entries then survive venv rebuilds because rm -rf .venv never
touches the cache, and every re-link reproduces the breakage. UV_CACHE_DIR
now pins <repo>/.uv-cache (gitignored). The earlier UV_NO_SYNC
serialization stays as defense-in-depth but was not the whole story.

* feat(tests): e2e scenario 3 — pr_fail revision loop + root→CEO chain

3a: reviewer pr_fail with a concrete issue -> needs_revision ->
i_will_plan re-entry (full plan gates) -> real fix lands on the cell
branch (the unchanged-PR hard gate refuses resubmit until it does) ->
clean second pass -> merge. 3b: submit_root -> gate -> Main PM complete
escalates the root to the CEO -> the REAL approve-and-merge endpoint
squash-merges to the origin's master. Harness gains the tasks router, a
seeded CEO identity, origin_commit, and a fake GitHub whose head.sha is
recomputed live (real-GitHub semantics the unchanged gate reads). Seeds
now encode the real shape: delivery roots are team=main_pm and
planning-typed.

---------

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-07-02 21:05:50 +02:00