Skip to content

feat(gate): verify CITATIONS fragments against the repo at the merge gate#22

Merged
stxkxs merged 1 commit into
mainfrom
feat/gate-citation-verification
Jun 4, 2026
Merged

feat(gate): verify CITATIONS fragments against the repo at the merge gate#22
stxkxs merged 1 commit into
mainfrom
feat/gate-citation-verification

Conversation

@stxkxs

@stxkxs stxkxs commented Jun 3, 2026

Copy link
Copy Markdown
Member

See the commit message for full details. Independent of #21 (branches off main; touches gate.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_CONTRACT already promised — that each quoted_fragment appears verbatim at the cited file — which parseGateVerdict previously only checked for presence.

Summary

  • Pure core (gate.ts): parseCitations + verifyCitations (line-based, whitespace-tolerant, status-discriminated) + an injected readFile on parseGateVerdict. Strict superset — no reader → unchanged behavior; module stays pure/transport-agnostic.
  • GitHub-backed reader (git.ts fetchRepoFile, workflows.ts buildCitationReader): 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).
  • Conservative blocking: REJECT only on 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

  • A fabricated file path (nonexistent file) is non-blocking — only a fabricated fragment in a real file blocks — to stay safe against the project-slug path-convention ambiguity.
  • 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 (13 new gate + 6 new git tests).

…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>
@stxkxs stxkxs marked this pull request as ready for review June 4, 2026 02:09
@stxkxs stxkxs force-pushed the feat/gate-citation-verification branch from ba9205c to a17927a Compare June 4, 2026 02:09
@stxkxs stxkxs merged commit 4b6f39b into main Jun 4, 2026
2 checks passed
@stxkxs stxkxs deleted the feat/gate-citation-verification branch June 4, 2026 02:13
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.

1 participant