/** * Every published symbol must SAY what it is aligned on — in a machine-checkable line * beside the code, not in a document that drifts away from it. * * ── Why this is a test, and why the line lives in the source ─────────────── * The library's governing rule is that nothing may diverge from the target. Nothing in * the code recorded *what each symbol is aligned on*, and it cost twice in one week: * `docs/api-contract.md` claimed a "1:1 passthrough" that was false in two ways, and a * maintainer classified `branch-registers.readLinks` as this library's own invention when * it implements `AddLinkV0` — a type NextGraph **defines** (`engine/repo/src/types.rs`) * and constructs nowhere. Reading "nothing constructs this upstream" and concluding "so * it is ours" is inference from an ABSENT IMPLEMENTATION, which the project's own rule * forbids: a definition is a fact, its being unwired is an absence, and the absence says * nothing about the target. * * So the provenance is recorded per symbol, next to it, and this test pins it. * * ── The format ──────────────────────────────────────────────────────────── * * // @provenance kind= level=<1|2|3|none> ref= * * - **name** — the PUBLISHED name, qualified for a namespace member (`inbox.post`, * `storeRegistry.createEntityDoc`). Internal symbols carry their own name. It is in * the line so this file can map an annotation to a symbol without a TypeScript parser. * - **level** — which layer of the target answers, numbered from the bottom like the * stack: `1` the engine (`engine/`), `2` the wasm binding / `@ng-org/web` * (`sdk/js/lib-wasm`, `sdk/js/web`), `3` the JS ORM (`sdk/js/orm`). `none` only when * nothing answers. * - **ref** — `path:symbol` into `nextgraph-rs` (a SYMBOL, never a line number: line * numbers rot and three citations in `docs/api-contract.md` already had). `none` only * for `invention`. * - **kind** — from a closed set: * * | kind | meaning | * |----------------------|----------------------------------------------------------------| * | `passthrough` | upstream's own symbol, same name and shape | * | `aligned` | our name or ergonomics, upstream's semantics and model | * | `declared-not-wired` | upstream DEFINES the type or shape; nothing constructs it yet | * | `invention` | nothing at any level answers — a deliberate, documented bet | * | `divergent` | upstream does this, and we do it differently | * * ── What this test deliberately does NOT check ──────────────────────────── * That the `ref` path exists on disk. `nextgraph-rs` is a sibling checkout that may be * absent on another machine, and a test that goes red for that reason teaches nothing — * it just trains people to skip it. The claim is verified by a human reading the source; * this file pins that a claim was MADE, is well-formed, and covers the whole surface. */ import { test, expect } from "bun:test"; import * as fs from "node:fs"; import * as path from "node:path"; const SRC = path.join(import.meta.dir, "..", "src"); const KINDS = new Set(["passthrough", "aligned", "declared-not-wired", "invention", "divergent"]); const LEVELS = new Set(["1", "2", "3", "none"]); /** `path/to/file.ext:Symbol` — a path and a symbol, never a line number. */ const REF_SHAPE = /^[A-Za-z0-9_.@/-]+\.[a-z]+:[A-Za-z0-9_]+$/; interface Provenance { name: string; kind: string; level: string; ref: string; file: string; } /** * The names a module really EXPORTS — declarations and named re-exports, comments * stripped first so a name that only appears in prose (an explained removal, a * `{@link}`) is not mistaken for a live export. Same rule as `vocabulary.test.ts`, * for the same reason: a gate that says yes to a name that is not there reads as * verified while proving nothing. */ function moduleExports(file: string): Set { const out = new Set(); if (!fs.existsSync(file)) return out; const text = fs .readFileSync(file, "utf8") .replace(/\/\*[\s\S]*?\*\//g, "") .replace(/^\s*\/\/.*$/gm, ""); for (const m of text.matchAll( /^export (?:declare )?(?:async )?(?:const|function|class|interface|type) (\w+)/gm, )) { out.add(m[1]!); } for (const m of text.matchAll(/export (?:type )?\{([^}]*)\}/g)) { for (const raw of m[1]!.split(",")) { const name = raw.trim().replace(/^type /, "").split(/\s+as\s+/).pop()?.trim(); if (name) out.add(name); } } return out; } /** namespace name → the names it really carries (`export * as ns from "./x"`). */ function namespaces(): Map> { const text = fs.readFileSync(path.join(SRC, "index.ts"), "utf8"); const out = new Map>(); for (const m of text.matchAll(/export \* as (\w+) from "\.\/([^"]+)"/g)) { out.set(m[1]!, moduleExports(path.join(SRC, m[2]! + ".ts"))); } return out; } /** * Every symbol the entry publishes, as a consumer names it: bare for a direct export, * `ns.member` for a namespace member. The namespace NAMES themselves are groupings, not * symbols — a consumer never holds `docs`, it calls `docs.docCreate` — so they carry no * provenance of their own and are excluded. */ function publishedSymbols(): string[] { const ns = namespaces(); const out = new Set(); for (const name of moduleExports(path.join(SRC, "index.ts"))) { if (!ns.has(name)) out.add(name); } for (const [name, members] of ns) for (const m of members) out.add(`${name}.${m}`); return [...out].sort(); } /** Every `@provenance` line in the source tree, parsed. */ function annotations(): Provenance[] { const out: Provenance[] = []; const walk = (dir: string): void => { for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { const full = path.join(dir, entry.name); if (entry.isDirectory()) walk(full); else if (entry.name.endsWith(".ts")) { for (const line of fs.readFileSync(full, "utf8").split("\n")) { // Anchored to a REAL annotation line — a comment that IS the annotation, not // one that quotes the format. `src/index.ts` documents the shape in its header // (a `// @provenance kind= …` template nested inside a `//` block); // an unanchored match read that template as a symbol called `` with a kind // called ``, and the well-formedness check went red on the documentation // of its own format. A `<…>` name is skipped for the same reason, belt and // braces: a placeholder is never a symbol. const m = line.match( /^\s*\/\/ @provenance\s+(\S+)\s+kind=(\S+)\s+level=(\S+)\s+ref=(\S+)/, ); if (m && !m[1]!.startsWith("<")) { out.push({ name: m[1]!, kind: m[2]!, level: m[3]!, ref: m[4]!, file: path.relative(SRC, full), }); } } } } }; walk(SRC); return out; } test("every published symbol carries a provenance annotation", () => { const annotated = new Set(annotations().map((a) => a.name)); const missing = publishedSymbols().filter((s) => !annotated.has(s)); // A failure is a question, not a formality: on what does this symbol align? Read the // nextgraph-rs source and say so — including "nothing answers", which is `invention`. expect(missing).toEqual([]); }); test("no symbol is annotated twice", () => { const seen = new Map(); for (const a of annotations()) seen.set(a.name, [...(seen.get(a.name) ?? []), a.file]); const duplicated = Object.fromEntries([...seen].filter(([, files]) => files.length > 1)); // Two annotations for one name is two answers to one question, and nothing says which // is current — the drift this file exists to prevent, reintroduced inside it. expect(duplicated).toEqual({}); }); test("every provenance annotation is well-formed", () => { const malformed: string[] = []; for (const a of annotations()) { const where = `${a.file} :: ${a.name}`; if (!KINDS.has(a.kind)) malformed.push(`${where} — kind '${a.kind}' is not one of ${[...KINDS].join(" | ")}`); if (!LEVELS.has(a.level)) malformed.push(`${where} — level '${a.level}' is not 1 | 2 | 3 | none`); if (a.kind === "invention") { // Nothing answers, at any level. A ref here would name a counterpart the kind // denies exists — the two halves have to agree or the label means nothing. if (a.ref !== "none") malformed.push(`${where} — kind=invention must carry ref=none, not '${a.ref}'`); if (a.level !== "none") malformed.push(`${where} — kind=invention must carry level=none, not '${a.level}'`); } else { // Every other kind CLAIMS a counterpart, so it must cite one. if (a.ref === "none") malformed.push(`${where} — kind=${a.kind} claims a counterpart, so ref may not be 'none'`); else if (!REF_SHAPE.test(a.ref)) malformed.push(`${where} — ref '${a.ref}' is not 'path:symbol' (a symbol, never a line number)`); if (a.level === "none") malformed.push(`${where} — kind=${a.kind} claims a counterpart, so level must be 1, 2 or 3`); } } expect(malformed).toEqual([]); }); test("the published surface is annotated at every kind that claims a counterpart", () => { // Not a quota — a smoke check that the closed set is actually being USED. If every // published symbol ever lands on one kind, the vocabulary has stopped discriminating // and the annotations have become decoration. const published = new Set(publishedSymbols()); const kinds = new Set(annotations().filter((a) => published.has(a.name)).map((a) => a.kind)); expect(kinds.size).toBeGreaterThan(1); });