test(okf): consume nav-golden fixtures + thin STEG 0 corpus gate
Consume commons' nav-golden fixture class byte-exact from portfolio-optimiser-commons @ b641741 (nav-golden-hierarchy positive + nav-golden-escape boundary) into test/nav-golden-corpus/ and wire a thin gate scripts/check-nav-golden.mjs (+ .test.mjs, 10 tests). Scope (operator decision, thin): the catalog is the convention owner and holds no consumer (null konsument), and the shared retrieval skill is deferred (spec.md §10 Stage 3). So the gate does NOT run a read-context navigator and does NOT assert the goldens byte-exact. It asserts only what the catalog owns: each fixture bundle/ is a conformant OKF bundle under the existing §3 checkBundle (every concept typed + root okf_version), and the committed expected-read-context.md golden is present and non-empty. Byte-exact navigator conformance stays a consumer concern (okr STEG 4 / Stage 3 skill). Fixtures pinned byte-exact (.gitattributes -text); manifest carries source repo+SHA per the vendoring-provenance rule. Naming: the stale "§5 pts 1-5" read-context label maps to the settled method-spec §3 Step 1 (commons 9801d35); no open §5 dependency. commons confirmed b641741 complete + goldens derived from §3 Step 1 (via coord). Verified: check-nav-golden 10/10; gate CLI exit 0 (2 fixtures PASS); full suite 73/73 across the six test files. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NeK9hkxrU9wFPBYGYnSV1V
This commit is contained in:
parent
0bca9c7525
commit
325727523d
20 changed files with 539 additions and 0 deletions
|
|
@ -186,6 +186,26 @@ protocol; 🟢 is reserved for the independent gate-verified step (operator veri
|
|||
fact. Verified: parity 8/8, `node check-okf-parity.mjs` → exit 0 ("parity holds"); regression 33/33;
|
||||
`check-versions` 10 OK. Fixes stay **upstream-first** (patching BOM-norm into the catalog copy would just
|
||||
re-diverge in the other direction). _(catalog session.)_
|
||||
- **2026-07-24** — **STEG 0: nav-golden fixture class consumed + thin corpus gate landed**
|
||||
(`test/nav-golden-corpus/` + `scripts/check-nav-golden.mjs` + `check-nav-golden.test.mjs`). commons
|
||||
authored a `nav-golden` fixture class — each fixture is an OKF bundle paired with a committed
|
||||
byte-exact `expected-read-context.md` golden (the fasit a conformant read-context navigator, commons
|
||||
`method-spec.md §3 Step 1` + §11 Navigation boundary, must produce). **Consumed byte-exact from
|
||||
portfolio-optimiser-commons `b641741`** (`nav-golden-hierarchy` positive + `nav-golden-escape`
|
||||
boundary; `diff -r` identical; `.gitattributes -text` pins bytes; manifest carries source repo+SHA per
|
||||
vendoring rule #7). **Scope decision (operator, thin):** the catalog is the convention owner and holds
|
||||
no consumer (null konsument), and the shared retrieval skill is deferred (spec §10 Stage 3, "do not
|
||||
build before Stage 2 says so") — so the gate does **not** run a navigator and does **not** assert the
|
||||
goldens byte-exact. It asserts only what the catalog owns: each fixture `bundle/` is a conformant OKF
|
||||
bundle under the existing §3 `checkBundle` (every concept typed + root `okf_version`), and the committed
|
||||
golden is present + non-empty. Byte-exact navigator conformance stays a consumer concern (okr STEG 4 /
|
||||
the deferred Stage 3 skill). **Naming correction (relayed to + confirmed by commons via coord):** the
|
||||
earlier record's "§5 pts 1-5" read-context label was stale — the settled normative location is
|
||||
`method-spec §3 Step 1`'s five read-context bullets (commons `9801d35`); there is no open §5 dependency
|
||||
(current spec §5 = extension keys). commons confirmed both prerequisites: Q1 `b641741` is the complete
|
||||
nav-golden set (future cases additive, non-moving); Q2 the goldens derive from settled §3 Step 1.
|
||||
Verified: `check-nav-golden` 10/10; gate CLI → exit 0 (2 fixtures PASS — hierarchy 5 concepts / escape 1,
|
||||
`okf_version` 0.1, goldens present); full suite **73/73** across the six test files. _(catalog session.)_
|
||||
|
||||
## Distilled architecture notes (OKF round, 2026-07)
|
||||
|
||||
|
|
|
|||
129
scripts/check-nav-golden.mjs
Normal file
129
scripts/check-nav-golden.mjs
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
#!/usr/bin/env node
|
||||
// check-nav-golden.mjs — STEG 0 nav-golden corpus gate (thin: conformance + golden presence).
|
||||
//
|
||||
// WHY THIS EXISTS (docs/okf-second-brain/log.md, STEG 0): commons authored a "nav-golden"
|
||||
// fixture class (examples/nav-golden-* @ b641741) — each fixture is an OKF bundle paired with
|
||||
// a committed expected-read-context.md golden, the byte-exact fasit a conformant read-context
|
||||
// navigator (commons method-spec.md §3 Step 1 + §11 Navigation boundary) must produce. The
|
||||
// catalog consumes those fixtures (test/nav-golden-corpus/) so they stay alive under CI.
|
||||
//
|
||||
// SCOPE (honest, thin — by design): the catalog is the CONVENTION OWNER and holds no consumer
|
||||
// (null konsument), and the shared retrieval skill is explicitly deferred (spec.md §10 Stage 3,
|
||||
// "do not build before Stage 2 says so"). So this gate does NOT run a navigator and does NOT
|
||||
// assert the goldens byte-exact. It asserts only what the catalog owns: (1) each fixture's
|
||||
// bundle/ is a conformant OKF bundle under the existing §3 check (checkBundle: every concept
|
||||
// typed + root okf_version present), and (2) the committed golden is present and non-empty.
|
||||
// Byte-exact navigator conformance is a consumer concern (okr STEG 4 / the deferred Stage 3
|
||||
// skill), NOT catalog's — see docs/okf-second-brain/spec.md.
|
||||
//
|
||||
// The runner CHECKS; it does not author the fasit (commons owns the goldens; the catalog owns
|
||||
// the corpus axes in manifest.json). Zero npm dependencies (node: builtins).
|
||||
|
||||
import { readdirSync, existsSync, readFileSync, statSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { checkBundle } from './okf-check.mjs';
|
||||
|
||||
export const GOLDEN_FILE = 'expected-read-context.md';
|
||||
export const BUNDLE_DIR = 'bundle';
|
||||
|
||||
// Check one consumed nav-golden fixture directory. Pure over fixtureDir; no process.exit.
|
||||
// (1) <fixtureDir>/bundle/ is a conformant OKF bundle (§3): every concept file carries `type:`
|
||||
// and the root index.md carries `okf_version`.
|
||||
// (2) <fixtureDir>/expected-read-context.md exists and is non-empty (trimmed).
|
||||
// Returns { fixture, status, problems[], okf, goldenPresent, goldenNonEmpty }.
|
||||
export function checkFixture(fixtureDir, name) {
|
||||
const problems = [];
|
||||
let okf = null;
|
||||
|
||||
const bundleRoot = join(fixtureDir, BUNDLE_DIR);
|
||||
if (!existsSync(bundleRoot) || !statSync(bundleRoot).isDirectory()) {
|
||||
problems.push(`missing ${BUNDLE_DIR}/ directory`);
|
||||
} else {
|
||||
okf = checkBundle(bundleRoot);
|
||||
if (okf.missingType.length > 0) {
|
||||
problems.push(`${BUNDLE_DIR}/ has ${okf.missingType.length} concept file(s) without type: ${okf.missingType.join(', ')}`);
|
||||
}
|
||||
if (!okf.okfVersion) {
|
||||
problems.push(`${BUNDLE_DIR}/ root index.md has no okf_version`);
|
||||
}
|
||||
}
|
||||
|
||||
const goldenPath = join(fixtureDir, GOLDEN_FILE);
|
||||
const goldenPresent = existsSync(goldenPath) && statSync(goldenPath).isFile();
|
||||
let goldenNonEmpty = false;
|
||||
if (!goldenPresent) {
|
||||
problems.push(`missing golden ${GOLDEN_FILE}`);
|
||||
} else {
|
||||
goldenNonEmpty = readFileSync(goldenPath, 'utf8').trim().length > 0;
|
||||
if (!goldenNonEmpty) problems.push(`golden ${GOLDEN_FILE} is empty`);
|
||||
}
|
||||
|
||||
return {
|
||||
fixture: name ?? fixtureDir,
|
||||
status: problems.length === 0 ? 'PASS' : 'FAIL',
|
||||
problems,
|
||||
okf,
|
||||
goldenPresent,
|
||||
goldenNonEmpty,
|
||||
};
|
||||
}
|
||||
|
||||
// Run the whole nav-golden corpus against its manifest. Pure over corpusRoot; no process.exit.
|
||||
export function runCorpus(corpusRoot) {
|
||||
const manifestPath = join(corpusRoot, 'manifest.json');
|
||||
if (!existsSync(manifestPath)) {
|
||||
throw new Error(`corpus manifest not found: ${manifestPath}`);
|
||||
}
|
||||
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
||||
const fixtures = manifest.fixtures ?? {};
|
||||
const results = [];
|
||||
|
||||
for (const [name, spec] of Object.entries(fixtures)) {
|
||||
const dir = join(corpusRoot, name);
|
||||
if (!existsSync(dir) || !statSync(dir).isDirectory()) {
|
||||
results.push({ fixture: name, axis: spec.axis, status: 'MISSING', problems: ['fixture directory absent on disk'] });
|
||||
continue;
|
||||
}
|
||||
const r = checkFixture(dir, name);
|
||||
results.push({ ...r, axis: spec.axis });
|
||||
}
|
||||
|
||||
// Orphan directories present on disk but absent from the manifest (no silent coverage gaps).
|
||||
const onDisk = readdirSync(corpusRoot, { withFileTypes: true })
|
||||
.filter((e) => e.isDirectory())
|
||||
.map((e) => e.name);
|
||||
const orphans = onDisk.filter((d) => !(d in fixtures));
|
||||
|
||||
const failed = results.filter((r) => r.status === 'FAIL' || r.status === 'MISSING');
|
||||
return { results, orphans, failed, exitCode: failed.length === 0 ? 0 : 1 };
|
||||
}
|
||||
|
||||
// --- CLI ---
|
||||
const isMain = process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1];
|
||||
if (isMain) {
|
||||
const arg = process.argv[2];
|
||||
const corpusRoot = arg
|
||||
? arg
|
||||
: fileURLToPath(new URL('../test/nav-golden-corpus/', import.meta.url));
|
||||
const { results, orphans, failed, exitCode } = runCorpus(corpusRoot);
|
||||
|
||||
const out = [];
|
||||
out.push(`Nav-golden corpus gate over: ${corpusRoot}`);
|
||||
out.push('');
|
||||
for (const r of results) {
|
||||
out.push(`${r.fixture.padEnd(22)} [${(r.axis || '').padEnd(24)}] ${r.status}`);
|
||||
for (const p of r.problems ?? []) out.push(` - ${p}`);
|
||||
if (r.okf) out.push(` okf: ${r.okf.scanned} concept(s), okf_version=${r.okf.okfVersion}, golden=${r.goldenPresent ? 'present' : 'ABSENT'}`);
|
||||
}
|
||||
out.push('');
|
||||
const pass = results.filter((r) => r.status === 'PASS').length;
|
||||
out.push(`Summary: ${results.length} fixtures — ${pass} pass, ${failed.length} fail.`);
|
||||
if (orphans.length) out.push(` ! Unmanifested corpus directories (add to manifest): ${orphans.join(', ')}`);
|
||||
out.push(exitCode === 0
|
||||
? 'OK: nav-golden fixtures conform and goldens present.'
|
||||
: `FAIL: ${failed.length} fixture(s) off expectation.`);
|
||||
|
||||
process.stdout.write(`${out.join('\n')}\n`);
|
||||
process.exit(exitCode);
|
||||
}
|
||||
194
scripts/check-nav-golden.test.mjs
Normal file
194
scripts/check-nav-golden.test.mjs
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
// Tests for the STEG 0 nav-golden corpus gate (check-nav-golden.mjs).
|
||||
// Style mirrors check-okf-parity.test.mjs: node:test, temp fixtures, a subprocess for
|
||||
// the CLI exit contract, zero npm deps. Two layers:
|
||||
// 1. Over the COMMITTED corpus — both consumed fixtures conform + their goldens are present.
|
||||
// 2. Runtime-materialized meta-tests that drive each FAIL axis (untyped concept, missing
|
||||
// okf_version, absent bundle/, absent golden, empty golden) + the orphan-dir report.
|
||||
// SCOPE mirror: this gate is THIN (conformance + golden presence). It does NOT run a
|
||||
// navigator, so there is no byte-exact golden assertion here — that is a consumer concern.
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { mkdtempSync, writeFileSync, mkdirSync, rmSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { checkFixture, runCorpus, GOLDEN_FILE } from './check-nav-golden.mjs';
|
||||
|
||||
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||
const RUNNER = join(HERE, 'check-nav-golden.mjs');
|
||||
const CORPUS = join(HERE, '..', 'test', 'nav-golden-corpus');
|
||||
|
||||
function tmpRoot() {
|
||||
return mkdtempSync(join(tmpdir(), 'nav-golden-'));
|
||||
}
|
||||
|
||||
// Materialize a minimal fixture directory: <base>/<name>/{bundle/{index.md,concept.md},golden}.
|
||||
// Options flip each FAIL axis independently.
|
||||
function makeFixture(base, name, opts = {}) {
|
||||
const {
|
||||
withBundle = true,
|
||||
okfVersion = true,
|
||||
conceptTyped = true,
|
||||
golden = 'read-context body\n',
|
||||
} = opts;
|
||||
const fdir = join(base, name);
|
||||
mkdirSync(fdir, { recursive: true });
|
||||
if (withBundle) {
|
||||
const bundle = join(fdir, 'bundle');
|
||||
mkdirSync(bundle, { recursive: true });
|
||||
writeFileSync(
|
||||
join(bundle, 'index.md'),
|
||||
okfVersion ? '---\ntype: index\nokf_version: 0.1\n---\n# root\n' : '---\ntype: index\n---\n# root\n',
|
||||
);
|
||||
writeFileSync(
|
||||
join(bundle, 'concept.md'),
|
||||
conceptTyped ? '---\ntype: project\ntitle: C\n---\n# c\n' : '---\ntitle: C\n---\n# c\n',
|
||||
);
|
||||
}
|
||||
if (golden !== null) writeFileSync(join(fdir, GOLDEN_FILE), golden);
|
||||
return fdir;
|
||||
}
|
||||
|
||||
// --- Layer 1: the committed corpus (the acceptance binding for the consumed fixtures) ---
|
||||
test('committed corpus: both nav-golden fixtures conform and their goldens are present', () => {
|
||||
const { results, failed, orphans, exitCode } = runCorpus(CORPUS);
|
||||
assert.equal(exitCode, 0, `committed corpus must be green; failed: ${JSON.stringify(failed.map((f) => f.fixture))}`);
|
||||
assert.deepEqual(failed, []);
|
||||
assert.deepEqual(orphans, [], `no unmanifested corpus dirs; orphans: ${orphans.join(', ')}`);
|
||||
const byName = Object.fromEntries(results.map((r) => [r.fixture, r]));
|
||||
for (const name of ['nav-golden-hierarchy', 'nav-golden-escape']) {
|
||||
const r = byName[name];
|
||||
assert.ok(r, `${name} must be in the manifest`);
|
||||
assert.equal(r.status, 'PASS', `${name} must PASS`);
|
||||
assert.equal(r.okf.okfVersion, '0.1', `${name} bundle root must echo okf_version 0.1`);
|
||||
assert.equal(r.goldenPresent, true);
|
||||
assert.equal(r.goldenNonEmpty, true);
|
||||
assert.deepEqual(r.problems, []);
|
||||
}
|
||||
});
|
||||
|
||||
test('CLI over committed corpus: exit 0 + "OK: nav-golden" line', () => {
|
||||
const stdout = execFileSync('node', [RUNNER], { encoding: 'utf8' });
|
||||
assert.match(stdout, /OK: nav-golden fixtures conform/);
|
||||
});
|
||||
|
||||
// --- Layer 2: materialized FAIL axes (checkFixture is pure over a fixture dir) ---
|
||||
test('PASS: a well-formed fixture (typed concept + okf_version + non-empty golden)', () => {
|
||||
const base = tmpRoot();
|
||||
try {
|
||||
const fdir = makeFixture(base, 'ok');
|
||||
const r = checkFixture(fdir, 'ok');
|
||||
assert.equal(r.status, 'PASS');
|
||||
assert.deepEqual(r.problems, []);
|
||||
assert.equal(r.okf.okfVersion, '0.1');
|
||||
assert.equal(r.goldenNonEmpty, true);
|
||||
} finally {
|
||||
rmSync(base, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('FAIL: a concept file without type', () => {
|
||||
const base = tmpRoot();
|
||||
try {
|
||||
const fdir = makeFixture(base, 'untyped', { conceptTyped: false });
|
||||
const r = checkFixture(fdir, 'untyped');
|
||||
assert.equal(r.status, 'FAIL');
|
||||
assert.ok(r.problems.some((p) => /without type/.test(p)), `problems: ${r.problems.join('; ')}`);
|
||||
} finally {
|
||||
rmSync(base, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('FAIL: root index.md without okf_version', () => {
|
||||
const base = tmpRoot();
|
||||
try {
|
||||
const fdir = makeFixture(base, 'nover', { okfVersion: false });
|
||||
const r = checkFixture(fdir, 'nover');
|
||||
assert.equal(r.status, 'FAIL');
|
||||
assert.ok(r.problems.some((p) => /okf_version/.test(p)), `problems: ${r.problems.join('; ')}`);
|
||||
} finally {
|
||||
rmSync(base, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('FAIL: missing bundle/ directory', () => {
|
||||
const base = tmpRoot();
|
||||
try {
|
||||
const fdir = makeFixture(base, 'nobundle', { withBundle: false });
|
||||
const r = checkFixture(fdir, 'nobundle');
|
||||
assert.equal(r.status, 'FAIL');
|
||||
assert.ok(r.problems.some((p) => /bundle\//.test(p)), `problems: ${r.problems.join('; ')}`);
|
||||
} finally {
|
||||
rmSync(base, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('FAIL: the committed golden is absent', () => {
|
||||
const base = tmpRoot();
|
||||
try {
|
||||
const fdir = makeFixture(base, 'nogolden', { golden: null });
|
||||
const r = checkFixture(fdir, 'nogolden');
|
||||
assert.equal(r.status, 'FAIL');
|
||||
assert.equal(r.goldenPresent, false);
|
||||
assert.ok(r.problems.some((p) => new RegExp(GOLDEN_FILE).test(p)), `problems: ${r.problems.join('; ')}`);
|
||||
} finally {
|
||||
rmSync(base, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('FAIL: the golden is present but empty (whitespace only)', () => {
|
||||
const base = tmpRoot();
|
||||
try {
|
||||
const fdir = makeFixture(base, 'emptygolden', { golden: ' \n\n' });
|
||||
const r = checkFixture(fdir, 'emptygolden');
|
||||
assert.equal(r.status, 'FAIL');
|
||||
assert.equal(r.goldenPresent, true);
|
||||
assert.equal(r.goldenNonEmpty, false);
|
||||
} finally {
|
||||
rmSync(base, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
// --- Layer 2b: runCorpus over a materialized corpus (manifest + orphan report + exit code) ---
|
||||
test('runCorpus: a FAIL fixture drives exit 1; an unmanifested dir is reported as an orphan', () => {
|
||||
const base = tmpRoot();
|
||||
try {
|
||||
makeFixture(base, 'good');
|
||||
makeFixture(base, 'bad', { conceptTyped: false });
|
||||
makeFixture(base, 'stowaway'); // on disk but NOT in the manifest -> orphan
|
||||
writeFileSync(
|
||||
join(base, 'manifest.json'),
|
||||
JSON.stringify({
|
||||
fixtures: {
|
||||
good: { axis: 'a' },
|
||||
bad: { axis: 'b' },
|
||||
},
|
||||
}),
|
||||
);
|
||||
const { results, orphans, failed, exitCode } = runCorpus(base);
|
||||
assert.equal(exitCode, 1, 'a FAIL fixture must make the corpus red');
|
||||
assert.deepEqual(failed.map((f) => f.fixture).sort(), ['bad']);
|
||||
assert.deepEqual(orphans, ['stowaway']);
|
||||
const good = results.find((r) => r.fixture === 'good');
|
||||
assert.equal(good.status, 'PASS');
|
||||
} finally {
|
||||
rmSync(base, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('runCorpus: a manifested fixture absent on disk is reported MISSING (exit 1)', () => {
|
||||
const base = tmpRoot();
|
||||
try {
|
||||
makeFixture(base, 'present');
|
||||
writeFileSync(
|
||||
join(base, 'manifest.json'),
|
||||
JSON.stringify({ fixtures: { present: { axis: 'a' }, ghost: { axis: 'b' } } }),
|
||||
);
|
||||
const { failed, exitCode } = runCorpus(base);
|
||||
assert.equal(exitCode, 1);
|
||||
assert.ok(failed.some((f) => f.fixture === 'ghost' && f.status === 'MISSING'));
|
||||
} finally {
|
||||
rmSync(base, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
5
test/nav-golden-corpus/.gitattributes
vendored
Normal file
5
test/nav-golden-corpus/.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# Preserve the consumed fixtures byte-exact (mirrors test/okf-parity-corpus).
|
||||
# The expected-read-context.md goldens are byte-exact fasit from commons @ b641741;
|
||||
# a future navigator-conformance consumer may compare byte-for-byte, so git must
|
||||
# not normalize EOL/BOM on any file in this corpus.
|
||||
* -text
|
||||
20
test/nav-golden-corpus/manifest.json
Normal file
20
test/nav-golden-corpus/manifest.json
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"_doc": "STEG 0 nav-golden corpus. Fixtures consumed byte-exact from portfolio-optimiser-commons @ b641741 (examples/nav-golden-*). Gate: scripts/check-nav-golden.mjs. SCOPE (thin, by design): the catalog is the convention owner and holds no consumer (null konsument), so this gate does NOT run a read-context navigator. It keeps each consumed fixture's bundle/ under the existing §3 OKF conformance check (every concept typed + root okf_version present) and guards that the committed expected-read-context.md golden is present and non-empty. Byte-exact navigator conformance against the goldens is a consumer concern (okr STEG 4) and the deferred shared retrieval skill (spec.md §10 Stage 3), NOT catalog's. Normative navigation rules the goldens derive from: commons method-spec.md §3 Step 1 (five read-context bullets) + §11 Navigation boundary.",
|
||||
"source": {
|
||||
"repo": "portfolio-optimiser-commons",
|
||||
"commit": "b641741",
|
||||
"path": "examples/",
|
||||
"consumedBundleSubdir": "bundle",
|
||||
"golden": "expected-read-context.md"
|
||||
},
|
||||
"fixtures": {
|
||||
"nav-golden-hierarchy": {
|
||||
"axis": "valid nested navigation",
|
||||
"note": "positive golden: depth-first traversal, both link forms, recursive verdict exclusion, resolved-path dedup, cycle termination, root-only missing-index rule"
|
||||
},
|
||||
"nav-golden-escape": {
|
||||
"axis": "out-of-bundle escape",
|
||||
"note": "boundary golden: parent/deep-parent/root-relative-trap escapes all skipped, one valid in-bundle sibling returned"
|
||||
}
|
||||
}
|
||||
}
|
||||
27
test/nav-golden-corpus/nav-golden-escape/README.md
Normal file
27
test/nav-golden-corpus/nav-golden-escape/README.md
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# nav-golden-escape — negative navigation golden (the gate must be able to go red)
|
||||
|
||||
Per catalog's requirement that step 0's gate must be able to **fail** (a gate that can
|
||||
only pass proves nothing), this negative case makes a conformant navigator and a naive one
|
||||
**provably diverge**. It is the counterpart to `nav-golden-hierarchy` (the positive case).
|
||||
|
||||
`bundle/index.md` links one valid sibling and three escaping / trap targets:
|
||||
|
||||
| Link | Class | Conformant outcome |
|
||||
|---|---|---|
|
||||
| `valid.md` | in-bundle | returned |
|
||||
| `../SHOULD-NOT-BE-READ.md` | `..` escape (the decoy really exists, one level up) | skipped; decoy never read |
|
||||
| `../../../../etc/passwd` | deep `..` escape | skipped |
|
||||
| `/etc/passwd` | root-relative **trap** | skipped |
|
||||
|
||||
The trap is the load-bearing one. Under the ratified rule, a leading `/` denotes the
|
||||
**bundle root**, so `/etc/passwd` resolves to `{bundle}/etc/passwd` (no such file → skip),
|
||||
NOT the filesystem path. A naive implementation that reads `/` as filesystem-absolute opens
|
||||
the real `/etc/passwd` — a path-traversal breach. This case fails for any implementation
|
||||
that raises, reads the decoy, or leaks `/etc/passwd` content; it passes only when the
|
||||
read-context is exactly `valid.md`.
|
||||
|
||||
The malformed-target sub-class (an embedded null byte, other invalid path components) is
|
||||
covered by the method-spec §11 **Navigation boundary** seam as a unit test — a literal null
|
||||
byte does not belong in a committed text fixture. (See the relocated finding
|
||||
`docs/plan/2026-07-20-funn-portfolio-optimiser-nullbyte.local.md` for the concrete
|
||||
null-byte reproduction that motivates the seam.)
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
type: project
|
||||
title: "Decoy — MUST NOT be read"
|
||||
---
|
||||
|
||||
This file sits ONE level above `bundle/`, outside the bundle. If its text ever appears in a read-context, the navigator resolved a `..` escape and breached the bundle boundary. It MUST never be reached.
|
||||
12
test/nav-golden-corpus/nav-golden-escape/bundle/index.md
Normal file
12
test/nav-golden-corpus/nav-golden-escape/bundle/index.md
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
type: index
|
||||
okf_version: 0.1
|
||||
title: "Nav-golden — escape / boundary case"
|
||||
---
|
||||
|
||||
Nav-golden escape case. Every link but the first escapes the bundle or is a root-relative trap; a conformant navigator skips them all, reads none of them, raises nothing, and still returns the one valid sibling.
|
||||
|
||||
- [Valid sibling](valid.md)
|
||||
- [Parent escape](../SHOULD-NOT-BE-READ.md)
|
||||
- [Deep escape](../../../../etc/passwd)
|
||||
- [Root-relative trap](/etc/passwd)
|
||||
6
test/nav-golden-corpus/nav-golden-escape/bundle/valid.md
Normal file
6
test/nav-golden-corpus/nav-golden-escape/bundle/valid.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
type: project
|
||||
title: "Valid sibling"
|
||||
---
|
||||
|
||||
The only in-bundle target. A conformant navigator returns exactly this file.
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
Nav-golden escape case. Every link but the first escapes the bundle or is a root-relative trap; a conformant navigator skips them all, reads none of them, raises nothing, and still returns the one valid sibling.
|
||||
|
||||
- [Valid sibling](valid.md)
|
||||
- [Parent escape](../SHOULD-NOT-BE-READ.md)
|
||||
- [Deep escape](../../../../etc/passwd)
|
||||
- [Root-relative trap](/etc/passwd)
|
||||
|
||||
## project: Valid sibling
|
||||
|
||||
The only in-bundle target. A conformant navigator returns exactly this file.
|
||||
34
test/nav-golden-corpus/nav-golden-hierarchy/README.md
Normal file
34
test/nav-golden-corpus/nav-golden-hierarchy/README.md
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# nav-golden-hierarchy — hierarchical navigation golden
|
||||
|
||||
Fixture class **nav-golden** (commons-owned, per the D4 step-0 split): `bundle/` in,
|
||||
`expected-read-context.md` out. Unlike the ingest-golden class (ingest-spec §11,
|
||||
extraction-shaped: `manifest.json` → `expected-bundle/`), a nav-golden case is
|
||||
**bundle → read-context** — the only shape that can express the load-bearing property
|
||||
"two conformant implementations MUST produce an identical read-context from the same
|
||||
bundle" (method-spec §3 Step 1).
|
||||
|
||||
This **positive** case exercises every decision the Q3 navigation contract pins:
|
||||
|
||||
| Decision (method-spec §3 Step 1) | How this bundle exercises it |
|
||||
|---|---|
|
||||
| Hierarchy allowed; escape, not depth, forbidden | `a/`, `a/b/` are navigated; nothing escapes |
|
||||
| Link syntax: leading `/` = bundle root, else relative | `index.md`: relative `overview.md` + root-relative `/a/index.md`; `a/index.md`: relative `b/index.md` + root-relative `/overview.md` |
|
||||
| Depth-first, first-seen order | render order is `overview` → `doc-a` → `doc-b` |
|
||||
| Dedup on resolved path | `/overview.md` (from `a/`) resolves to the already-seen `overview.md` → one entry |
|
||||
| Cycle termination | `a/b/index.md` links back to `/a/index.md` (already seen) → stops |
|
||||
| One segment per level | each index links only its immediate children |
|
||||
| Missing `index.md` binds the root alone | `c/` has no `index.md` and nothing links `c/orphan.md`; it is simply unreachable, not an error |
|
||||
| Verdict exclusion = type check per reached file, recursive | `a/verdict-nested.md` is reached but excluded |
|
||||
| Flat render regardless of depth | nested concepts render as the same `## {type}: {title}` sections; no level heading; nested index bodies are navigation, not content, and are not rendered |
|
||||
|
||||
**Traversal trace:** `index.md` (its body is the leading summary) → `overview.md` →
|
||||
`/a/index.md` → `a/doc-a.md` → `a/verdict-nested.md` (reached, excluded) → `a/b/index.md`
|
||||
→ `a/b/doc-b.md` → `/a/index.md` (seen, deduped) → `/overview.md` (seen, deduped).
|
||||
Unreachable: `c/orphan.md`.
|
||||
|
||||
**Serialization** the gate compares against: the root index body verbatim, then each
|
||||
**non-index** concept as `## {type}: {title}` + blank line + the file body, sections
|
||||
separated by one blank line, file ending in exactly one trailing newline. The spec does
|
||||
not pin whitespace beyond this shape; a gate MAY compare byte-exact or after
|
||||
trailing-whitespace normalization. **Only the root index body is the summary** — nested
|
||||
index bodies (`a/index.md`, `a/b/index.md`) are navigation and do not appear.
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
type: reference
|
||||
title: "Doc B"
|
||||
---
|
||||
|
||||
Level-2 concept reached depth-first, deepest in the traversal.
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
type: index
|
||||
okf_version: 0.1
|
||||
title: "Subsection B index"
|
||||
---
|
||||
|
||||
- [Doc B](doc-b.md)
|
||||
- [Back to A](/a/index.md)
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
type: methodology
|
||||
title: "Doc A"
|
||||
---
|
||||
|
||||
Level-1 concept reached depth-first after Overview.
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
type: index
|
||||
okf_version: 0.1
|
||||
title: "Section A index"
|
||||
---
|
||||
|
||||
- [Doc A](doc-a.md)
|
||||
- [Nested verdict](verdict-nested.md)
|
||||
- [Subsection B](b/index.md)
|
||||
- [Overview again](/overview.md)
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
type: verdict
|
||||
title: "Nested verdict — must be excluded"
|
||||
decision: approved
|
||||
description: "A verdict file at a nested level. A conformant navigator reaches it (dedup marks it seen) but MUST exclude it from the read-context — recursively, not only at the root. This is the leakage surface a graph-based exclusion would miss."
|
||||
---
|
||||
|
||||
This body MUST NOT appear in the read-context.
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
type: project
|
||||
title: "Orphan — unreachable"
|
||||
---
|
||||
|
||||
Directory `c/` has no `index.md`, and nothing links to this file. A link-following navigator never reaches it; a directory-walking one wrongly would. It MUST be absent from the read-context, and its parent directory's missing `index.md` MUST NOT be an error (that rule binds the bundle root alone).
|
||||
10
test/nav-golden-corpus/nav-golden-hierarchy/bundle/index.md
Normal file
10
test/nav-golden-corpus/nav-golden-hierarchy/bundle/index.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
type: index
|
||||
okf_version: 0.1
|
||||
title: "Nav-golden — hierarchical navigation case"
|
||||
---
|
||||
|
||||
Nav-golden hierarchical case. This bundle exercises depth-first traversal, both link forms (root-relative and relative), recursive verdict exclusion, resolved-path de-duplication, cycle termination, and the root-only binding of the missing-index rule.
|
||||
|
||||
- [Overview](overview.md)
|
||||
- [Section A](/a/index.md)
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
type: project
|
||||
title: "Overview"
|
||||
---
|
||||
|
||||
Root-level concept reached by a relative link.
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
Nav-golden hierarchical case. This bundle exercises depth-first traversal, both link forms (root-relative and relative), recursive verdict exclusion, resolved-path de-duplication, cycle termination, and the root-only binding of the missing-index rule.
|
||||
|
||||
- [Overview](overview.md)
|
||||
- [Section A](/a/index.md)
|
||||
|
||||
## project: Overview
|
||||
|
||||
Root-level concept reached by a relative link.
|
||||
|
||||
## methodology: Doc A
|
||||
|
||||
Level-1 concept reached depth-first after Overview.
|
||||
|
||||
## reference: Doc B
|
||||
|
||||
Level-2 concept reached depth-first, deepest in the traversal.
|
||||
Loading…
Add table
Add a link
Reference in a new issue