Validate hunter findings with an orchestrator before filing; opt-in autofix and merge behind an independent PR review - #235
Merged
Conversation
…utofix and merge behind an independent PR review Refs #211 Co-Authored-By: Colonizer <noreply@colonizer.dev>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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_findingnow runsvalidation::validatebefore the filing path: a one-shot orchestrator-model call (the agent module'smodelsetting, reusingautonomy::ask_model— the issue is right that this is a routing decision, not new plumbing) returns{real, severity, reason}. A real finding emits avalidatedchain event + ledger line and then goes through the existingfindings::filepath unchanged, including duplicate detection and the 5-per-colony cap. A rejected finding emitsrejectedwith its reason;parse_decisionrefuses 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, andreview_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
publishmodule (github-pr) gains two settings,autofixandautomerge, both defaultfalse;POST /api/sessionsgains per-runautofix?/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 normalsessions::createflow (own branch/worktree,model_tier: Noneso it runs the orchestrator model,autofix: Some(false)so fixes never cascade,automergeinherited,fix_forback-reference to the hunter). When the fix colony's PR opens,publish.rsspawns 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 truncatedgh 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_requestcovers it. Verdictpass+ automerge →gh pr merge --squashand amergedevent. Verdictfail→ 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.jsonlbecomes a staged, append-only record (validated | rejected | filed | duplicate | fix_colony | review | merged | error, legacy lines inferred as filed/duplicate) served byGET /api/sessions/{id}/findings(404 unknown colony) and the aggregateGET /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'sevents.jsonland broadcast on its WS, soscripts/colony-report.mjsand the cockpit can reconstruct the chain. They are host-generated: the runner never emits them, soprotocol.rs,docs/agent-events.schema.jsonand 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.tsxare untouched (siblings #205/#210 own them).Docs.
docs/protocol.md: two new endpoint rows, thePOST /api/sessionsbody 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
events.jsonlseq space as agentd-stamped events. The fix uses two cursors:Runtime.agent_seq(agentd dedupe/reconnect cursor, advanced only by real agent events) andRuntime.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 tofile_cursor+1and keeps its true seq in a newa_seqfield;Runtime::loadrecoversagent_seqfrom 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 testa_host_chain_event_does_not_consume_the_next_agentd_seq_or_drop_that_eventpins all of it.a_seqis inert everywhere else (browser reducer default,AgentEvent::Other, colony-report).automerge: Some(inherited)while its ownautofixisSome(false)(no cascading). Test-pinned.""), every finding now ends as a ledger/logerrorinstead of being filed. That is the issue's requirement, but it is a behaviour change worth a release note: operators must set the orchestratormodelfor findings to file.spawn_fix_colonyreturnsPin<Box<dyn Future>>rather than beingasync— required to break a real E0391 opaque-type cycle (create → boot → … → file_finding → spawn_fix_colony → create). Behaviour identical.<session_dir>/review.mdbefore posting and intentionally kept (removed with the session dir at cleanup), so a failedgh pr commentstill leaves a copy.Verification
cargo test --workspace: 440 colonizer tests + agentd integration/smoke, all pass (new: parse decision/verdict tables,ensure_independentself-review rejection, chain-event append+broadcast, ledger recording, fix-colony spawning end-to-end againsttest_app, autofix/automerge helpers, cap/record semantics, seq regression test).cargo clippy --workspace --all-targets -- -D warningsandcargo 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).Size
git diff --stat: 15 tracked files, +906/−52, plus 3 new files (validation.rs649,cockpit/findings.ts58,findings.test.ts114) — ~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: newSession/NewSessionfields 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_modelmerely widened topub(crate)(one line).🤖 Generated by Colonizer in a microVM