← 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');
}