Expand description
The merge-verify gate — the soundness boundary of the Foreman pattern.
Under the Path-B design (see
docs/proposals/verified-parallel-coding-orchestrator.md §7), static
footprint analysis is only an advisory scheduling hint. Correctness comes
entirely from this gate, which verifies each farmed-out worktree before its
changes are integrated.
§Fail-closed by construction
The cardinal rule (enforced after @neo/@linus review of the first cut): a
verdict of MergeVerdict::Accepted requires positive evidence that the
required checks ran and affirmed. The absence of a failure is never
acceptance. Concretely:
- A build/test that was not configured ⇒
MergeVerdict::Inconclusive, never accepted — unless the caller supplies an explicit, auditedNoVerifyWaiver(e.g. a docs-only change), which yieldsAccepted { basis: Waived }. - An unparseable changed file, a policy denial, or any “we don’t know” state resolves toward reject/inconclusive, not accept.
§Checks
- AST-diff containment —
car_ast::diff_symbolstells us which symbols the worktree actually changed; any outside the subtask’s declared footprint is a violation (a non-deterministic agent editing beyond what it promised). Advisory: skipped when no footprint was declared. - Duplicate-declaration scan — two definitions of the same
(name, kind)introduced in one file (a CodeCRDT semantic-conflict class that physical worktree isolation cannot catch). - Policy consult — the integration is checked against the shared
PolicyEngineas a gating input (it can deny the merge), not just an audit. This is the “policy-aware” differentiator vs a baregit merge. - Build/test gate — the load-bearing soundness leg; the compiler is also our broken-reference detector (which is why we don’t hand-roll an unsound one). Runs only if the AST checks passed and policy allowed.
- Audit — every verdict is appended to the shared
EventLogas aGateAccepted/GateRejectedevent, carrying the evidence provenance B3 needs to attribute (not merely count) false-accepts.
The pure verification logic (extract_changes, containment_violations,
duplicate_declarations, decide) takes already-extracted content and is
fully unit-testable without git. verify_changes orchestrates the I/O
(build/test command, policy consult, audit emission); deriving FileChanges
from a real git worktree is the caller’s job (B2).
Structs§
- Changed
Symbol - A symbol the worktree actually changed.
- Containment
Violation - A changed symbol that fell outside the declared footprint.
- Declared
Footprint - What a subtask declared it would change. Advisory: an empty footprint means “no declaration”, which disables the containment check (but never enables acceptance on its own — the build/test gate is still required).
- Duplicate
Declaration - Two definitions of the same
(name, kind)in one file after integration. - File
Change - One file’s before/after content as observed in a worktree.
Nonecontent means the file did not exist on that side (whole-file add or delete). - Gate
Config - Configuration for a single gate run.
- Gate
Evidence - The provenance of one gate run — what was checked, what ran, what each leg found. B3 uses this to attribute false-accepts to a root cause, not merely count them.
- NoVerify
Waiver - An explicit, audited waiver letting a legitimately test-less change (e.g. docs-only) be accepted without a build/test run. This is the only way a missing build/test can yield acceptance — it must be deliberately constructed by the caller, never defaulted.
- Symbol
Ref - A
(file, symbol)location.fileis repo-relative.
Enums§
- Acceptance
Basis - Why a worktree was accepted. There is no way to construct this from “nothing failed” — it requires either a passed build/test or an explicit waiver.
- Build
Test Status - Outcome of the build/test leg.
NotConfigured(nobody supplied a command) andNotRun(skipped because an earlier check already failed or policy denied) are deliberately distinct from each other and fromPassed— so the verdict logic and the audit trail can never confuse “forgot” with “ran”. - Change
Kind - How a symbol changed between before and after.
- Check
Outcome - Whether a check ran and what it found.
NotRunis never acceptance-eligible. - Merge
Verdict - The gate’s verdict on one farmed-out worktree. Three-valued and fail-closed:
Inconclusivemeans “we could not affirm safety” and must be treated by the caller exactly like a rejection for integration purposes (it just signals a different remediation — usually “configure a build/test”, not “replan”). - Policy
Decision - Whether policy permits the integration. A
Denyis absolute.
Functions§
- containment_
violations - Flag changed symbols outside a declared footprint. Empty when no footprint was declared (containment disabled).
- decide
- The pure acceptance decision. This is the most safety-critical logic in the
gate, so it lives here — pure and exhaustively testable — rather than inline
in the async orchestrator. Acceptance requires affirmative evidence;
every “we don’t know” path resolves to
RejectedorInconclusive. - duplicate_
declarations - Scan the integrated
afterside for duplicate declarations: two definitions of the same(name, kind)in one file. Usesall_symbols()so method-level duplicates insideimpl/class blocks are caught (the common CodeCRDT case), not just top-level ones. Cross-file duplicates are normal and not flagged. - extract_
changes - Extract the symbols each file change touched, plus the set of content-changed
files
car-astcould not parse. A file with no content delta is ignored. A file with a content delta whose present side fails to parse is recorded inunparsed_changed_files(symbol checks are blind to it — only build/test covers it). - verify_
changes - Run the full gate on a worktree’s changes and return a fail-closed verdict.
infraprovides the shared policy engine (a gating input) and event log (the audit trail). The build/test command runs only if the AST checks passed and policy allowed — integrating a tree we already know is unsafe is wasted work.