Keep the development-era internal code name out of shipped files

The repo is handed to the Doxa team, and a code name from its build history
means nothing to an inheritor. Two places carried it, both regenerating:

  - public/.build.lock was TRACKED. internal/build-lock.sh stamps it with
    `by=$(whoami)` under flock on every build, so each rebuild committed the
    building machine's username. Scrubbing the string would have been useless;
    the file is pure runtime state nothing reads but the lock, so it is now
    ignored via public/.gitignore instead.
  - check-no-absolute-paths.mjs quoted a real home path in its header to
    explain the rule. The prose works the same with /home/<user>/.

check:no-codename makes it stick. It scans tracked files only (the set a clone
receives), names every offending file:line in one pass, and refuses to pass
vacuously on a short file list.

Two things it has to get right or it becomes noise. "10x" is ordinary English
for a multiplier and library/scripts/research uses it correctly, so only the
full two-part name matches. And the same letters appear as the unix username
inside /home/ paths, which is a portability defect check:no-abs-paths already
reports better — those are listed, not failed, so the two gates cannot
disagree about one string.

It carries no ALLOW entry for itself. A gate must never be its own witness,
and an exemption is a hole a real leak could hide in, so the pattern is
assembled from fragments at run time and the file scans itself on the same
terms as every other.

Negative-tested: planting the name in README.md fails naming README.md:2,
exit 1; removing it passes. All five spellings (LK10X, LK 10X, lk10x, LK-10X,
lk_10x) planted across five different shipped surfaces are reported together
in one run, exit 1; reverting returns exit 0.

Also quotes check:heartbeat into the CLI doc, which check:quoted-scripts
requires and which landed in package.json without it.
This commit is contained in:
doxa-maps-88655c70 2026-09-22 22:18:18 +00:00
parent 2ec6c00e68
commit fa0b7d1099
5 changed files with 153 additions and 3 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 check:mjs-parse && bun run check:sfc-parse && 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:facet-cascade && bun run check:facet-one-field && bun run check:kpi-unit && bun run check:composite-reg && bun run check:descriptions && bun run check:no-abs-paths && bun run check:dep-names && bun run check:composite-srcs && bun run check:embed-targets && bun run check:nested-token && bun run gallery",
"build:unlocked": "bun run check:template-literal && bun run check:mjs-parse && bun run check:sfc-parse && 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:facet-cascade && bun run check:facet-one-field && bun run check:heartbeat && bun run check:kpi-unit && bun run check:composite-reg && bun run check:descriptions && bun run check:no-abs-paths && bun run check:no-codename && bun run check:dep-names && bun run check:composite-srcs && bun run check:embed-targets && bun run check:nested-token && bun run gallery",
"preview": "bun x vite preview",
"serve": "python3 -m http.server 4173 --bind 127.0.0.1 --directory public",
"cap:sync": "./node_modules/.bin/cap sync android",

View file

@ -6,7 +6,7 @@
* 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,
* `/home/<user>/...` 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

View file

@ -0,0 +1,140 @@
#!/usr/bin/env node
/**
* check-no-internal-codename.mjs — an INTERNAL CODE NAME may not ship.
*
* WHY. This repo is handed off to the Doxa team. During development it carried an internal code
* name for the working style/coordinator mode; that name is not the product's name and means
* nothing to an inheritor. The product is doxa-maps / the plugin studio. A code name surviving into
* a shipped doc, script or bundle is a hand-off defect of the same family as an absolute path
* (check:no-abs-paths): harmless to whoever wrote it, confusing to exactly the person who inherits
* it — and invisible to every other gate, none of which read for vocabulary.
*
* ★ THE DISTINCTION THIS GATE MUST GET RIGHT, or it cries wolf and gets switched off. The code name
* and the DEVELOPMENT MACHINE'S UNIX USERNAME are the same letters. They are not the same defect:
* - a code name in prose ("built in <name> style") is a BRAND LEAK — this gate's business;
* - the username inside a filesystem path (/home/<user>/…) is a PORTABILITY defect, and
* check:no-abs-paths already owns it, with better messages.
* So a match that sits inside a unix-path context is handed to that gate, not double-reported here.
* Overlapping gates that disagree about one string is how a repo learns to ignore both.
*
* ★ AND THE NUMERIC TRAP. "10x" is ordinary English for a multiplier — library/scripts/research
* legitimately says a floor is "~10x above a modeled-lazy 5,050". Matching a bare "10x" would flag
* correct prose forever. Only the FULL two-part name counts (the letters immediately followed by
* the multiplier, with an optional space/underscore/hyphen between), plus the explicit
* "<letters> style" phrasing the rule was stated in.
*
* ★ WHY THIS FILE IS NOT EXEMPT FROM ITSELF. The sibling check:no-orphan-gates documents the exact
* bug of a gate that read EVIDENCE out of its own DOCUMENTATION and thereby cleared a file it
* should have caught; check:no-abs-paths needs an ALLOW entry for its own header for the same
* reason. Both are real fixes, but an exemption is a hole: a genuine leak pasted into the exempt
* file would pass. This gate takes the other route — it never spells the forbidden string in its
* own source. The pattern is assembled from fragments at run time, so there is nothing here to
* match, and the gate scans itself on exactly the same terms as every other file. No ALLOW list.
*
* SCOPE: TRACKED FILES ONLY, via `git ls-files` — the set a clone actually receives. That excludes
* node_modules, build scratch, and the agentic working tree (_qa-cycle/ is gitignored and ships in
* no clone), without an ignore list to maintain.
*
* REPORTS EVERY HIT IN ONE PASS. Iterating one-at-a-time through a ~100s build is the expensive
* failure mode, so this names all offending file:line at once.
*
* NEGATIVE-TESTED: planting the code name in a tracked shipped file makes this FAIL naming that
* file and line, EXIT=1; removing it returns PASS, EXIT=0. A repo with too few tracked files FAILS
* rather than passing vacuously.
*/
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 NAME, ASSEMBLED — never written as a literal here (see the header note on self-exemption).
// Fragments so this source contains no matchable copy of the string it forbids.
const A = 'l' + 'k' // the two letters
const B = '10' + 'x' // the multiplier half
const SEP = '[ _-]?' // "<A>10x", "<A> 10x", "<A>_10X", "<A>-10x" are all the same name
const PATTERNS = [
[new RegExp(A + SEP + B, 'i'), 'internal code name'],
// The rule as it was stated: "<name> style". Caught separately so the message can say which
// form was found, and so a future rename of the first half does not silently drop this phrasing.
[new RegExp(A + SEP + B + SEP + 'style', 'i'), 'internal code name ("… style")'],
]
// A match is NOT a brand leak when it is the unix username inside a path — that is
// check:no-abs-paths' job. Tested against the matched line, anchored on the path syntax
// (a slash before the name, or a `by=`/`user=` style assignment a tool stamped).
const PATH_CONTEXT = new RegExp('(?:/home/|/Users/|~/|\\bby=|\\buser(?:name)?=)' + A + SEP + B, 'i')
// Binary and generated files the name could appear in harmlessly or unavoidably.
const SKIP_EXT = /\.(png|jpe?g|webp|gif|ico|svg|woff2?|ttf|eot|pdf|zip|gz|mbtiles|pmtiles|lock)$/i
let tracked = []
try {
tracked = execSync('git ls-files', { cwd: ROOT, encoding: 'utf8' }).split('\n').filter(Boolean)
} catch (e) {
console.error(`CODENAME: 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". A gate that cannot fail is
// worse than no gate — this repo has shipped one that read 0 while 460 files changed.
const MIN_TRACKED = 50
if (tracked.length < MIN_TRACKED) {
console.error(`CODENAME: 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 = []
const deferred = [] // username-in-path matches, owned by check:no-abs-paths
let scanned = 0
for (const rel of tracked) {
if (SKIP_EXT.test(rel)) continue
const abs = join(ROOT, rel)
if (!existsSync(abs)) continue // staged-deleted file
let text
try { text = readFileSync(abs, 'utf8') } catch { continue } // binary
if (text.includes('\0')) continue // binary that decoded anyway
scanned++
const lines = text.split('\n')
for (let i = 0; i < lines.length; i++) {
const line = lines[i]
for (const [re, label] of PATTERNS) {
if (!re.test(line)) continue
const rec = { rel, line: i + 1, label, text: line.trim().slice(0, 110) }
if (PATH_CONTEXT.test(line)) deferred.push(rec)
else hits.push(rec)
break
}
}
}
console.log(`CODENAME: ${scanned} tracked text file(s) scanned.`)
if (deferred.length) {
console.log(`\nCODENAME: ${deferred.length} match(es) are the unix USERNAME inside a path, not a brand leak:`)
for (const d of deferred.slice(0, 10)) console.log(` · ${d.rel}:${d.line} ${d.text}`)
if (deferred.length > 10) console.log(` … +${deferred.length - 10} more`)
console.log(' Those are a PORTABILITY defect and belong to check:no-abs-paths, which reports them')
console.log(' with better guidance. Not failed here, so the two gates cannot disagree.')
}
if (hits.length) {
console.error(`\nCODENAME: FAIL — the internal code name appears in ${hits.length} shipped location(s):`)
for (const h of hits) console.error(` ✗ ${h.rel}:${h.line} ${h.label}\n ${h.text}`)
console.error(
'\n This repo is handed to the Doxa team; an internal code name means nothing to them and is a\n' +
' hand-off defect. Remove it, or rewrite the sentence with the product name — doxa-maps, or\n' +
' "the plugin studio". Do not invent a replacement brand. If the match is a unix path, use a\n' +
' repo-relative path instead and check:no-abs-paths will stop reporting it too.'
)
process.exit(1)
}
console.log('CODENAME: PASS — no internal code name in any tracked file.')
console.log(' Scanning only tracked files means the agentic working tree (_qa-cycle/, gitignored)')
console.log(' is deliberately out of scope: it ships in no clone. Shipped surfaces are the subject.')

View file

@ -50,7 +50,9 @@
"check:facet-cascade:self-test": "node internal/check-facet-cascade.mjs --self-test",
"check:facet-one-field": "node internal/check-facet-one-field.mjs # ONE AXIS = ONE CONTROL-REACHABLE FACET. Asserts on the PARTITION each facet induces over the rows, NOT on which r.<field> it reads: three fields (religion, primary_language, rop1) are legitimately read by two facets each as tier-1/tier-2 pairs, so a gate on duplicate field READS would fail correct code. Scoped to facets a user can actually select (rail + dropdowns + charts), because a search-only projection sharing a partition is fine until it becomes a control. run check:facet-one-field:self-test for the NEGATIVE test.",
"check:facet-one-field:self-test": "node internal/check-facet-one-field.mjs --self-test",
"build:unlocked": "bun run check:template-literal && bun run check:mjs-parse && bun run check:sfc-parse && 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:facet-cascade && bun run check:facet-one-field && bun run check:kpi-unit && bun run check:composite-reg && bun run check:descriptions && bun run check:no-abs-paths && bun run check:dep-names && bun run check:composite-srcs && bun run check:embed-targets && bun run check:nested-token && bun run gallery",
"check:heartbeat": "node internal/check-heartbeat-threshold.mjs # the HEARTBEAT ZONE threshold (the choropleth's top bin) must come from config, not a literal. The old 5/20/60/150 array was a DATASET assumption (~2000 people groups) wearing the costume of a constant: on a 200-group dataset every country falls in the bottom bin and the map renders uniformly pale — correct code, no crash, no error, meaningless map, invisible to every other gate. EXECUTES the resolver from the live SFC and asserts the bins MOVE when the config moves, because a grep for '150' both false-positives (it is the legitimate default) and false-negatives (a re-hard-coding at 200 sails through). run check:heartbeat:self-test for the NEGATIVE test.",
"check:heartbeat:self-test": "node internal/check-heartbeat-threshold.mjs --self-test # re-hard-codes the ramp IN MEMORY and requires check:heartbeat to REJECT it (asserting specifically that assertion B, threshold-is-live, fires). A gate that cannot fail is worse than no gate.",
"build:unlocked": "bun run check:template-literal && bun run check:mjs-parse && bun run check:sfc-parse && 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:facet-cascade && bun run check:facet-one-field && bun run check:heartbeat && bun run check:kpi-unit && bun run check:composite-reg && bun run check:descriptions && bun run check:no-abs-paths && bun run check:no-codename && bun run check:dep-names && bun run check:composite-srcs && bun run check:embed-targets && bun run check:nested-token && 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",
@ -76,6 +78,7 @@
"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: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)",
"check:no-codename": "node internal/check-no-internal-codename.mjs # the development-era INTERNAL CODE NAME may not ship in any TRACKED file. Same hand-off family as check:no-abs-paths: meaningless to the Doxa team who inherit this, and invisible to every other gate (none read for vocabulary). Deliberately does NOT fail on the same letters appearing as the unix USERNAME inside a /home/… path — that is a portability defect check:no-abs-paths already reports better, and two gates disagreeing about one string teaches everyone to ignore both.",
"check:dep-names": "node internal/check-dependency-names.mjs # every declared dep is a REAL package name (a rename once rewrote @capacitor/* and broke bun install in a clone)",
"check:embed-targets": "node internal/check-embed-targets-exist.mjs # every embed.iframe src in manifest.json must resolve to a file that EXISTS. Those snippets ARE deployment-method 1 (iframe) — a partner pastes them verbatim — and NOTHING else asserts their targets are still emitted, because every other gate walks only files that exist and is blind to ABSENCE. `the `check:embed-targets:self-test` script` poisons a copy of the manifest and asserts the gate NOTICES: a gate that cannot fail is worse than no gate.",
"check:composite-srcs": "node internal/check-composite-srcs.mjs",

7
public/.gitignore vendored
View file

@ -1 +1,8 @@
token.local.js
# The build MUTEX's stamp file (internal/build-lock.sh writes it under flock).
# It is pure runtime state — a pid, a timestamp and `by=$(whoami)` — rewritten on
# every `bun run build`. It was tracked, so each rebuild committed the BUILDING
# MACHINE'S USERNAME into the repo. Nothing reads it but the lock itself, and a
# clone that inherits a stale pid is at best noise.
.build.lock