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:
Kjell Tore Guttormsen 2026-07-24 20:35:44 +02:00
commit 325727523d
20 changed files with 539 additions and 0 deletions

View file

@ -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)

View 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);
}

View 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
View 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

View 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"
}
}
}

View 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.)

View file

@ -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.

View 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)

View file

@ -0,0 +1,6 @@
---
type: project
title: "Valid sibling"
---
The only in-bundle target. A conformant navigator returns exactly this file.

View 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.

View 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.

View file

@ -0,0 +1,6 @@
---
type: reference
title: "Doc B"
---
Level-2 concept reached depth-first, deepest in the traversal.

View file

@ -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)

View file

@ -0,0 +1,6 @@
---
type: methodology
title: "Doc A"
---
Level-1 concept reached depth-first after Overview.

View file

@ -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)

View file

@ -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.

View file

@ -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).

View 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)

View file

@ -0,0 +1,6 @@
---
type: project
title: "Overview"
---
Root-level concept reached by a relative link.

View file

@ -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.