ktg-plugin-marketplace/scripts/check-okf-parity.test.mjs
Kjell Tore Guttormsen 6a72b26985 feat(okf): enforce §3 okf_version shape, bump convention 0.1 -> 0.2
The 2026-07-23 reservation said enforcement waits until "the emitters have
migrated". Measured rather than assumed, and the emitter set turned out to be
TWO, not three:

  okr @0059da7       okf_version: 0.1 + okf_layout: kb-layout-2026-06. Migrated.
  linkedin-studio    scaffold.ts:38 -> okf_version: 0.1. Never carried a layout
                     string; has no okf_layout and needs none.
  ms-ai-architect    NOT an emitter. Zero index.md in the repo; okf_version
                     appears in one planning doc. The status table already said
                     "designed, not built" -- only the word "emitters" implied it.

Swept the whole marketplace plus the sibling consumer repos: every live
okf_version value is 0.1. The flip is a no-op today and locks the invariant.

The gate checks SHAPE (/^\d+(\.\d+)*$/), never membership: the upstream value
set is Google's (spec §12), so a bundle targeting a newer version passes. A
membership check would be the convention claiming a set it says it does not own
-- the over-reach class this round has corrected three times. Presence stays
unenforced (reported, not failed) as a separate §3 MUST.

Convention bumped 0.1 -> 0.2 by this log's own criterion: the set of conforming
bundles changed (the spec explicitly said a layout string "conforms today", and
rollout rule 6 defines conformance as gate output). The §12 per-plugin re-check
is pre-measured as finding zero violations.

Also closes a parity blind spot found while landing this: check-okf-parity
compared the okf_version VALUE, which is identical exactly when two impls
disagree about whether it is acceptable -- a false "agree" on the axis the gate
exists to watch. The signature now carries a boolean shape verdict, with
red-marker-layout as the committed red proof. Its "diverge" expectation encodes
okr's lag (they still pure-echo) and flips to "agree" when they mirror it.

And a correction the round earned: portfolio-optimiser enumerated their own
bundle surface and it is four, not three. The dormant fourth
(reference_domain.py:49 -> package data at :70) has no flag form at all, so our
published claim that the re-measurement surfaced "one" unreported entry was
itself one-of-two. Sharpens the standing rule: a search shaped like one entry
type cannot see another; install-vs-fixture is one instance, not the class.

Suite 73 -> 78. coord: PO replied, okr + llm-ingestion-okf notified (the latter
under the standing promise to flag any okf-check.mjs change).
2026-07-25 20:32:30 +02:00

177 lines
8.7 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Tests for the OKF parity gate (check-okf-parity.mjs).
// Style mirrors okf-check.test.mjs: node:test, temp bundles, subprocess for the CLI
// exit contract, zero npm deps. Two layers:
// 1. Over the COMMITTED corpus — green fixtures agree, red fixtures diverge (red-proof).
// 2. Runtime-materialized meta-tests for the git/FS-hostile axes (symlink, NFC/NFD) and
// the byte-tracking red-proof, plus the graceful-skip registry seam.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import { mkdtempSync, writeFileSync, mkdirSync, rmSync, symlinkSync, readdirSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { IMPLS, evaluateBundle, runCorpus, normalizedOutcome } from './check-okf-parity.mjs';
const HERE = dirname(fileURLToPath(import.meta.url));
const RUNNER = join(HERE, 'check-okf-parity.mjs');
const CORPUS = join(HERE, '..', 'test', 'okf-parity-corpus');
const CATALOG = IMPLS.find((i) => i.name === 'catalog');
const OKR = IMPLS.find((i) => i.name === 'okr');
const GHOST = { name: 'ghost', modulePath: new URL('./does-not-exist.mjs', import.meta.url) };
function tmpRoot() {
return mkdtempSync(join(tmpdir(), 'okf-parity-'));
}
const ROOTINDEX = 'okf_version: 0.1\n\n# Bundle\n';
const TYPED = '---\ntype: Note\ntitle: t\ndescription: d\nresource: r\ntimestamp: 2026-07-23\n---\n# c\n';
// --- A1/A2: both real impls import cross-repo and yield the contract ---
test('smoke: catalog + okr both import and return the per-file contract (A1/A2)', async () => {
assert.ok(OKR, 'okr must be a registered impl');
const dir = tmpRoot();
try {
writeFileSync(join(dir, 'index.md'), ROOTINDEX);
writeFileSync(join(dir, 'c.md'), TYPED);
const cat = await normalizedOutcome(CATALOG, dir);
const okr = await normalizedOutcome(OKR, dir);
for (const o of [cat, okr]) {
assert.equal(typeof o.conceptCount, 'number');
assert.ok(Array.isArray(o.untyped));
assert.equal(o.okfVersion, '0.1');
}
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
// --- Layer 1: the committed corpus ---
test('committed corpus: green fixtures agree, red fixtures diverge (red-proof)', async () => {
const { results, failed, availableImpls } = await runCorpus(CORPUS);
assert.ok(availableImpls.length >= 2, `need >=2 impls to test parity, have: ${availableImpls.join(',')}`);
assert.deepEqual(failed, [], `no fixture may miss its expectation; failed: ${JSON.stringify(failed.map((f) => f.fixture))}`);
const byName = Object.fromEntries(results.map((r) => [r.fixture, r]));
// every green-* agrees, every red-* diverges
for (const r of results) {
if (r.fixture.startsWith('green-')) assert.equal(r.actual, 'agree', `${r.fixture} must agree`);
if (r.fixture.startsWith('red-')) assert.equal(r.actual, 'diverge', `${r.fixture} must diverge`);
assert.equal(r.status, 'PASS');
}
// the canon fixture: catalog FAIL(>=1 untyped) vs okr OK(0 untyped)
const canon = byName['red-combined'];
const cat = canon.outcomes.find((o) => o.name === 'catalog');
const okr = canon.outcomes.find((o) => o.name === 'okr');
assert.ok(cat.untyped.length >= 1, 'catalog counts BOM+innboks as untyped');
assert.equal(okr.untyped.length, 0, 'okr normalizes BOM + skips innboks -> clean');
});
test('CLI over committed corpus: exit 0 + "parity holds" + labels red-proof', () => {
const stdout = execFileSync('node', [RUNNER], { encoding: 'utf8' });
assert.match(stdout, /OK: parity holds/);
assert.match(stdout, /PASS \(red-proof\)/);
});
// --- Layer 2a: byte-tracking red-proof (the gate's redness follows the bytes) ---
test('red-proof: BOM+CRLF typed concept diverges; the same file as UTF-8/LF agrees', async () => {
const impls = [CATALOG, OKR];
const withBom = tmpRoot();
const clean = tmpRoot();
try {
writeFileSync(join(withBom, 'index.md'), ROOTINDEX);
writeFileSync(join(withBom, 'c.md'), '---\r\ntype: Note\r\n---\r\n# c\r\n'); // BOM + CRLF
const evBom = await evaluateBundle(withBom, impls);
assert.equal(evBom.diverges, true, 'BOM+CRLF must split catalog (fence miss) vs okr (normalized)');
writeFileSync(join(clean, 'index.md'), ROOTINDEX);
writeFileSync(join(clean, 'c.md'), '---\ntype: Note\n---\n# c\n'); // same concept, plain LF
const evClean = await evaluateBundle(clean, impls);
assert.equal(evClean.diverges, false, 'neutralizing the bytes makes the gate go green — redness tracks bytes');
} finally {
rmSync(withBom, { recursive: true, force: true });
rmSync(clean, { recursive: true, force: true });
}
});
// --- marker axis: §3 okf_version shape enforcement (catalog enforces, okr echoes) ---
// This axis was invisible to the gate until the signature carried the shape verdict: the
// signature compared the okf_version VALUE, which is identical across impls precisely when
// they disagree about whether it is acceptable. A false "agree" on the one axis the gate
// exists to watch.
test('marker axis: a layout string in okf_version diverges; a version-shaped one agrees', async () => {
const impls = [CATALOG, OKR];
const layout = tmpRoot();
const versioned = tmpRoot();
try {
writeFileSync(join(layout, 'index.md'), 'okf_version: kb-layout-2026-06\n\n# Bundle\n');
writeFileSync(join(layout, 'c.md'), TYPED);
const evLayout = await evaluateBundle(layout, impls);
assert.equal(evLayout.diverges, true, 'catalog enforces the shape, okr still pure-echoes -> diverge');
writeFileSync(join(versioned, 'index.md'), ROOTINDEX);
writeFileSync(join(versioned, 'c.md'), TYPED);
const evVersioned = await evaluateBundle(versioned, impls);
assert.equal(evVersioned.diverges, false, 'a version-shaped value is accepted by both -> agree');
} finally {
rmSync(layout, { recursive: true, force: true });
rmSync(versioned, { recursive: true, force: true });
}
});
// --- Layer 2b: symlink axis (runtime-materialized; must not hang/crash) ---
test('symlink axis: symlinked file + dir + loop are skipped by both, no hang, agree', async () => {
const dir = tmpRoot();
try {
writeFileSync(join(dir, 'index.md'), ROOTINDEX);
writeFileSync(join(dir, 'real.md'), TYPED);
symlinkSync(join(dir, 'real.md'), join(dir, 'link.md')); // symlinked .md
mkdirSync(join(dir, 'sub'));
symlinkSync(join(dir, 'sub'), join(dir, 'subline')); // symlinked dir
symlinkSync('.', join(dir, 'loop')); // self-loop; must not be followed
const ev = await evaluateBundle(dir, [CATALOG, OKR]);
assert.equal(ev.diverges, false, 'both skip symlinks (Dirent isFile/isDirectory false) -> agree');
assert.equal(ev.comparableCount, 2);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
// --- Layer 2c: NFC/NFD filename axis (platform-sensitive, soft — log, never hard-fail) ---
test('NFC/NFD filename axis: runs without crash; logs what the FS preserved (soft)', async () => {
const dir = tmpRoot();
try {
writeFileSync(join(dir, 'index.md'), ROOTINDEX);
writeFileSync(join(dir, 'café.md'), TYPED); // NFC é
writeFileSync(join(dir, 'café.md'), TYPED); // NFD é
const present = readdirSync(dir).filter((n) => n.startsWith('caf'));
// Soft: on APFS both may coexist (2) or normalize to one (1). Either is acceptable;
// we only require the gate to survive and both impls to see the same FS.
const ev = await evaluateBundle(dir, [CATALOG, OKR]);
assert.equal(ev.diverges, false, 'both impls read the same FS bytes -> agree (no filename normalization in either)');
console.log(`[nfc-nfd] FS preserved ${present.length} of 2 filename variants: ${present.join(', ')}`);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
// --- Layer 2d: graceful-skip registry seam (polyrepo / catalog-only CI) ---
test('graceful skip: an unavailable impl is skipped with a notice, no crash', async () => {
const dir = tmpRoot();
try {
writeFileSync(join(dir, 'index.md'), ROOTINDEX);
writeFileSync(join(dir, 'c.md'), TYPED);
const ev = await evaluateBundle(dir, [CATALOG, GHOST]);
assert.deepEqual(ev.skipped, ['ghost']);
assert.equal(ev.comparableCount, 1);
assert.equal(ev.diverges, false, '<2 comparable impls -> cannot diverge');
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('graceful skip: runCorpus with <2 available impls -> all SKIPPED, exit 0', async () => {
const { results, exitCode, availableImpls } = await runCorpus(CORPUS, [CATALOG, GHOST]);
assert.deepEqual(availableImpls, ['catalog']);
assert.ok(results.every((r) => r.status === 'SKIPPED'), 'every fixture skipped when only 1 impl is present');
assert.equal(exitCode, 0, 'a missing sibling is an environment limitation, not a parity failure');
});