Skip to content

Validate hunter findings with an orchestrator before filing; opt-in autofix and merge behind an independent PR review - #235

Merged
ParallelEntrepreneur merged 1 commit into
mainfrom
colonizer/issue-211-100aaf0d
Sep 21, 2026
Merged

ParallelEntrepreneur merged 1 commit into
mainfrom
colonizer/issue-211-100aaf0d

Conversation

@ParallelEntrepreneur

Copy link
Copy Markdown
Collaborator

Closes #211.

Findings from bug-hunting colonies no longer go straight to GitHub. Every finding is validated by a fresh host-side call on the orchestrator model before it is filed; validated findings can (opt-in) spawn an autofix colony whose PR is reviewed by a distinct fresh session on the orchestrator model and merged only when the review passes and merge is explicitly enabled. Every stage is recorded — an append-only ledger, host-appended chain events, and a cockpit readout — so nothing is silently dropped.

What changed

Validation gate (Rust). events.rs::file_finding now runs validation::validate before the filing path: a one-shot orchestrator-model call (the agent module's model setting, reusing autonomy::ask_model — the issue is right that this is a routing decision, not new plumbing) returns {real, severity, reason}. A real finding emits a validated chain event + ledger line and then goes through the existing findings::file path unchanged, including duplicate detection and the 5-per-colony cap. A rejected finding emits rejected with its reason; parse_decision refuses a rejection that carries no reason. Validation is fail-closed: if the model call fails (or no orchestrator model is configured), the finding is recorded {state:"error"} with a warn in the colony log and nothing is filed — the issue's "no finding reaches GitHub without an orchestrator validation event", enforced by construction.

New module crates/colonizer/src/validation.rs. Hosts validation, the chain-event emitter, the ledger recorder, spawn_fix_colony, and review_fix_pr. Pure decision functions (parse_decision, parse_verdict, ensure_independent) are unit-tested as decision tables in the house style.

Autofix + merge (opt-in, escalating). The publish module (github-pr) gains two settings, autofix and automerge, both default false; POST /api/sessions gains per-run autofix?/automerge? overrides stored on the session. Default behaviour remains validate-and-file only. With autofix on, a filed finding spawns a fix colony through the normal sessions::create flow (own branch/worktree, model_tier: None so it runs the orchestrator model, autofix: Some(false) so fixes never cascade, automerge inherited, fix_for back-reference to the hunter). When the fix colony's PR opens, publish.rs spawns the review.

Independence is a hard invariant. The reviewer is a brand-new session id (short_id()) with a fresh context — it receives only the finding/issue context and the truncated gh pr diff, never the author's transcript. ensure_independent(author, reviewer) runs as the first statement of the review, before any side effect, and rejects author == reviewer; a_session_cannot_review_its_own_pull_request covers it. Verdict pass + automerge → gh pr merge --squash and a merged event. Verdict fail → the review is posted as a PR comment and the PR stays open for a human. Pass without automerge leaves the PR open too.

Findings over HTTP. The per-colony ledger sessions/<id>/findings.jsonl becomes a staged, append-only record (validated | rejected | filed | duplicate | fix_colony | review | merged | error, legacy lines inferred as filed/duplicate) served by GET /api/sessions/{id}/findings (404 unknown colony) and the aggregate GET /api/findings. The cap still counts only filed/duplicate lines, so rejected findings never consume it and legacy files count exactly as before.

Auditability. The five chain events (validated, rejected, fix_colony, review, merged) are host-appended to the hunter colony's events.jsonl and broadcast on its WS, so scripts/colony-report.mjs and the cockpit can reconstruct the chain. They are host-generated: the runner never emits them, so protocol.rs, docs/agent-events.schema.json and the runner fixtures are deliberately untouched. colony-report counts each stage, prints them in transcripts, and flags a failed review as "worth reading".

Cockpit. The colony Inspector gains a FINDINGS section: one row per finding with the stage trail found → validated → filed → fix <id> → review pass/fail → merged, rejection/error reasons, and issue/PR links. Folding lives in a pure module (web/src/cockpit/findings.ts) with 8 vitest cases. OverviewView.tsx/Cockpit.tsx are untouched (siblings #205/#210 own them).

Docs. docs/protocol.md: two new endpoint rows, the POST /api/sessions body extension, and §6.6 extended with the validation pipeline, the five event examples, the ledger format, the opt-in ladder, and the review-independence invariant.

Reviewers should look closely at

  1. Seq-space sharing between host chain events and agentd events. Host events live in the same events.jsonl seq space as agentd-stamped events. The fix uses two cursors: Runtime.agent_seq (agentd dedupe/reconnect cursor, advanced only by real agent events) and Runtime.last_seq (file/broadcast cursor, max seq persisted from either source). When an incoming agentd seq collides with a host line, the persisted line is restamped to file_cursor+1 and keeps its true seq in a new a_seq field; Runtime::load recovers agent_seq from restamped lines (host lines are excluded by their five reserved types) so a mid-colony host restart re-asks agentd for exactly what never landed. The regression test a_host_chain_event_does_not_consume_the_next_agentd_seq_or_drop_that_event pins all of it. a_seq is inert everywhere else (browser reducer default, AgentEvent::Other, colony-report).
  2. Automerge gating. "automerge requires autofix" is implemented as: the module default is gated on autofix, but an explicit per-session value always counts. Without that, merges would be impossible — a fix colony carries automerge: Some(inherited) while its own autofix is Some(false) (no cascading). Test-pinned.
  3. Fail-closed default. On an install where the Orchestrator model setting is empty (Claude Code's default is ""), every finding now ends as a ledger/log error instead of being filed. That is the issue's requirement, but it is a behaviour change worth a release note: operators must set the orchestrator model for findings to file.
  4. spawn_fix_colony returns Pin<Box<dyn Future>> rather than being async — required to break a real E0391 opaque-type cycle (create → boot → … → file_finding → spawn_fix_colony → create). Behaviour identical.
  5. A failed review's body is written to <session_dir>/review.md before posting and intentionally kept (removed with the session dir at cleanup), so a failed gh pr comment still leaves a copy.

Verification

  • cargo test --workspace: 440 colonizer tests + agentd integration/smoke, all pass (new: parse decision/verdict tables, ensure_independent self-review rejection, chain-event append+broadcast, ledger recording, fix-colony spawning end-to-end against test_app, autofix/automerge helpers, cap/record semantics, seq regression test).
  • cargo clippy --workspace --all-targets -- -D warnings and cargo fmt --all --check: clean.
  • cd web && npm test (302 vitest tests incl. 8 new), npm run build (tsc --noEmit + vite): pass.
  • node --test scripts/test/colony-report.test.mjs: 14 pass (5 new: full chain, transcript lines, rejection reason, worth-reading rules, summary aggregation).
  • A cross-layer consistency pass confirmed Rust ↔ web ↔ scripts ↔ docs agree field-for-field on the ledger record, event names, endpoint shapes and setting names, and swept the issue's done-when list with file/line evidence for each item.

Size

git diff --stat: 15 tracked files, +906/−52, plus 3 new files (validation.rs 649, cockpit/findings.ts 58, findings.test.ts 114) — ~1,730 added lines over 18 files, above the 400/10 soft ceiling. The issue asks for an end-to-end pipeline with six deliverables spanning four layers that all have to agree on one contract (Rust core + HTTP, cockpit, report script, protocol doc); roughly 40% of the added lines are tests, and the seq-correctness work (item 1 above) is what makes host-written events safe in an agentd-owned file. Every layer was kept surgical — no refactors of untouched code.

Sibling overlap (for the merge queue)

Files here that siblings also touch: sessions.rs, main.rs, modules.rs (#205, #210), web/src/types.ts, web/src/api.ts, web/src/mock.ts (#208, #209, #210). All edits are additive and localized: new Session/NewSession fields with serde defaults (the field-tolerance test is extended), two new routes, two new publish-schema properties, appended TS interfaces, one mock method + mock publish-schema keys. Smallest-version scaffolding only — no new crates, no migrations, autonomy::ask_model merely widened to pub(crate) (one line).


🤖 Generated by Colonizer in a microVM

…utofix and merge behind an independent PR review

Refs #211

Co-Authored-By: Colonizer <noreply@colonizer.dev>
@ParallelEntrepreneur
ParallelEntrepreneur merged commit 04c12b6 into main Sep 21, 2026
9 checks passed
@ParallelEntrepreneur
ParallelEntrepreneur deleted the colonizer/issue-211-100aaf0d branch September 24, 2026 17:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Validate hunter findings with an orchestrator before filing; autofix and merge behind an independent PR review

1 participant