provenance:privacy:policies
Differences
This shows you the differences between two versions of the page.
| Both sides previous revisionPrevious revision | |||
| provenance:privacy:policies [2026/09/10 18:06] – add pass-4 generic review log and the mutation-test table; document the whole-corpus lineage scan, needle specificity and the strengthened generator guards (Authored by Claude) karel.kubicek.claude | provenance:privacy:policies [2026/09/10 18:09] (current) – publish policies_table_check.mjs and its output; record the guard-floor fix (Authored by Claude) karel.kubicek.claude | ||
|---|---|---|---|
| Line 14: | Line 14: | ||
| | Page id | '' | | Page id | '' | ||
| | Models | Page, scripts and this log: Claude (Opus 5, with the 2026-09-09 draft written by the same model in an earlier session). Review layer: three '' | | Models | Page, scripts and this log: Claude (Opus 5, with the 2026-09-09 draft written by the same model in an earlier session). Review layer: three '' | ||
| - | | Scripts added | '' | + | | Scripts added | '' |
| | Write path | '' | | Write path | '' | ||
| | Accidental exposure | None. Credentials stayed in '' | | Accidental exposure | None. Credentials stayed in '' | ||
| Line 1378: | Line 1378: | ||
| print(' | print(' | ||
| print(' | print(' | ||
| + | </ | ||
| + | |||
| + | ==== The cell-by-cell table check — '' | ||
| + | |||
| + | <file javascript policies_table_check.mjs> | ||
| + | // Assert that privacy: | ||
| + | // CELL BY CELL, not merely that each numeral occurs somewhere in the output. | ||
| + | // | ||
| + | // node scripts/ | ||
| + | // | ||
| + | // WHY this exists in addition to check_page_numbers.mjs: | ||
| + | // whether every numeral on the page appears anywhere in the concatenated script | ||
| + | // output. A mutation test on 2026-09-10 changed the `llm` row's count from 12 to | ||
| + | // 77 and the guard still passed, because 77 occurs elsewhere in the output (the | ||
| + | // PROBE size, and the public-artifacts count). A wrong figure that collides with | ||
| + | // a live one is invisible to a membership test. This one re-derives each row | ||
| + | // from the report and compares positionally. | ||
| + | // | ||
| + | // Exits non-zero on the first mismatch, printing the row it disagrees with. | ||
| + | import fs from ' | ||
| + | |||
| + | const PAGE = process.argv[2] ?? ' | ||
| + | const REPORT = process.argv[3] ?? ' | ||
| + | const SIG = process.argv[4] ?? ' | ||
| + | |||
| + | const page = fs.readFileSync(PAGE, | ||
| + | const report = fs.readFileSync(REPORT, | ||
| + | const sig = fs.readFileSync(SIG, | ||
| + | |||
| + | let failures = 0; | ||
| + | const fail = (what, expected, got) => { | ||
| + | failures += 1; | ||
| + | console.log(`MISMATCH | ||
| + | }; | ||
| + | const ok = (what) => console.log(`OK | ||
| + | |||
| + | // A page table row -> array of trimmed cells, markup stripped. | ||
| + | const cells = (line) => | ||
| + | line.replace(/ | ||
| + | .map((c) => c.replace(/ | ||
| + | |||
| + | const pageRows = (headerMatch) => { | ||
| + | const lines = page.split(' | ||
| + | const i = lines.findIndex((l) => l.startsWith(' | ||
| + | if (i < 0) throw new Error(`no page table whose header matches ${headerMatch}`); | ||
| + | const out = []; | ||
| + | for (let j = i + 1; j < lines.length && lines[j].startsWith(' | ||
| + | if (!out.length) throw new Error(`page table ${headerMatch} has no rows`); | ||
| + | return out; | ||
| + | }; | ||
| + | |||
| + | const reportSection = (marker) => { | ||
| + | const i = report.indexOf(marker); | ||
| + | if (i < 0) throw new Error(`report has no section ${JSON.stringify(marker)}`); | ||
| + | const rest = report.slice(i + marker.length); | ||
| + | // A section ends at the next "--- " subsection header or " | ||
| + | // column-rule line under each table is also dashes, so match the space. | ||
| + | const end = rest.search(/ | ||
| + | return (end < 0 ? rest : rest.slice(0, | ||
| + | }; | ||
| + | |||
| + | // ---------------------------------------------------------------- 1. by era | ||
| + | { | ||
| + | const want = new Map(); | ||
| + | for (const l of reportSection(' | ||
| + | const m = l.match(/ | ||
| + | if (m) want.set(m[1], | ||
| + | } | ||
| + | if (want.size < 5) throw new Error(' | ||
| + | const rows = pageRows(/ | ||
| + | if (rows.length !== want.size) fail(' | ||
| + | for (const r of rows) { | ||
| + | const [name, all, ...eras] = r; | ||
| + | if (!want.has(name)) { fail(`method row " | ||
| + | const w = want.get(name); | ||
| + | const got = [all, ...eras.map((e) => e.replace(' | ||
| + | const exp = [w[0], ...w.slice(1).map((x) => String(parseFloat(x)))]; | ||
| + | const norm = got.map((x) => String(parseFloat(x))); | ||
| + | if (norm.join(',' | ||
| + | } | ||
| + | ok(`method-by-era table: ${rows.length} rows`); | ||
| + | } | ||
| + | |||
| + | // ---------------------------------------------------------------- 2. by year | ||
| + | { | ||
| + | const want = new Map(); | ||
| + | for (const l of reportSection(' | ||
| + | const m = l.match(/ | ||
| + | if (m) want.set(m[1], | ||
| + | } | ||
| + | const rows = pageRows(/ | ||
| + | for (const r of rows) { | ||
| + | const year = r[0].replace(' | ||
| + | if (!want.has(year)) { fail(`year row ${year}`, '(not in the report)', | ||
| + | const w = want.get(year); | ||
| + | const got = [r[1].replace(/,/ | ||
| + | const exp = [w[0], w[1], w[2], String(parseFloat(w[3]))]; | ||
| + | if (got.map((x) => String(parseFloat(x))).join(',' | ||
| + | fail(`year row ${year}`, exp.join(' | ||
| + | } | ||
| + | } | ||
| + | if (rows.length !== want.size) fail(' | ||
| + | ok(`per-year table: ${rows.length} rows`); | ||
| + | } | ||
| + | |||
| + | // --------------------------------------------------------------- 3. by venue | ||
| + | { | ||
| + | const want = new Map(); | ||
| + | for (const l of reportSection(' | ||
| + | const m = l.match(/ | ||
| + | if (m) want.set(m[1], | ||
| + | } | ||
| + | const alias = { ' | ||
| + | const rows = pageRows(/ | ||
| + | for (const r of rows) { | ||
| + | const v = alias[r[0]] ?? r[0]; | ||
| + | if (!want.has(v)) { fail(`venue row ${r[0]}`, '(not in the report)', | ||
| + | const w = want.get(v); | ||
| + | const got = [r[1], r[2], r[3].replace(/,/ | ||
| + | const exp = [w[0], w[1], w[2], String(parseFloat(w[3]))]; | ||
| + | if (got.map((x) => String(parseFloat(x))).join(',' | ||
| + | fail(`venue row ${r[0]}`, exp.join(' | ||
| + | } | ||
| + | } | ||
| + | if (rows.length !== want.size) fail(' | ||
| + | ok(`per-venue table: ${rows.length} rows`); | ||
| + | } | ||
| + | |||
| + | // ---------------------------------------------------------- 4. Fisher' | ||
| + | { | ||
| + | // Only the POLICY rows are published; the UNION rows stay on the provenance page. | ||
| + | const want = []; | ||
| + | for (const l of sig.split(' | ||
| + | const m = l.match(/ | ||
| + | if (m) want.push({ sub: `${m[2]}/ | ||
| + | } | ||
| + | if (want.length !== 5) throw new Error(`expected 5 POLICY significance rows, parsed ${want.length}`); | ||
| + | const rows = pageRows(/ | ||
| + | if (rows.length !== want.length) fail(' | ||
| + | // The page orders rows for reading; match on the subgroup fraction, not position. | ||
| + | const bySub = new Map(want.map((w) => [w.sub, w])); | ||
| + | for (const r of rows) { | ||
| + | const sub = r[1].replace(/,/ | ||
| + | if (!bySub.has(sub)) { fail(`significance row " | ||
| + | const w = bySub.get(sub); | ||
| + | const base = r[2].replace(/,/ | ||
| + | if (base !== w.base) fail(`significance base for " | ||
| + | // p is printed on the page in scientific form with superscript digits | ||
| + | // ("2.65 x 10^-8" | ||
| + | // and compare the value, exponent included. An earlier form of this check | ||
| + | // compared only the first three digits and a mutation test on 2026-09-10 | ||
| + | // showed it could not see an exponent moved from -8 to -5. | ||
| + | const SUP = { ' | ||
| + | const pageP = (() => { | ||
| + | const s = r[3].split(' | ||
| + | const sci = s.match(/ | ||
| + | if (sci) { | ||
| + | const exp = [...sci[2]].map((c) => SUP[c] ?? c).join('' | ||
| + | return Number(sci[1]) * 10 ** Number(exp); | ||
| + | } | ||
| + | return Number(s); | ||
| + | })(); | ||
| + | const repP = Number(w.p); | ||
| + | if (!Number.isFinite(pageP)) { | ||
| + | fail(`significance p for " | ||
| + | } else if (Math.abs(pageP - repP) > Math.abs(repP) * 0.02) { | ||
| + | fail(`significance p for " | ||
| + | } | ||
| + | } | ||
| + | ok(`significance table: ${rows.length} rows`); | ||
| + | } | ||
| + | |||
| + | console.log(failures ? `\n${failures} MISMATCHES` : '\nAll four tables match the report cell by cell.' | ||
| + | process.exitCode = failures ? 1 : 0; | ||
| </ | </ | ||
| Line 1731: | Line 1905: | ||
| ('The quote check', | ('The quote check', | ||
| ('The significance test', ' | ('The significance test', ' | ||
| + | ('The cell-by-cell table check', | ||
| ('The external checks', | ('The external checks', | ||
| ('The GitHub name-search check', | ('The GitHub name-search check', | ||
| Line 1750: | Line 1925: | ||
| (' | (' | ||
| ' | ' | ||
| + | (' | ||
| + | ' | ||
| (' | (' | ||
| ' | ' | ||
| Line 1799: | Line 1976: | ||
| for _name, _path, _tail in OUTPUTS: | for _name, _path, _tail in OUTPUTS: | ||
| _body = read(_path) | _body = read(_path) | ||
| - | if len(_body.strip()) < 200: | + | |
| + | # these outputs is legitimately six lines long. The terminal-string check | ||
| + | # below is what catches truncation. | ||
| + | | ||
| sys.exit(f' | sys.exit(f' | ||
| if _tail not in _body: | if _tail not in _body: | ||
| Line 2725: | Line 2905: | ||
| "rest of corpus" | "rest of corpus" | ||
| bracketed column is the naive base rate that leaves them in. | bracketed column is the naive base rate that leaves them in. | ||
| + | </ | ||
| + | |||
| + | ==== Output of '' | ||
| + | |||
| + | <file text policies_table_check-output.txt> | ||
| + | OK method-by-era table: 11 rows | ||
| + | OK per-year table: 12 rows | ||
| + | OK per-venue table: 7 rows | ||
| + | OK significance table: 5 rows | ||
| + | |||
| + | All four tables match the report cell by cell. | ||
| </ | </ | ||
provenance/privacy/policies.1789063613.txt.gz · Last modified: by karel.kubicek.claude
