fix(gate): scan the repo configuration too, and assert nothing tracked goes unread

The leak scan reads an enumeration — SURFACES plus ROOT_FILES — so any tracked file
outside it is invisible, and an unread file reads as a clean one. Nine were: .gitignore,
.gitattributes, .mise.toml, and .github/** (dependabot plus the three workflows). All
clean today, which is the point: nothing would have caught it if they were not. This is
the second time the enumeration has drifted — Axiom-77q9 fixed the first, 38 files under
axiom-mcp — and both were found by hand rather than by the gate.

.github is now a surface and the three dotfiles are root files, so the scan reads 1985
files instead of 1978. The workflow's paths filter gained the same four entries, since a
change to a file the scan reads must still start the job — the CI-paths test enforces
that pairing and failed until it did.

The durable fix is the assertion rather than the four additions: a new test derives the
tracked file list from git and fails if any file is in neither the scanned set nor
SELF_EXEMPT, naming the file and where to declare it. Verified by deleting the .github
surface and watching it fail with all four paths listed, then restoring. The third
omission now fails in CI instead of waiting to be noticed.

Verified: 518/518 unit tests, 15/15 leak-scan tests, pre-deploy --static clean.
This commit is contained in:
Charles Wiltgen
2026-09-16 15:00:04 -07:00
parent 4257671cee
commit 03a84d3de8
3 changed files with 49 additions and 4 deletions
+8 -2
View File
@@ -33,7 +33,10 @@ on:
- 'MARKETPLACE-SUBMISSION.md'
- 'CURSOR-MARKETPLACE-SUBMISSION.md'
- 'SUBMISSION-STATUS.md'
- '.github/workflows/test-suite.yml'
- '.github/**'
- '.gitattributes'
- '.gitignore'
- '.mise.toml'
push:
branches: [main]
paths:
@@ -56,7 +59,10 @@ on:
- 'MARKETPLACE-SUBMISSION.md'
- 'CURSOR-MARKETPLACE-SUBMISSION.md'
- 'SUBMISSION-STATUS.md'
- '.github/workflows/test-suite.yml'
- '.github/**'
- '.gitattributes'
- '.gitignore'
- '.mise.toml'
permissions:
contents: read
+27 -1
View File
@@ -3,7 +3,8 @@ import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import test from "node:test";
import { type LeakRule, ROOT_FILES, RULES, SURFACES, isObviousPlaceholderUuid, scanRepo, shippedFiles } from "./leak-scan.ts";
import { execFileSync } from "node:child_process";
import { type LeakRule, ROOT_FILES, RULES, SELF_EXEMPT, SURFACES, isObviousPlaceholderUuid, scanRepo, shippedFiles } from "./leak-scan.ts";
/**
* The engine is tested with its own rules, not the shipped list. Two reasons: a
@@ -322,3 +323,28 @@ test("the CI paths filter covers every surface the scan reads", () => {
assert.ok(entries.includes(file), `root file "${file}" is not in the CI paths filter`);
}
});
test("every tracked file is scanned, or exempt with a reason", () => {
// The surface model enumerates what to read, so anything outside the enumeration is
// invisible — and an unread file reads as a clean one. That has shipped twice: 38
// axiom-mcp files (Axiom-77q9) and later .gitignore, .gitattributes, .mise.toml and
// .github/**. Both were found by hand. This asserts the property instead, so the
// third omission fails here rather than waiting for someone to notice a gap.
const root = path.resolve(import.meta.dirname, "..");
const tracked = execFileSync("git", ["-c", "core.quotepath=false", "ls-files"], {
cwd: root,
encoding: "utf8",
})
.split("\n")
.filter(Boolean);
const scanned = new Set(shippedFiles(root));
const unread = tracked.filter((rel) => !scanned.has(rel) && !SELF_EXEMPT.has(rel));
assert.deepEqual(
unread,
[],
`tracked file(s) in no scanned surface: ${unread.join(", ")}. Add the directory to ` +
`SURFACES or the file to ROOT_FILES in scripts/leak-scan.ts, and add it to the ` +
`workflow's paths filter — an unscanned file reads as a clean one.`,
);
});
+14 -1
View File
@@ -83,12 +83,22 @@ export const SURFACES: Surface[] = [
{ path: "docs", trackedOnly: true },
{ path: "tools", trackedOnly: true },
{ path: "scripts", trackedOnly: true },
// Repo configuration ships publicly and can carry a local path (a tool config, a
// runner path in a workflow) as easily as source can. It was in no surface until a
// tracked-but-unscanned check found it; scripts/leak-scan.test.ts now fails when any
// tracked file is in neither a surface nor ROOT_FILES nor SELF_EXEMPT, so the next
// omission is caught rather than discovered.
{ path: ".github", trackedOnly: true },
];
/**
* Root files are read from disk, not from the index: CHANGELOG.md is gitignored
* here yet is rendered into the published site, so "tracked" is the wrong test
* for them.
*
* The three dotfiles were added by the tracked-but-unscanned check. `.mise.toml`
* and `.gitignore` both name local paths in normal use, which is exactly what the
* absolute-home-path rule exists to catch.
*/
export const ROOT_FILES = [
"README.md",
@@ -100,6 +110,9 @@ export const ROOT_FILES = [
"MARKETPLACE-SUBMISSION.md",
"CURSOR-MARKETPLACE-SUBMISSION.md",
"SUBMISSION-STATUS.md",
".gitattributes",
".gitignore",
".mise.toml",
];
const SKIP_DIRS = new Set([
@@ -120,7 +133,7 @@ const SKIP_DIRS = new Set([
* legitimate place for the strings to appear. Exempting them by name is narrower
* than weakening a pattern to avoid matching its own source.
*/
const SELF_EXEMPT = new Set(["scripts/leak-scan.ts", "scripts/leak-scan.test.ts"]);
export const SELF_EXEMPT = new Set(["scripts/leak-scan.ts", "scripts/leak-scan.test.ts"]);
export const RULES: LeakRule[] = [
{