feat(gate): verify CITATIONS fragments against the repo at the merge gate#22
Merged
Conversation
…gate
EVIDENCE_CONTRACT promises every gate verdict's quoted_fragment appears
verbatim at the cited file, but the gate only checked block *presence* — a
verdict could cite code that exists nowhere and still pass. This adds
programmatic verbatim verification, wired to fire in the default
managed-agents transport.
─── Pure core (src/gate.ts) ───
- parseCitations — hand-rolled, zero-dependency parser (matching fab's
no-deps style) for the CITATIONS block → {claim, file, lineRange,
quotedFragment} tuples, dedenting the `quoted_fragment: |` block scalar.
Returns [] on a format it doesn't recognize, so verification can only ever
strengthen the gate, never false-REJECT from parser brittleness.
- verifyCitations(citations, FileReader) — line-based, whitespace-tolerant
contiguous-run match (each line trimmed, blanks dropped) so block-scalar
dedenting and indentation differences don't cause false negatives. Returns
a status discriminator: ok | fragment-not-found | file-unreadable | malformed.
- parseGateVerdict gains an optional injected `readFile`. Conservative
blocking policy: REJECT only on fragment-not-found — the file WAS read and
the cited code is not in it (unambiguous fabrication). file-unreadable (a
404 from path-convention drift or a transient failure) and malformed are
left non-blocking, so verification never turns an infra hiccup or a parser
gap into a false REJECT. With no reader it is a no-op — a strict superset of
prior behavior. The module stays pure (all I/O injected) and
transport-agnostic.
─── GitHub-backed reader (src/git.ts, src/workflows.ts) ───
- fetchRepoFile — GitHub Contents API over native fetch; decodes base64,
returns null on 404 (clean "doesn't exist" signal), throws on
auth/rate-limit/network and on the >1MB "encoding: none" response so callers
can fail open rather than mistake infra failure for fabrication.
- buildCitationReader (workflows.ts) — prefetches every file a verdict cites
from the feature branch into a Map, then hands parseGateVerdict a synchronous
reader over that cache (keeps gate.ts pure/sync). Fail-open: any non-404
fetch error skips verification for that role. This is what lets verification
run in managed-agents, where the work tree lives in the cloud sandbox, not
on fab's disk.
- preCreateFeatureBranch now also returns the resolved {token, owner, repo,
branch}; executeWorkflow threads it as citationSource into runMergeGate,
which verifies each gate role's verdict against the branch.
─── Tests ───
- gate.test.ts — parseCitations (block scalar, multiple, none); verifyCitations
(present / absent / missing-file / indentation-tolerant / contiguity, with
status assertions); parseGateVerdict × reader (keeps APPROVE on match, REJECT
on fabricated fragment, non-blocking on unreadable file, unchanged with no
reader, REJECT verdict unaffected).
- git.test.ts — fetchRepoFile (base64 decode, 404→null, non-404 throws,
encoding-none throws, directory→null, path/ref url-encoding + auth header).
Intentional limitations: a fabricated FILE path (nonexistent file) is
non-blocking to stay safe against the project-slug path-convention ambiguity;
only a fabricated FRAGMENT in a file that exists blocks. Files are fetched
per-cited-file per-role (no cross-role cache yet).
Verification: npm run lint / build / format:check clean; npm test 286/286.
Co-authored-by: stxkxsbot <275011021+stxkxsbot@users.noreply.github.com>
ba9205c to
a17927a
Compare
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.
See the commit message for full details. Independent of #21 (branches off
main; touchesgate.ts/git.ts/workflows.ts, no overlap).This is the P2.a item from the mid-2026 capabilities plan: rather than adopt native structured outputs (which couples the transport-agnostic gate to one transport), it closes the gap the
EVIDENCE_CONTRACTalready promised — that eachquoted_fragmentappears verbatim at the cited file — whichparseGateVerdictpreviously only checked for presence.Summary
gate.ts):parseCitations+verifyCitations(line-based, whitespace-tolerant,status-discriminated) + an injectedreadFileonparseGateVerdict. Strict superset — no reader → unchanged behavior; module stays pure/transport-agnostic.git.tsfetchRepoFile,workflows.tsbuildCitationReader): prefetches cited files from the feature branch so verification fires in the default managed-agents transport (work tree lives in the cloud sandbox, not on disk).fragment-not-found(file read, cited code absent = fabrication). A 404 / path-convention drift / transient fetch error is non-blocking and fails open — verification can only strengthen the gate, never false-REJECT a legitimate PR.Intentional limitations
Verification
npm run lint/build/format:checkclean ·npm test286/286 (13 new gate + 6 new git tests).