← back to Dw Sku Integrity
provenance-ledger-join.mjs
120 lines
#!/usr/bin/env node
// Fixture-safe join for a future read-only export of sku_repair_p4_20260826.
// No database client and no network API are imported. Inputs are local files;
// output is a local decision plan. A ledger must be explicitly attested complete
// and pass row-count + SHA-256 checks before exclusion can prove a code native.
import { createHash } from 'node:crypto';
import { readFileSync, writeFileSync } from 'node:fs';
import { MIXED_USE_MINT_PREFIXES, GREENFIELD_MINT_PREFIXES, stripUnitSuffix } from './classify.mjs';
export const REQUIRED_BACKUP_TABLE = 'sku_repair_p4_20260826';
function sha256(buffer) {
return createHash('sha256').update(buffer).digest('hex');
}
function readJsonLines(buffer, label) {
const text = buffer.toString('utf8').trim();
if (!text) return [];
return text.split('\n').map((line, index) => {
try { return JSON.parse(line); }
catch (error) { throw new Error(`${label}:${index + 1}: invalid JSON: ${error.message}`); }
});
}
function candidatePrefix(code) {
return (/^(DW[A-Z0-9]{1,6})-/i.exec(code || '') || [])[1]?.toUpperCase() || null;
}
export function loadVerifiedLedger(ledgerPath, manifestPath) {
const buffer = readFileSync(ledgerPath);
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
if (manifest.backup_table !== REQUIRED_BACKUP_TABLE) throw new Error(`wrong backup_table: ${manifest.backup_table || 'missing'}`);
if (manifest.complete !== true) throw new Error('ledger manifest must attest complete=true');
if (!Number.isSafeInteger(manifest.row_count) || manifest.row_count < 1) throw new Error('manifest row_count must be a positive integer');
const digest = sha256(buffer);
if (manifest.sha256 !== digest) throw new Error(`ledger SHA-256 mismatch: expected ${manifest.sha256}, got ${digest}`);
const rows = readJsonLines(buffer, 'ledger');
if (rows.length !== manifest.row_count) throw new Error(`ledger row_count mismatch: expected ${manifest.row_count}, got ${rows.length}`);
const assignedCodes = new Set();
for (const [index, row] of rows.entries()) {
const code = String(row.assigned_dw_sku || '').trim().toUpperCase();
if (!/^DW[A-Z0-9]{1,6}-.+/.test(code)) throw new Error(`ledger:${index + 1}: invalid assigned_dw_sku`);
if (assignedCodes.has(code)) throw new Error(`ledger:${index + 1}: duplicate assigned_dw_sku ${code}`);
assignedCodes.add(code);
}
return { manifest, assignedCodes, digest };
}
// Classes whose DW-code candidate should be checked against a verified ledger.
// A VERIFIED, complete undo ledger is the authority on provenance for EVERY
// DW-code self-copy row — the mixed-use PROVENANCE_REVIEW holds, the greenfield
// MINT_RESIDUE_RESCRAPE pre-blocks, AND the plain SELF_COPY_DW rows (whose prefix
// was neither greenfield nor mixed-use, e.g. DWPR/DWHD, but which may still hold a
// reverted-mint code). The prefix buckets are only fallback heuristics for when no
// ledger is available; once the exact reverted-mint list is attested, ledger
// membership — not the prefix — decides. (Verified 2026-08-30: greenfield DWAG rows
// carry scraper-native DWAG-376xxx in `sku`, while the reverted mints were
// DWAG-1000xx — a different range absent from these rows, so the prefix guard
// over-blocked; conversely a DWPR/DWHD row whose code IS in the ledger is residue
// even though its prefix isn't in a guard set.) Collisions/source/cork/staging/bare
// are intentionally left untouched — the ledger holds only DW mint codes.
const LEDGER_CHECKED_CLASSES = new Set(['SELF_COPY_DW', 'PROVENANCE_REVIEW', 'MINT_RESIDUE_RESCRAPE']);
export function joinPlanRows(planRows, verifiedLedger) {
const stats = { rows: planRows.length, minted_residue: 0, proven_native: 0, unchanged: 0 };
const rows = planRows.map((row) => {
if (!LEDGER_CHECKED_CLASSES.has(row.class)) { stats.unchanged += 1; return { ...row }; }
const candidate = stripUnitSuffix(String(row.sku || '').trim());
const prefix = candidatePrefix(candidate);
// No recoverable DW code (e.g. a bare MINT_RESIDUE_RESCRAPE) → can't ledger-check.
if (!prefix) { stats.unchanged += 1; return { ...row }; }
// Defensive: a PROVENANCE_REVIEW row must only ever come from a mixed-use prefix.
if (row.class === 'PROVENANCE_REVIEW' && !MIXED_USE_MINT_PREFIXES.has(prefix) && !GREENFIELD_MINT_PREFIXES.has(prefix)) {
throw new Error(`PROVENANCE_REVIEW row has no recognized mint-prefix candidate: ${row.sku || '<blank>'}`);
}
if (verifiedLedger.assignedCodes.has(candidate.toUpperCase())) {
stats.minted_residue += 1; // an exact reverted-mint code → re-scrape, never self-copy
return {
...row,
class: 'MINT_RESIDUE_RESCRAPE', candidate: null, collides: false,
group: 'rescrape_program_TK10900', provenance: 'exact_phase4_undo_ledger_match',
};
}
// Not in the ledger. A row that was HELD/blocked is now proven scraper-native;
// a row already headed to self-copy stays as-is (already correct).
if (row.class === 'SELF_COPY_DW') { stats.unchanged += 1; return { ...row }; }
stats.proven_native += 1;
return {
...row,
class: 'SELF_COPY_DW_PROVEN_NATIVE', candidate, collides: false,
group: 'recoverable_now_self_copy', provenance: 'excluded_by_complete_phase4_undo_ledger',
};
});
return { rows, stats };
}
function arg(name) {
const index = process.argv.indexOf(name);
return index >= 0 ? process.argv[index + 1] : null;
}
if (process.argv[1] && import.meta.url === new URL(`file://${process.argv[1]}`).href) {
const ledgerPath = arg('--ledger');
const manifestPath = arg('--manifest');
const planPath = arg('--plan');
const outPath = arg('--out');
if (!ledgerPath || !manifestPath || !planPath) {
throw new Error('usage: node provenance-ledger-join.mjs --ledger ledger.jsonl --manifest manifest.json --plan plan.jsonl [--out joined.jsonl]');
}
const ledger = loadVerifiedLedger(ledgerPath, manifestPath);
const planRows = readJsonLines(readFileSync(planPath), 'plan');
const joined = joinPlanRows(planRows, ledger);
if (outPath) writeFileSync(outPath, joined.rows.map(JSON.stringify).join('\n') + '\n');
process.stdout.write(JSON.stringify({ ok: true, backup_table: ledger.manifest.backup_table, ledger_sha256: ledger.digest, ...joined.stats, output: outPath || null }, null, 2) + '\n');
}