diff --git a/.project/concepts/app-contract/polyfill-surface/contract_polyfill-surface.md b/.project/concepts/app-contract/polyfill-surface/contract_polyfill-surface.md index 45fa9dd..ea3b659 100644 --- a/.project/concepts/app-contract/polyfill-surface/contract_polyfill-surface.md +++ b/.project/concepts/app-contract/polyfill-surface/contract_polyfill-surface.md @@ -103,6 +103,9 @@ export async function getObjects( export type RdfOrmConfig = Scope & { where?: ...; orderBy?: ... }; // the SDK's own export function subscribeDoc(nuri: NuriLike, onChange: (r: DocChange, t: DocChangeType) => void): Unsubscribe; export function subscribeDocs(nuris: NuriLike[], onChange: (r: DocChange, t: DocChangeType) => void): Unsubscribe; +// Extracts the variant key from a DocChange - "State" / "Patch" / "TabInfo" / ... - +// a convenience over the verified payload shape, not an upstream call. +export function docChangeType(resp: DocChange): DocChangeType; // ── low-level document / SPARQL primitives ─────────────────────────────── export const docs: { diff --git a/packages/polyfill/test/vocabulary.test.ts b/packages/polyfill/test/vocabulary.test.ts index db1558e..4c1d170 100644 --- a/packages/polyfill/test/vocabulary.test.ts +++ b/packages/polyfill/test/vocabulary.test.ts @@ -324,3 +324,47 @@ test("the api-contract appendix lists exactly what the entry exports", () => { expect({ missing, extra }).toEqual({ missing: [], extra: [] }); }); +// --- the concept contract's Surface block must match the code ------------- + +/** + * `.project/concepts/app-contract/polyfill-surface/contract_polyfill-surface.md` is the + * doc a consumer's `usage_` leaf pulls against — separate from `docs/api-contract.md` + * above, and with no check of its own: `docChangeType` was published and missing from its + * `## Surface` block, and nothing here or elsewhere caught it (T01.p). Unlike the other + * doc's per-subject "### Today" blocks, this one is a SINGLE fenced block under one + * heading, so the check is simpler: every `export` it shows must actually be exported. + */ +test("every `export` shown in the concept contract's Surface block is actually exported", () => { + const md = fs.readFileSync( + path.join( + import.meta.dir, + "..", + "..", + "..", + ".project", + "concepts", + "app-contract", + "polyfill-surface", + "contract_polyfill-surface.md", + ), + "utf8", + ); + const start = md.indexOf("\n## Surface"); + const end = md.indexOf("\n## Guarantees", start); + const section = md.slice(start, end); + const published = new Set(publishedNames()); + const stale: string[] = []; + for (const block of section.matchAll(/```ts\n([\s\S]*?)```/g)) { + for (const line of block[1]!.split("\n")) { + const decl = line.match( + /^export (?:declare )?(?:async )?(?:const|function|class|interface|type) (\w+)/, + ); + const name = decl?.[1]; + if (name && !published.has(name) && !isNamespaceMember(name)) stale.push(name); + } + } + // A failure is a question, not a rename: has the symbol been removed (then say so and + // rule on why), or has the entry lost something this contract still promises? + expect([...new Set(stale)]).toEqual([]); +}); +