§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
313 lines
12 KiB
JavaScript
313 lines
12 KiB
JavaScript
// test-fix-op-classify.test.mjs — R11 fix-operation classifier (lib/fix-op.mjs).
|
||
//
|
||
// The classifier IS the O1 driver with writes disabled: it attempts the value
|
||
// swap and checks the §4 invariant (docs/r11-tiered-fix-design.md). Anything it
|
||
// cannot prove is O3, with a TYPED abort code — the abort taxonomy is the
|
||
// pilot's measurement #3, so the codes are part of the contract, not diagnostics.
|
||
|
||
import { test } from 'node:test';
|
||
import assert from 'node:assert/strict';
|
||
|
||
import { ABORT_CODES, blockWindow, classifyFlag, extractValueTokens } from '../../scripts/kb-eval/lib/fix-op.mjs';
|
||
|
||
/** Minimal flag record per docs/r11-flag-format-2026-07.md. */
|
||
function flag(over = {}) {
|
||
return {
|
||
id: 'x/y.md#1',
|
||
judge_verdict: 'not_grounded',
|
||
rule: '',
|
||
evidence_url: 'https://learn.microsoft.com/x',
|
||
evidence_quote: 'The service supports max_docs=250 per batch.',
|
||
reason: 'Source states 250, claim states 100.',
|
||
file: 'skills/x/references/y.md',
|
||
line: 3,
|
||
claim: 'Parameteren max_docs er 100.',
|
||
disposition: 'outdated',
|
||
...over,
|
||
};
|
||
}
|
||
|
||
// The happy-path fixture is deliberately LANGUAGE-NEUTRAL (a key=value pair that
|
||
// reads the same in the file and in the English quote). That is not fixture
|
||
// convenience — it is the pilot's measured result: the corpus is Norwegian and
|
||
// the quotes are English, so a swap is only provable where the surrounding
|
||
// context is code, a URL, or a parameter key. See the CONTEXT_MISMATCH tests.
|
||
const FILE = ['# Tittel', '', '| konfig | max_docs=100 |', '| Annet | tekst |', '', 'Etterord.'].join('\n');
|
||
|
||
// ---------------------------------------------------------------- token types
|
||
|
||
test('extractValueTokens types a plain integer as number', () => {
|
||
const t = extractValueTokens('Grensen er 100 dokumenter.');
|
||
assert.deepEqual(
|
||
t.map((x) => [x.type, x.value]),
|
||
[['number', '100']],
|
||
);
|
||
});
|
||
|
||
test('extractValueTokens types percent, ISO date and version distinctly', () => {
|
||
const types = (s) => extractValueTokens(s).map((x) => x.type);
|
||
assert.deepEqual(types('Treffraten er 20 %.'), ['percent']);
|
||
assert.deepEqual(types('Gjelder fra 2026-08-02.'), ['iso_date']);
|
||
assert.deepEqual(types('Krever versjon 2.3.0 av pakken.'), ['version']);
|
||
});
|
||
|
||
test('extractValueTokens does not emit a status word as a swappable value', () => {
|
||
assert.deepEqual(extractValueTokens('Hosted agents er i Public Preview.'), []);
|
||
});
|
||
|
||
// --------------------------------------------------------------- block window
|
||
|
||
test('blockWindow spans the contiguous non-blank block containing the line', () => {
|
||
const lines = FILE.split('\n');
|
||
assert.deepEqual(blockWindow(lines, 3), { start: 3, end: 4 });
|
||
});
|
||
|
||
test('blockWindow anchored on a table header reaches the rows below it', () => {
|
||
const lines = ['| A | B |', '| --- | --- |', '| rad | 7 |', '', 'annet'];
|
||
assert.deepEqual(blockWindow(lines, 1), { start: 1, end: 3 });
|
||
});
|
||
|
||
test('blockWindow on a blank line degenerates to that line only', () => {
|
||
const lines = FILE.split('\n');
|
||
assert.deepEqual(blockWindow(lines, 2), { start: 2, end: 2 });
|
||
});
|
||
|
||
// ------------------------------------------------------------- the O1 happy path
|
||
|
||
test('classifyFlag proves a clean value swap as O1', () => {
|
||
const r = classifyFlag(flag(), FILE);
|
||
assert.equal(r.op, 'O1');
|
||
assert.equal(r.code, 'PROVEN');
|
||
assert.equal(r.proposal.line, 3);
|
||
assert.equal(r.proposal.token, '100');
|
||
assert.equal(r.proposal.replacement, '250');
|
||
assert.equal(r.proposal.before, '| konfig | max_docs=100 |');
|
||
assert.equal(r.proposal.after, '| konfig | max_docs=250 |');
|
||
});
|
||
|
||
test('the O1 proposal changes exactly one line and leaves the rest byte-identical', () => {
|
||
const r = classifyFlag(flag(), FILE);
|
||
const before = FILE.split('\n');
|
||
const after = before.slice();
|
||
after[r.proposal.line - 1] = r.proposal.after;
|
||
const changed = after.map((l, i) => (l === before[i] ? null : i)).filter((i) => i !== null);
|
||
assert.deepEqual(changed, [2]);
|
||
// §4(2): the rest of the line survives the swap byte-for-byte.
|
||
assert.equal(r.proposal.before.replace('100', ' |