Gate: no machine-specific path may enter tracked source
Some checks are pending
build-gate / build (push) Waiting to run

The repo is clean -- git grep over every tracked file returns zero /home/
paths. That is a real hand-off property: a clone works on any machine, which
is what "the path Doxa inherits" requires. But nothing enforced it. Proven by
experiment: a /home/lk10x/... line planted in internal/settled-tree-gate.mjs
passed check:public-hygiene, check:parse AND check:quoted-scripts. The tree
was clean by accident, not by construction.

This is the worst failure shape for a hand-off. An absolute path works
perfectly for whoever wrote it and breaks only on the inheritor's machine, so
no test run by its author can catch it. The sibling ideation tree already
paid that cost: two docs cite a workspace root that no longer exists, so a
reader following them silently finds nothing.

Not a flag on check:public-hygiene, which already detects these patterns but
walks a SERVED tree where node_modules never exists -- pointed at the repo
root it produced 1,427 findings, nearly all from node_modules/.vite. Its
scope is right for its job. This scans `git ls-files`, the set a clone
actually receives, which excludes node_modules and build output with no
ignore list to maintain.

Negative-tested three ways: the exact planted path that beat three other
gates now FAILS with file:line; a /Users/ path in a doc FAILS; a checkout
with too few tracked files FAILS rather than passing vacuously. Wired into
build:unlocked and confirmed running inside a full build (783 files, exit 0).

It then caught its own documentation -- my reference.md row quoted the
literal patterns as examples. Rewrote the example rather than allow-listing
the file, which would have blinded the gate to a real path in the repo's
largest doc.
This commit is contained in:
doxa-maps 2026-09-22 06:34:27 +00:00
parent 2015378fcd
commit 8fcb187f36
5 changed files with 109 additions and 4 deletions

View file

@ -25,7 +25,7 @@ The scripts these commands are grounded in (from this branch's `package.json`):
// claiming to be ground truth, and this one had silently stopped being it.
"dev": "bun x vite",
"build": "bash internal/build-lock.sh",
"build:unlocked": "bun run check:template-literal && bun run clean && bun run token && for d in plugin-designs/*/; do BUNDLE=$(basename \"$d\") bun x vite build || exit 1; done && bun run tree && bun run check:drop && bun run check:parse && bun run verify && bun run check:sri && bun run check:public-hygiene && bun run check:label-joins && bun run check:descriptions && bun run gallery",
"build:unlocked": "bun run check:template-literal && bun run clean && bun run token && for d in plugin-designs/*/; do BUNDLE=$(basename \"$d\") bun x vite build || exit 1; done && bun run tree && bun run check:drop && bun run check:parse && bun run verify && bun run check:sri && bun run check:public-hygiene && bun run check:label-joins && bun run check:descriptions && bun run check:no-abs-paths && bun run gallery",
"preview": "bun x vite preview",
"serve": "python3 -m http.server 4173 --bind 0.0.0.0 --directory public",
"cap:sync": "./node_modules/.bin/cap sync android",

View file

@ -234,6 +234,7 @@ so running it inside the build would make it unfalsifiable. Run them before publ
| `bun run verify:5-caps` | a capability tier that no longer has a working exemplar — the 5×5 product claim going stale | needs a browser + a Mapbox token |
| `bun run check:quoted-scripts` | a **doc that quotes a command which has since changed** — an omitted step in a quoted chain is invisible: there is no wrong character to notice, just a check the reader believes is in the chain | compares docs against `package.json`; cheap, but it guards documentation rather than build output |
| `bun run check:capability-exemplars` | `documentation/reference-capability-exemplars.md` drifting from the manifest's real tiers (regenerate with `bun run capability-exemplars`) | the table is generated *from* the manifest, so running it mid-build would compare a file against the thing that just wrote it |
| `bun run check:no-abs-paths` | a MACHINE-SPECIFIC PATH in tracked source — a `/home/<user>/` prefix, its macOS equivalent, or the Windows user-profile form. It is in `build:unlocked`, listed here because the failure it prevents is a HAND-OFF failure: an absolute path works perfectly for whoever wrote it and breaks only on the inheritor's machine, so no test run by its author can catch it. Scans `git ls-files` (the set a clone receives), which excludes node_modules and build output without an ignore list. Measured 2026-09-22: the repo was clean BY ACCIDENT — a planted `/home/…` line passed check:public-hygiene, check:parse and check:quoted-scripts, because public-hygiene scans the SERVED tree, not source. |
| `bun run check:settled` | measuring or publishing a tree **mid-build** — a build's `clean` step `rm -rf`s `public/doxa-maps`, so samples taken seconds apart give opposite answers | it exists to be run *around* builds, not inside one |
### Before you run the list — three gates need a PRECONDITION, or they look broken

View file

@ -0,0 +1,103 @@
#!/usr/bin/env node
/**
* check-no-absolute-paths.mjs — no machine-specific path may enter the SOURCE tree.
*
* WHY (measured 2026-09-22)
* The repo is currently clean: `git grep -lE '/home/[a-z0-9_-]+/'` over every tracked file returns
* ZERO. That is a real hand-off property — a clone works on any machine, which is exactly what
* "the path Doxa inherits" requires. But nothing enforced it. Proven by experiment: a
* `/home/lk10x/...` line planted in internal/settled-tree-gate.mjs passed check:public-hygiene,
* check:parse and check:quoted-scripts. The tree was clean BY ACCIDENT, not by construction.
*
* ★ WHY THIS FAILURE IS THE WORST SHAPE FOR A HAND-OFF. An absolute path works PERFECTLY for the
* person who wrote it and breaks only on someone else's machine — the inheritor, i.e. precisely the
* person the north star is about. It is invisible to every test run by its author. The sibling
* ideation tree already paid this cost: two docs cite `~/Workspace/dx/root-doxa-maps/...`, a root
* that no longer exists, so a reader following them silently finds nothing.
*
* WHY NOT REUSE check:public-hygiene. It already detects /home/…, /Users/… and C:\Users\… — but it
* walks a SERVED TREE, where node_modules never exists. Pointed at the repo root it produced 1,427
* findings, essentially all from node_modules/.vite. Its scope is correct for its job; this is a
* different job, so it gets a different (tiny) tool rather than a flag that weakens the first.
*
* SCOPE: TRACKED FILES ONLY, via `git ls-files`. That is the set a clone actually receives, and it
* excludes node_modules, build output and local scratch without needing an ignore list to maintain.
*
* NEGATIVE-TESTED: planting a /home/… line in a tracked source file makes this FAIL naming the file
* and line, EXIT=1; removing it returns PASS. A repo with no tracked files FAILS (vacuous guard).
*/
import { execSync } from 'node:child_process'
import { readFileSync, existsSync } from 'node:fs'
import { resolve, dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
// The patterns are the same three check:public-hygiene uses, kept deliberately identical so the two
// gates cannot disagree about what counts as a machine-specific path.
const PATTERNS = [
[/\/home\/[a-z0-9_-]+\//i, 'local path (/home/…)'],
[/\/Users\/[A-Za-z0-9_-]+\//, 'local path (/Users/…)'],
[/[A-Z]:\\Users\\/, 'local path (C:\\Users\\…)'],
]
// Binary and generated files a path could appear in harmlessly or unavoidably.
const SKIP_EXT = /\.(png|jpe?g|webp|gif|ico|woff2?|ttf|eot|pdf|zip|gz|mbtiles|pmtiles|lock)$/i
// A tracked file may legitimately DISCUSS an absolute path — the anti-pattern log and this file's
// own header both quote one to explain the rule. An allow-list by path keeps the gate honest
// without letting the exemption spread: it names files, never patterns.
const ALLOW = new Set([
'internal/check-no-absolute-paths.mjs', // this file quotes the patterns it forbids
])
let tracked = []
try {
tracked = execSync('git ls-files', { cwd: ROOT, encoding: 'utf8' }).split('\n').filter(Boolean)
} catch (e) {
console.error(`ABSOLUTE-PATHS: FAIL — could not list tracked files (${String(e.message).slice(0, 60)}).`)
console.error(' This gate scans `git ls-files`, the set a clone actually receives.')
process.exit(1)
}
// Vacuous guard: "0 tracked files" must never read as "0 problems".
const MIN_TRACKED = 50
if (tracked.length < MIN_TRACKED) {
console.error(`ABSOLUTE-PATHS: FAIL — only ${tracked.length} tracked file(s) (expected >= ${MIN_TRACKED}).`)
console.error(' Not a git checkout, or the listing is broken. Refusing to pass vacuously.')
process.exit(1)
}
const hits = []
let scanned = 0
for (const rel of tracked) {
if (SKIP_EXT.test(rel) || ALLOW.has(rel)) continue
const abs = join(ROOT, rel)
if (!existsSync(abs)) continue // staged-deleted file
let text
try { text = readFileSync(abs, 'utf8') } catch { continue } // binary
scanned++
const lines = text.split('\n')
for (let i = 0; i < lines.length; i++) {
for (const [re, label] of PATTERNS) {
if (re.test(lines[i])) { hits.push({ rel, line: i + 1, label, text: lines[i].trim().slice(0, 100) }); break }
}
}
}
console.log(`ABSOLUTE-PATHS: ${scanned} tracked text file(s) scanned.`)
if (hits.length) {
console.error(`\nABSOLUTE-PATHS: FAIL — ${hits.length} machine-specific path(s) in tracked source:`)
for (const h of hits.slice(0, 20)) console.error(` ✗ ${h.rel}:${h.line} ${h.label}\n ${h.text}`)
if (hits.length > 20) console.error(` … +${hits.length - 20} more`)
console.error(
'\n An absolute path works perfectly for whoever wrote it and breaks only on the inheritor\'s\n' +
' machine, so no test run by its author will catch it. Use a path relative to the repo root\n' +
' (resolve(dirname(fileURLToPath(import.meta.url)), \'..\')) or an env var. If a file must QUOTE\n' +
' a path to explain something, add it to ALLOW in this gate — by filename, never by pattern.'
)
process.exit(1)
}
console.log('ABSOLUTE-PATHS: PASS — no machine-specific path in any tracked file.')

View file

@ -46,7 +46,7 @@
"check:public-hygiene": "node internal/public-hygiene-check.mjs",
"check:label-joins": "node internal/check-label-joins.mjs",
"check:dxlife": "node internal/check-dxlife-untouched.mjs",
"build:unlocked": "bun run check:template-literal && bun run clean && bun run token && for d in plugin-designs/*/; do BUNDLE=$(basename \"$d\") bun x vite build || exit 1; done && bun run tree && bun run check:drop && bun run check:parse && bun run verify && bun run check:sri && bun run check:public-hygiene && bun run check:label-joins && bun run check:descriptions && bun run gallery",
"build:unlocked": "bun run check:template-literal && bun run clean && bun run token && for d in plugin-designs/*/; do BUNDLE=$(basename \"$d\") bun x vite build || exit 1; done && bun run tree && bun run check:drop && bun run check:parse && bun run verify && bun run check:sri && bun run check:public-hygiene && bun run check:label-joins && bun run check:descriptions && bun run check:no-abs-paths && bun run gallery",
"check:template-literal": "node internal/template-literal-check.mjs",
"verify:map-paint": "node internal/verify-map-paint.mjs",
"check:sri": "node internal/sri-consistency-check.mjs",
@ -70,7 +70,8 @@
"check:settled": "node internal/settled-tree-gate.mjs",
"verify:5-caps": "node internal/verify-5-capabilities.mjs",
"verify:gate1": "node internal/acceptance/big-picture-acceptance.mjs # the Gate-1 chain end-to-end against LIVE data: header emits research-filter -> composite passes props down -> the REAL cap-2 grid re-filters -> count reflected back up. Asserts the NEGATIVE (a filter that must return 0 does) and RESET. Was an ORPHAN — present in internal/acceptance/ but invoked by NO script, so it could never report anything.",
"check:descriptions": "node internal/check-profile-descriptions.mjs # every shipped profile must say what it is (reads the BUILT manifest)"
"check:descriptions": "node internal/check-profile-descriptions.mjs # every shipped profile must say what it is (reads the BUILT manifest)",
"check:no-abs-paths": "node internal/check-no-absolute-paths.mjs # no /home/… or /Users/… in any TRACKED file (a clone must work on any machine)"
},
"devDependencies": {
"@deployment/capacitor/android": "7",

View file

@ -1 +1 @@
pid=1234302 started=2026-09-22T04:51:59Z by=lk10x
pid=1763935 started=2026-09-22T06:28:44Z by=lk10x