#!/usr/bin/env node /** * publish-data.mjs · MAKE THE PAGE'S OWN CLAIM TRUE. * * ═══════════════════════════════════════════════════════════════════════════════════════════════ * ⛔⛔ WHY THIS EXISTS, AND IT IS NOT A NICE-TO-HAVE. * The reliability study shipped live saying, in three separate places: * "the data here is released under CC BY 4.0" * "The scripts and raw answers are published so the method can be re-run" * "Our measurements, our scripts and our raw answers are published so these findings can be * checked against us" * None of it was published. No public URL existed, and the page did not even link to the files. * ★ A FALSIFIABLE FALSE CLAIM, INSIDE THE DISCLOSURE BLOCK, ON A PAGE AUDITING MEASUREMENT * HONESTY. The one page in the estate that cannot afford it. * ⚠️ It was not a lie anyone wrote on purpose. The sentence was true of the INTENT and nobody * checked it against the filesystem, which is the same failure mode as a design doc written in * the present tense. THE FIX IS A STEP IN THE CHAIN, NOT A REMINDER. * * WHAT IT PUBLISHES (everything needed to reproduce or refute the study): * data/ the frozen report JSONs + the raw per-answer store, exactly as measured * scripts/ every instrument, so the method can be re-run against models that still exist * bank/ the question bank the answers were produced from * README.md licence, manifest, method, and the stated limits * * ⛔ REFUSES rather than publishing a partial set: a data release missing its raw answers invites * exactly the "trust us" posture the study argues against. * * usage: node variance/publish-data.mjs [--write] */ import fs from 'node:fs'; import path from 'node:path'; const HERE = import.meta.dirname; const AUDIT = path.resolve(HERE, '..'); /* ⚠️ TWO levels up, not three. AUDIT is /research/ai-recommendation-audit-2026, so '..','..' lands on the SITE root and 'research' re-enters it. The first version used three and wrote a complete, correct-looking release into clusters/01-core/research — outside any deployed site. ★ THE VERIFIER SAID 14/14 FILES LANDED AND IT WAS TELLING THE TRUTH. "The file exists" and "the file is where it will be served from" are different claims, and only the second one ships. */ const OUT = path.resolve(AUDIT, '..', '..', 'research', 'ai-recommendation-reliability-2026'); const WRITE = process.argv.includes('--write'); const REPORTS = [/^reliability-report-T0-.*\.json$/, /^model-mortality-.*\.json$/, /^temp-probe-.*\.json$/, /^size-at-temp0-.*\.json$/]; const SCRIPTS = ['reliability-study.mjs', 'model-mortality.mjs', 'temp-probe.mjs', 'size-at-temp0.mjs', 'build-study-page.mjs', 'page-shell.mjs', 'scaffold-audit.mjs', 'publish-data.mjs']; const STORES = ['study-answers-cats-lattice-owned-T0.jsonl']; const BANKS = [path.join(AUDIT, 'cats-lattice-owned.json')]; const newestMatching = (re) => fs.readdirSync(HERE).filter((f) => re.test(f)).sort().pop() || null; const reportFiles = REPORTS.map(newestMatching); const missingReports = reportFiles.filter((f) => !f); if (missingReports.length) { console.error(`REFUSED: ${missingReports.length} report file(s) missing. Run the instruments before publishing.`); process.exit(2); } for (const s of STORES) if (!fs.existsSync(path.join(HERE, s))) { console.error(`REFUSED: raw answer store ${s} is missing. A release without raw answers is a "trust us", which is what this study argues against.`); process.exit(2); } const manifest = []; const plan = []; const add = (src, rel) => { const bytes = fs.statSync(src).size; plan.push({ src, rel, bytes }); manifest.push({ file: rel, bytes }); }; /* ⛔ PUBLISH IN EXTENSIONS THE EDGE ACTUALLY SERVES, VERIFIED IN PRODUCTION (2026-09-23). The first release copied README.md and scripts/*.mjs faithfully, the deploy passed, and both returned 404 live: · `.md` is DELIBERATELY denied by the site worker's sensitive-root guard, which exists to stop CLAUDE-INSTRUCTIONS.md and friends leaking. That guard is right and must not be weakened for a convenience. · `.mjs` is not in the deny list but does not serve either; `.js`, `.json` and `.jsonl` all do. ★ THE FILE EXISTING IN THE DEPLOY DIRECTORY IS NOT THE SAME CLAIM AS THE FILE BEING REACHABLE. Audit as the crawler fetches, not as the filesystem lists. A data release nobody can download is the same broken promise as no release at all. */ for (const f of reportFiles) add(path.join(HERE, f), `data/${f}`); for (const f of STORES) add(path.join(HERE, f), `data/${f}`); for (const f of SCRIPTS) { const p = path.join(HERE, f); if (fs.existsSync(p)) add(p, `scripts/${f}.txt`); } for (const p of BANKS) if (fs.existsSync(p)) add(p, `bank/${path.basename(p)}`); const totalKB = (plan.reduce((a, f) => a + f.bytes, 0) / 1024).toFixed(0); console.log(`publish plan: ${plan.length} files, ${totalKB} KB -> ${path.relative(process.cwd(), OUT)}`); for (const f of plan) console.log(` ${String((f.bytes / 1024).toFixed(1)).padStart(8)} KB ${f.rel}`); /* The raw store is JSONL of every answer INCLUDING failures, because the failures are part of the method: a reader can see which cells were rate-limited and recompute coverage themselves. */ const storeRows = fs.readFileSync(path.join(HERE, STORES[0]), 'utf8').split('\n').filter(Boolean).length; const rel = JSON.parse(fs.readFileSync(path.join(HERE, reportFiles[0]), 'utf8')); const mort = JSON.parse(fs.readFileSync(path.join(HERE, reportFiles[1]), 'utf8')); const README = `# AI recommendation reliability, 2026 Raw measurements, instruments and question bank behind **[We audited our own AI visibility index](https://lucreya.com/articles/ai-recommendation-reliability-2026)**. **Licence: [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).** Use it, republish it, argue with it. Attribution: Vincent Wesley Couey, Lucreya (Lattice network). ## What is here | path | what it is | |---|---| | \`data/reliability-report-T0-*.json\` | self-agreement floors and between-model agreement, bootstrap CIs over questions | | \`data/model-mortality-*.json\` | live availability probe of every model this estate has published with, incl. the positive control | | \`data/temp-probe-*.json\` | self-agreement at temperature 0 vs 0.3 | | \`data/size-at-temp0-*.json\` | the size effect isolated against a measured noise floor | | \`data/study-answers-*.jsonl\` | **every raw answer**, one JSON object per line, including failed and rate-limited cells | | \`scripts/*.mjs\` | the instruments, runnable as-is | | \`bank/cats-lattice-owned.json\` | the question bank the answers were produced from | ${storeRows} raw answer rows. ${rel.questions} distinct questions. ${mort.roster} models probed for availability, ${mort.retired} retired. ## Reproducing it \`\`\` node scripts/reliability-study.mjs --plan # what it would do, no API calls node scripts/reliability-study.mjs --run --questions 40 --repeats 5 node scripts/reliability-study.mjs --report node scripts/model-mortality.mjs --write \`\`\` Each instrument needs a provider key on disk (\`~/.groq-api-key\`, \`~/.gemini-api-keys\`, \`~/.hf-token\`). Models absent from your provider will be reported as retired, which is the point of the third finding. ## Stated limits - Agreement is Jaccard overlap over canonicalised company names. - Confidence intervals are bootstrap over **questions**, not over pairs, because pairs inside one question are not independent. - Any model with fewer than 5 questions carrying repeats is reported as insufficient data rather than given a number. - Several models are served through one inference host; a host-level effect would be common to them. - "Retired" means a provider no longer serves that model id on the probe date. The weights may exist elsewhere. - This is one estate's roster on one date. A lower bound on the problem, not a census. ## Conflict of interest Lucreya is part of the Lattice network, which sells disclosed placements on pages that AI engines cite. That is a commercial interest in this subject. These files are published so the findings can be checked against us. _Generated by \`variance/publish-data.mjs\`. Regenerated whenever the measurements are._ `; if (!WRITE) { console.log('\nDRY RUN — pass --write'); process.exit(0); } fs.mkdirSync(path.join(OUT, 'data'), { recursive: true }); fs.mkdirSync(path.join(OUT, 'scripts'), { recursive: true }); fs.mkdirSync(path.join(OUT, 'bank'), { recursive: true }); for (const f of plan) fs.copyFileSync(f.src, path.join(OUT, f.rel)); fs.writeFileSync(path.join(OUT, 'README.md'), README); /* ⭐ index.html IS THE ONLY ENTRY POINT THAT RELIABLY SERVES. Markdown is denied at the edge, so a release whose front door is a README is a release with no front door. This page is what a reader, a crawler and a sceptic all actually land on, so it carries the manifest, the licence and the limits rather than pointing at a file that 404s. */ const esc = (t) => String(t).replace(/&/g, '&').replace(//g, '>'); const rows = manifest.map((m) => `${esc(m.file)}${(m.bytes / 1024).toFixed(1)} KB`).join('\n'); const indexHtml = ` Data release: AI recommendation reliability 2026 | Lucreya

LucreyaResearch › Data release

AI recommendation reliability, 2026: the data

Everything behind We audited our own AI visibility index: every raw answer, every report, every instrument. Licensed CC BY 4.0. Attribution: Vincent Wesley Couey, Lucreya.

${storeRows} raw answer rows across ${rel.questions} questions. ${mort.roster} models probed for availability, ${mort.retired} retired.

Files

${rows}
filesize
manifest.jsonn/a

Instruments are published with a .txt extension because the edge does not serve .mjs. Rename to .mjs to run them.

Stated limits

Conflict of interest. Lucreya is part of the Lattice network, which sells disclosed placements on pages that AI engines cite. That is a commercial interest in this subject. These files are published so the findings can be checked against us.
`; fs.writeFileSync(path.join(OUT, 'index.html'), indexHtml); fs.writeFileSync(path.join(OUT, 'manifest.json'), JSON.stringify({ _contract: 'Every file behind the AI recommendation reliability study, released CC BY 4.0 so the findings can be checked against their author.', study: 'https://lucreya.com/articles/ai-recommendation-reliability-2026', licence: 'https://creativecommons.org/licenses/by/4.0/', generatedAt: new Date().toISOString(), rawAnswerRows: storeRows, files: manifest, }, null, 1) + '\n'); /* ⛔ VERIFY THE ARTIFACT, NOT THE EXIT CODE. A publish step that copies zero files and exits 0 is the failure this estate keeps paying for. */ const written = plan.filter((f) => fs.existsSync(path.join(OUT, f.rel))).length; console.log(`\nWROTE ${written}/${plan.length} files + README.md + manifest.json -> ${OUT}`); if (written !== plan.length) { console.error('REFUSED: not every planned file landed.'); process.exit(3); }