Skip to main content

Module gate

Module gate 

Source
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, audited NoVerifyWaiver (e.g. a docs-only change), which yields Accepted { basis: Waived }.
  • An unparseable changed file, a policy denial, or any “we don’t know” state resolves toward reject/inconclusive, not accept.

§Checks

  1. AST-diff containment — car_ast::diff_symbols tells 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.
  2. 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).
  3. Policy consult — the integration is checked against the shared PolicyEngine as a gating input (it can deny the merge), not just an audit. This is the “policy-aware” differentiator vs a bare git merge.
  4. 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.
  5. Audit — every verdict is appended to the shared EventLog as a GateAccepted / GateRejected event, 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§

ChangedSymbol
A symbol the worktree actually changed.
ContainmentViolation
A changed symbol that fell outside the declared footprint.
DeclaredFootprint
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).
DuplicateDeclaration
Two definitions of the same (name, kind) in one file after integration.
FileChange
One file’s before/after content as observed in a worktree. None content means the file did not exist on that side (whole-file add or delete).
GateConfig
Configuration for a single gate run.
GateEvidence
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.
NoVerifyWaiver
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.
SymbolRef
A (file, symbol) location. file is repo-relative.

Enums§

AcceptanceBasis
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.
BuildTestStatus
Outcome of the build/test leg. NotConfigured (nobody supplied a command) and NotRun (skipped because an earlier check already failed or policy denied) are deliberately distinct from each other and from Passed — so the verdict logic and the audit trail can never confuse “forgot” with “ran”.
ChangeKind
How a symbol changed between before and after.
CheckOutcome
Whether a check ran and what it found. NotRun is never acceptance-eligible.
MergeVerdict
The gate’s verdict on one farmed-out worktree. Three-valued and fail-closed: Inconclusive means “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”).
PolicyDecision
Whether policy permits the integration. A Deny is 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 Rejected or Inconclusive.
duplicate_declarations
Scan the integrated after side for duplicate declarations: two definitions of the same (name, kind) in one file. Uses all_symbols() so method-level duplicates inside impl/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-ast could not parse. A file with no content delta is ignored. A file with a content delta whose present side fails to parse is recorded in unparsed_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. infra provides 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.