Move the private field-access dataset out of the repo

data-private/ was a TRACKED folder, so field-access-visas.json would ship
with the repo the moment it goes public. A tracked folder is not private.

The table generator no longer writes inside the checkout: private tables go
to DOXA_PRIVATE_DIR (a folder outside the repo) or are skipped. data-private/
is gitignored so it cannot be re-created by accident.

The file is removed from HEAD only; it is still in history (5 commits).
Publish from fresh history or scrub it before the repo goes public.
This commit is contained in:
doxa-maps-88655c70 2026-09-24 18:34:01 +00:00
parent e7f84bb84d
commit 3fd1fbcaea
6 changed files with 20 additions and 17281 deletions

4
.gitignore vendored
View file

@ -130,3 +130,7 @@ _qa-cycle/
# measurements and the one exception (previews/, which has no data/ source
# and must stay tracked).
# The tracked source for shipped assets is data/<name>.
# Private datasets never live in this repo (see DOXA_PRIVATE_DIR in
# library/scripts/research/build-tabulator-tables.mjs). Guard against re-creation.
data-private/

View file

@ -1,14 +0,0 @@
# data-private/ — NEVER published
**The law: nothing in this folder is ever copied into the web-root or any public
repo.** It exists for internal analysis only. The build's copy steps read from
`data/` — deliberately not from here.
Classification rationale (release governance): datasets whose publication could
help close doors — e.g. field-access/visa patterns — stay here. Public-good
datasets (internet gaps, gospel-resource gaps, people-group pairings) live in
`data/` and ship.
| File | Why private |
|---|---|
| `tables/field-access-visas.json` | Visa/field-access patterns per destination — publication could accelerate access closures |

File diff suppressed because it is too large Load diff

View file

@ -65,7 +65,7 @@ const LEAK = [
// ★ HARD in every mode (Driver-critical, 2026-09-24): field-access / visa data must never
// ship in ANY public tree -- it maps how workers enter closed countries. Found live-served
// before this rule existed. Filename AND content patterns, no strict-tier softening.
[/field-access-visas/, 'PRIVATE data-class: field-access/visa table (see data-private/README.md)'],
[/field-access-visas/, 'PRIVATE data-class: field-access/visa table (private tables live outside the repo, see DOXA_PRIVATE_DIR)'],
[/"visa_/, 'PRIVATE data-class: visa field in shipped data'],
]
// ★ ADDED 2026-09-24 (public-exposure audit, policies P3/P4/P7). A public tree carries

View file

@ -32,8 +32,12 @@ import { dirname, join, resolve } from 'node:path';
const __dirname = dirname(fileURLToPath(import.meta.url));
const OUT = join(__dirname, 'out');
const TABLES = join(OUT, 'tables');
// Tables that must NEVER ship publicly (see data-private/README.md).
// Tables that must NEVER ship publicly. They are never written inside this repo: set
// DOXA_PRIVATE_DIR to a folder OUTSIDE the checkout to keep them for internal analysis, or
// leave it unset and they are skipped. (They used to live in a tracked data-private/ folder;
// a tracked folder in a public repo is not private.)
const PRIVATE_TABLES = new Set(['field-access-visas']);
const PRIVATE_DIR = process.env.DOXA_PRIVATE_DIR ? resolve(process.env.DOXA_PRIVATE_DIR) : null;
const PROFILES = resolve(__dirname, '..', '..', '..', 'plugin-designs');
const read = (f) => JSON.parse(readFileSync(join(OUT, f), 'utf8'));
@ -66,13 +70,17 @@ function emit(name, rows, columns, meta, assert) {
}
}
if (assert) assert(rows);
// RELEASE GOVERNANCE: tables in PRIVATE_TABLES are written to data-private/
// (never copied to the web-root, never indexed) — see data-private/README.md.
// RELEASE GOVERNANCE: tables in PRIVATE_TABLES never land in this repo — only in
// DOXA_PRIVATE_DIR (outside the checkout), or nowhere.
if (PRIVATE_TABLES.has(name)) {
const priv = join(TABLES, '..', '..', 'data-private', 'tables');
if (!PRIVATE_DIR) {
console.log(`[tables] ${name.padEnd(26)} ${String(rows.length).padStart(5)} rows PRIVATE — skipped (set DOXA_PRIVATE_DIR to keep it outside the repo)`);
return { name, rows: rows.length, private: true };
}
const priv = join(PRIVATE_DIR, 'tables');
if (!existsSync(priv)) mkdirSync(priv, { recursive: true });
writeFileSync(join(priv, `${name}.json`), JSON.stringify({ meta, columns, rows }, null, 1));
console.log(`[tables] ${name.padEnd(26)} ${String(rows.length).padStart(5)} rows PRIVATE → data-private/`);
console.log(`[tables] ${name.padEnd(26)} ${String(rows.length).padStart(5)} rows PRIVATE → ${priv}`);
return { name, rows: rows.length, private: true };
}
if (!existsSync(TABLES)) mkdirSync(TABLES, { recursive: true });
@ -1319,7 +1327,7 @@ for (const b of built) {
const app = ROUTE[b.name];
if (!app) continue;
// ★ THE LEAK THIS LOOP CAUSED (found live 2026-09-24, Driver-critical): emit() already
// respects PRIVATE_TABLES and writes those to data-private/ -- but THIS loop copied from
// respects PRIVATE_TABLES and keeps those out of the repo -- but THIS loop copied from
// out/tables into the BUNDLE's data dir without the same check, so field-access-visas.json
// landed in plugin-designs/key-cities/data/tables/, flowed into the public snapshot AND the
// served catalog, and was fetchable by anonymous clone + live 200. A privacy rule enforced

View file

@ -36,7 +36,7 @@ const TABS = [
{ id: 'languages', label: 'By Country', file: 'languages-by-country' },
{ id: 'operators', label: 'ISPs & Carriers', file: 'network-operators' },
{ id: 'messaging', label: 'Messaging Apps', file: 'messaging-channels' },
// Field-access/visa data is PRIVATE (data-private/ — release governance):
// Field-access/visa data is PRIVATE (kept outside the repo — release governance):
// publishing it could accelerate access closures. Tab removed from the
// public build; the dataset stays available for internal analysis.
]