§10-målingen er gjennomført mot live ledger (243 records). Ingen KB-fil er redigert og ingen ledger-record er skrevet — §8s single-writer-state er urørt. Instrumentet ER O1-driveren med writes av (scripts/kb-eval/lib/fix-op.mjs, 30 tester). Måling #1 og #3 kommer dermed ut av mekanismen som senere skal ta på korpuset, ikke ut av en proxy-heuristikk. HOVEDFUNN — §4 som skrevet er utilstrekkelig, målt: Kjørt eksakt som spesifisert slapp den gjennom 6 swaps på piloten, hvorav 4 er GALE editer (presisjon 2/6): - 30-dagers → 24-dagers (enhets-kryssing: kilden sier 24 HOURS) - 3000 req/sek → 50 (metrikk-kryssing: query-throttle vs indexing-rate) - Microsoft Agent 365 → 7 (identifikator lemlestet, «7» høstet fra «E7») - text-embedding-ada-002 → ada-2 (identifikator lemlestet) §4 binder proveniensen til verdien og formen på editen — ingenting om at de to tokenene betegner SAMME STØRRELSE. Påstanden om at invarianten er «deliberately stronger than human review at scale» holder ikke. TILLEGG: contextCorresponds() krever samme label eller samme enhet på begge sider. Bevisst leksikalsk, UTEN oversettelsestabell — «dokumenter» læres ikke å være «documents», fordi en synonymtabell innfører en ny faktakilde og er en operatørbeslutning. Konsekvensen er målt: swap er provbar praktisk talt bare der konteksten er språknøytral (URL, kodeeksempel, parameternøkkel). TALLENE: - Pilot (≥7): 24 filer / 202 flagg → O1 = 2 (1,0 %), O3 = 200 (99,0 %) - Hele korpuset: 218 filer / 776 flagg → 15 sluppet gjennom, 9 korrekte - Kun iso_date (api-version-bump) overlever hånd-verifisering: 9/9. number/version lemlester identifikatorer (AI-900 → AI-901, gpt-4o → gpt-5.1o ×2, Java-agent 3.7.5 → 3.4.0 = nedgradering) og skal IKKE påføres. - Kun 7 av 200 aborter (3,5 %) er en fiksbar engineering-gap. Mer locator- arbeid kan ikke flytte O1-tallet vesentlig. Måling #2 (R8 → O2) er IKKE besvart og kan ikke besvares maskinelt: R8 gir null O1, og hvilke av de 46 enumerasjonene som subtraherer rent avhenger av dommerens PROSA-reason. Måling #4 (review-throughput) er ikke målt — det krever menneskelige review-økter som ikke har skjedd. Begge står som ikke-målt, ikke som antatt. VIDERE FUNN: subtraksjon kan etterlate en misvisende rest (§5 sier den «cannot introduce a new error» — sant om setningen, usant om leserens slutning), og kan ødelegge sann informasjon (prebuilt-check → finnes, heter prebuilt-check.us). `disposition` er `outdated` på 202/202 og bærer null informasjon, i strid med flagg-formatspesifikasjonen. `claim` matcher fillinjen ordrett i 0 av 202. Full oppskrift og åpne operatørbeslutninger: docs/r11-pilot-results.md
175 lines
8.5 KiB
JavaScript
175 lines
8.5 KiB
JavaScript
#!/usr/bin/env node
|
|
// classify-fix-ops.mjs — R11 pilot runner (docs/r11-tiered-fix-design.md §10).
|
|
//
|
|
// Runs the fix-operation classifier over the pilot population: the files
|
|
// carrying >= 7 `not_grounded` flags, the densest available sample. Produces the
|
|
// four §10 measurements — the O1/O3 split, the R8 breakdown, the typed abort
|
|
// distribution, and the per-flag record needed to re-analyse without re-running.
|
|
//
|
|
// READ-ONLY over the corpus and the ledger. It never edits a KB file and never
|
|
// touches judge-pass-manifest.json — §8's single-writer state is untouched. The
|
|
// only write is its own report, and only with --write.
|
|
//
|
|
// Usage: node scripts/kb-eval/classify-fix-ops.mjs [--write] [--threshold N] [--examples N]
|
|
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
import { ABORT_CODES, classifyFlag } from './lib/fix-op.mjs';
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const REPO = path.resolve(__dirname, '..', '..');
|
|
const DATA = path.join(__dirname, 'data');
|
|
|
|
const argv = process.argv.slice(2);
|
|
const flagArg = (name, fallback) => {
|
|
const i = argv.indexOf(name);
|
|
return i === -1 ? fallback : Number(argv[i + 1]);
|
|
};
|
|
const THRESHOLD = flagArg('--threshold', 7);
|
|
const EXAMPLES = flagArg('--examples', 3);
|
|
|
|
const ledger = JSON.parse(fs.readFileSync(path.join(DATA, 'judge-pass-manifest.json'), 'utf8'));
|
|
|
|
const ng = (rec) => (rec.flags || []).filter((f) => f.judge_verdict === 'not_grounded');
|
|
const population = ledger.files.filter((rec) => ng(rec).length >= THRESHOLD);
|
|
|
|
// Two passes over the same population. The canonical one applies the context
|
|
// condition; the §4-only pass exists purely to MEASURE what that condition
|
|
// removes — it is never a source of proposals, because four of the six swaps it
|
|
// admits on this population are wrong edits (see lib/fix-op.mjs).
|
|
const items = [];
|
|
const s4Only = [];
|
|
for (const rec of population) {
|
|
const text = fs.readFileSync(path.join(REPO, rec.file), 'utf8');
|
|
for (const flag of ng(rec)) {
|
|
const verdict = classifyFlag(flag, text);
|
|
s4Only.push(classifyFlag(flag, text, { contextCheck: false }));
|
|
items.push({
|
|
id: flag.id,
|
|
file: flag.file,
|
|
line: flag.line,
|
|
rule: flag.rule || '(none)',
|
|
claim: flag.claim,
|
|
evidence_url: flag.evidence_url,
|
|
evidence_quote: flag.evidence_quote,
|
|
reason: flag.reason,
|
|
op: verdict.op,
|
|
code: verdict.code,
|
|
detail: verdict.detail,
|
|
proposal: verdict.proposal,
|
|
});
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------ measurements
|
|
|
|
const tally = (rows, key) =>
|
|
rows.reduce((acc, r) => {
|
|
const k = typeof key === 'function' ? key(r) : r[key];
|
|
acc[k] = (acc[k] || 0) + 1;
|
|
return acc;
|
|
}, {});
|
|
|
|
const o1 = items.filter((i) => i.op === 'O1');
|
|
const o3 = items.filter((i) => i.op === 'O3');
|
|
const byCode = tally(o3, 'code');
|
|
const byRule = tally(items, 'rule');
|
|
const r8 = items.filter((i) => i.rule === 'R8');
|
|
|
|
// LOCATOR_MISS / LOCATOR_AMBIGUOUS are a FIXABLE engineering gap (the locator did
|
|
// not find the value the claim asserts). Every other abort is intrinsic to the
|
|
// flag: no swappable value, no same-type replacement in the cited quote, or a
|
|
// claim that is not a value swap at all. The distinction is what tells the
|
|
// operator whether more engineering would move the O1 number.
|
|
const LOCATOR_CODES = new Set([ABORT_CODES.LOCATOR_MISS, ABORT_CODES.LOCATOR_AMBIGUOUS]);
|
|
const locatorAborts = o3.filter((i) => LOCATOR_CODES.has(i.code)).length;
|
|
|
|
const s4O1 = s4Only.filter((v) => v.op === 'O1').length;
|
|
|
|
// O1 precision is NOT uniform across token types, and this split is the pilot's
|
|
// operational conclusion. Hand-verified over the whole not_grounded population:
|
|
// every iso_date swap is an `api-version=` bump in a URL or code sample and all
|
|
// were correct; the number/version swaps mutilated identifiers instead
|
|
// ("AI-900" -> "AI-901", "gpt-4o" -> "gpt-5.1o" twice, a Java agent DOWNgrade),
|
|
// because a matching identifier prefix ("AI-", "gpt-") satisfies the context
|
|
// condition while the digit is part of a name rather than a quantity.
|
|
const O1_HAND_VERIFIED_TYPES = new Set(['iso_date']);
|
|
const byType = tally(o1, (i) => i.proposal.type);
|
|
const recommended = o1.filter((i) => O1_HAND_VERIFIED_TYPES.has(i.proposal.type));
|
|
const pct = (n) => `${((n / items.length) * 100).toFixed(1)} %`;
|
|
|
|
const report = {
|
|
_meta: {
|
|
purpose:
|
|
'R11 pilot measurement (§10): fix-operation classification over the densest not_grounded sample. Read-only — no KB file and no ledger record was written.',
|
|
contract: 'docs/r11-tiered-fix-design.md §3/§4/§10',
|
|
classifier: 'scripts/kb-eval/lib/fix-op.mjs (the O1 driver with writes disabled)',
|
|
ledger: 'scripts/kb-eval/data/judge-pass-manifest.json',
|
|
ledger_records: ledger.files.length,
|
|
threshold: `not_grounded >= ${THRESHOLD} (source_silent excluded, per §10)`,
|
|
generated_from: 'ledger snapshot at run time — counts are re-derived, never read from a plan',
|
|
disclaimer_two_202s:
|
|
"This population is 202 flags. §3's '202 flags whose claim and quote contain a numeric token' is a DIFFERENT 202, measured over the full 712-flag population. Do not conflate them.",
|
|
},
|
|
population: { files: population.length, flags: items.length },
|
|
s4_as_written: {
|
|
O1: s4O1,
|
|
note:
|
|
'What §4 exactly as written would admit. NOT a source of proposals: on the >=7 pilot all 6 were hand-verified and 4 were wrong edits (unit crossing, metric crossing, two mutilated identifiers) — measured precision 2/6. Runs at other thresholds carry no hand-verification.',
|
|
},
|
|
o1_by_type: byType,
|
|
o1_recommended: {
|
|
count: recommended.length,
|
|
types: [...O1_HAND_VERIFIED_TYPES],
|
|
note:
|
|
'The only O1 class that survived hand-verification: iso_date, which in this corpus is always an api-version bump inside a URL or code sample. number/version proposals are NOT safe to apply — they mutilate product, model and certification identifiers.',
|
|
},
|
|
split: { O1: o1.length, O2: 0, O3: o3.length, O2_note: 'O2 requires operator ratification (§5); until then every non-O1 item is O3 by design.' },
|
|
abort_codes: byCode,
|
|
locator_aborts: { count: locatorAborts, note: 'fixable engineering gap — every other abort is intrinsic to the flag' },
|
|
by_rule: byRule,
|
|
r8: { total: r8.length, O1: r8.filter((i) => i.op === 'O1').length, codes: tally(r8.filter((i) => i.op === 'O3'), 'code') },
|
|
items,
|
|
};
|
|
|
|
// ---------------------------------------------------------------------- output
|
|
|
|
console.log(`R11 pilot — ${population.length} files / ${items.length} not_grounded flags (threshold >= ${THRESHOLD})`);
|
|
console.log(`ledger: ${ledger.files.length} records\n`);
|
|
console.log(`O1 (provable value swap): ${o1.length} (${pct(o1.length)})`);
|
|
console.log(`O3 (human): ${o3.length} (${pct(o3.length)})`);
|
|
console.log(`O2: 0 (unratified — §5)`);
|
|
const handNote =
|
|
THRESHOLD === 7
|
|
? ' — all 6 hand-verified: 4 are wrong edits (unit crossing, metric crossing, two mutilated identifiers)'
|
|
: ' (hand-verification was done on the >=7 pilot only)';
|
|
console.log(`\n§4 as written would admit ${s4O1}${handNote}. Context condition removes ${s4O1 - o1.length}.\n`);
|
|
console.log('abort codes:');
|
|
for (const [code, n] of Object.entries(byCode).sort((a, b) => b[1] - a[1])) {
|
|
console.log(` ${code.padEnd(20)} ${String(n).padStart(4)} ${pct(n)}`);
|
|
}
|
|
console.log(`\nO1 by token type: ${JSON.stringify(byType)}`);
|
|
console.log(`O1 hand-verified-safe class (iso_date / api-version): ${recommended.length} — the rest mutilate identifiers, do NOT apply`);
|
|
console.log(`\nlocator aborts (fixable): ${locatorAborts} intrinsic aborts: ${o3.length - locatorAborts}`);
|
|
console.log(`\nrule distribution: ${JSON.stringify(byRule)}`);
|
|
console.log(`R8: ${r8.length} flags — O1 ${report.r8.O1}, aborts ${JSON.stringify(report.r8.codes)}`);
|
|
|
|
if (EXAMPLES > 0 && o1.length > 0) {
|
|
console.log(`\n--- ${Math.min(EXAMPLES, o1.length)} proven O1 proposals ---`);
|
|
for (const i of o1.slice(0, EXAMPLES)) {
|
|
console.log(`\n${i.file}:${i.proposal.line} [${i.rule}] ${i.token || i.proposal.token} -> ${i.proposal.replacement}`);
|
|
console.log(` - ${i.proposal.before}`);
|
|
console.log(` + ${i.proposal.after}`);
|
|
console.log(` quote: ${i.proposal.evidence_quote.slice(0, 160)}`);
|
|
}
|
|
}
|
|
|
|
if (argv.includes('--write')) {
|
|
const out = path.join(DATA, 'r11-pilot-classification.json');
|
|
fs.writeFileSync(out, JSON.stringify(report, null, 2) + '\n');
|
|
console.log(`\nwrote ${out}`);
|
|
} else {
|
|
console.log('\n(dry run — pass --write to persist r11-pilot-classification.json)');
|
|
}
|