@ybotok/loam 0.1.0-beta.2 → 0.1.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"likec4.js","sourceRoot":"","sources":["../../src/core/likec4.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { LikeC4 } from \"likec4\";\n\n/** A parse/validation issue reported by LikeC4. */\nexport interface LikeC4Error {\n message: string;\n line?: number;\n sourceFsPath?: string;\n}\n\n/** loam-neutral element view (flattened from the LikeC4 parsed model). */\nexport interface Elem {\n id: string;\n kind: string;\n title: string;\n description?: string;\n /**\n * The `services/<id>` directory this element stands for, from the element's\n * `metadata { service '...' }`. Absent on the elements nobody has bound —\n * see `elementService` for the fallback.\n */\n service?: string;\n tags: string[];\n}\n\n/**\n * The service directory an element stands for: an explicit\n * `metadata { service '<id>' }` binding wins, the title is the fallback.\n *\n * The fallback is what every docs repo written before the binding existed relies\n * on, and it is also the trap the binding exists to close: matching on the title\n * means renaming a box in a diagram silently unlinks it from its service, and\n * every check that joined the two just stops finding anything.\n */\nexport function elementService(e: Elem): string {\n return e.service ?? e.title;\n}\n\n/**\n * Every id an endpoint may be filed under, nearest first: `a.b.c`, `a.b`, `a`.\n *\n * A landscape does not have to draw a service as one opaque box. The moment\n * somebody models its containers — `paymentService.api`, `paymentService.worker`\n * — an edge drawn INTO a container is still an edge into the service, and every\n * join that groups by service has to know that. Resolving only the exact id is\n * what made those edges invisible: the spine check silently skipped them, and\n * the no-openapi grace treated a service with a dozen inbound container calls as\n * one nobody calls at all.\n */\nfunction ancestorIds(id: string): string[] {\n const out = [id];\n for (let dot = id.lastIndexOf(\".\"); dot !== -1; dot = id.lastIndexOf(\".\", dot - 1)) {\n out.push(id.slice(0, dot));\n }\n return out;\n}\n\n/**\n * A memoized `id -> service` resolver over one document.\n *\n * Resolution order, and why it is this order:\n *\n * 1. the nearest ancestor (the id itself first) carrying an explicit\n * `metadata { service '<id>' }` — a binding is a claim somebody wrote down,\n * so it outranks every guess;\n * 2. the nearest ancestor whose title names a REAL `services/<id>/` directory —\n * positive evidence from the filesystem, available only to callers that\n * hand in `known`;\n * 3. the element's own title, and finally the raw id — today's fallback, kept\n * LAST because it is the one that lies: it happily resolves\n * `paymentService.api` to \"api\", a service that has never existed.\n *\n * Built once per document and shared, because it is called inside loops over\n * every relationship and the walk up the ancestor chain is not free.\n */\nexport function serviceResolver(\n elements: Elem[],\n known?: ReadonlySet<string>,\n): (id: string) => string {\n const byId = new Map(elements.map((e) => [e.id, e]));\n const memo = new Map<string, string>();\n return (id: string): string => {\n const hit = memo.get(id);\n if (hit !== undefined) return hit;\n let answer: string | undefined;\n for (const candidate of ancestorIds(id)) {\n const e = byId.get(candidate);\n if (e?.service !== undefined) {\n answer = e.service;\n break;\n }\n }\n if (answer === undefined && known !== undefined) {\n for (const candidate of ancestorIds(id)) {\n const e = byId.get(candidate);\n if (e !== undefined && known.has(e.title)) {\n answer = e.title;\n break;\n }\n }\n }\n if (answer === undefined) {\n const self = byId.get(id);\n answer = self ? elementService(self) : id;\n }\n memo.set(id, answer);\n return answer;\n };\n}\n\n/**\n * The service a relationship endpoint belongs to. An id that names no element\n * resolves to itself, so a partial document degrades to the id rather than\n * throwing.\n *\n * `known` is the set of service directories that actually exist; pass it\n * wherever the caller has enumerated `services/`, so an edge into a modelled\n * container resolves to the service that owns it instead of to the container's\n * own title. Callers with a single document and no repository in hand omit it\n * and keep the pre-existing behaviour.\n */\nexport function serviceOf(elements: Elem[], id: string, known?: ReadonlySet<string>): string {\n return serviceResolver(elements, known)(id);\n}\n\n/** loam-neutral relationship view. */\nexport interface Rel {\n source: string;\n target: string;\n title?: string;\n /** OpenAPI operationId this call uses, from the relationship's `metadata { op '...' }`. */\n op?: string;\n tags: string[];\n}\n\nexport interface LoadedDoc {\n errors: LikeC4Error[];\n elements: Elem[];\n relationships: Rel[];\n}\n\n/**\n * The slice of a LikeC4 model loam reads: elements and relationships, and on\n * each of those only id/kind/title/description/tags/metadata.\n *\n * It is a type of its own, rather than an inline cast at the one call site,\n * because BOTH of LikeC4's model stages expose exactly this slice — which is\n * what makes the cheap stage a safe substitute for the expensive one. The\n * substitution is pinned in test/likec4-model-parity.test.ts, which flattens\n * both stages through `flattenModel` below and compares the results.\n */\nexport interface ReadableModel {\n elements: () => Iterable<{\n id: string;\n kind: string;\n title: string;\n description?: unknown;\n tags?: readonly string[];\n metadata?: unknown;\n }>;\n relationships: () => Iterable<{\n source: { id: string };\n target: { id: string };\n title?: string | null;\n tags?: readonly string[];\n metadata?: unknown;\n }>;\n}\n\n/** Flatten a LikeC4 model into loam's neutral `Elem`/`Rel` view. */\nexport function flattenModel(model: ReadableModel): { elements: Elem[]; relationships: Rel[] } {\n const elements: Elem[] = [...model.elements()].map((e) => ({\n id: e.id,\n kind: e.kind,\n title: e.title,\n description: descText(e.description),\n service: metaKey(e.metadata, \"service\"),\n tags: [...(e.tags ?? [])],\n }));\n const relationships: Rel[] = [...model.relationships()].map((r) => ({\n source: r.source.id,\n target: r.target.id,\n // LikeC4 reports an untitled edge as title: null — normalize to the declared `title?: string`.\n title: r.title ?? undefined,\n op: metaKey(r.metadata, \"op\"),\n tags: [...(r.tags ?? [])],\n }));\n return { elements, relationships };\n}\n\n/**\n * Load and validate a single self-contained `.likec4` document, in-process\n * (no external tool, no JVM). Returns validation errors and, if clean, the\n * flattened elements + relationships.\n */\nexport async function loadFile(path: string): Promise<LoadedDoc> {\n return loadSource(await readFile(path, \"utf8\"));\n}\n\n/**\n * `loadFile` for text that is not (yet) on disk — what the archive merge uses\n * to prove a computed landscape parses BEFORE anything is written.\n */\nexport async function loadSource(src: string): Promise<LoadedDoc> {\n // LikeC4 owns a Langium workspace and registers a process-exit listener for\n // it. Validation creates many short-lived instances, so every acquired\n // instance must be disposed even on an invalid document or a failed model\n // computation. Its logger is disabled because diagnostics are returned in\n // LoadedDoc instead of being written out-of-band to stderr.\n const likec4 = await LikeC4.fromSource(src, { logger: false });\n try {\n // Assigned, not cast: LikeC4 declares `getErrors()` as a non-nullable array\n // whose element type is structurally wider than ours, so the compiler can\n // check the shape here. The cast was the only thing standing between a\n // dependency renaming `message` and a build that still succeeded — and the\n // `?? []` it carried could never fire.\n const errors: LikeC4Error[] = likec4.getErrors();\n if (errors.length > 0) {\n return { errors, elements: [], relationships: [] };\n }\n\n // `parsedModel`, not `computedModel`: the computed stage additionally\n // computes every VIEW the document declares (and a default one when it\n // declares none), which loam never reads — it renders nothing. That work is\n // superlinear in the number of RELATIONSHIPS, so a landscape at fleet shape\n // turned `loam list` from under a second into minutes. The elements and\n // relationships below are identical either way.\n const model = (await likec4.parsedModel()) as ReadableModel;\n return { errors, ...flattenModel(model) };\n } finally {\n await likec4.dispose();\n }\n}\n\n/**\n * Read one string key out of a LikeC4 `metadata { ... }` block. Two keys carry\n * loam's spines: `op` on a relationship (the OpenAPI operationId it calls) and\n * `service` on an element (the services/<id> directory it stands for). Elements\n * with no metadata come back as `{}`, so a missing key is indistinguishable from\n * a missing block — both mean \"not bound\".\n */\nfunction metaKey(m: unknown, key: string): string | undefined {\n if (m && typeof m === \"object\") {\n const v = (m as Record<string, unknown>)[key];\n if (typeof v === \"string\") return v;\n }\n return undefined;\n}\n\n/** LikeC4 descriptions can be a string or a rich-text object ({ txt } / { text } / { md }). */\nfunction descText(d: unknown): string | undefined {\n if (typeof d === \"string\") return d;\n if (d && typeof d === \"object\") {\n const o = d as Record<string, unknown>;\n for (const key of [\"txt\", \"text\", \"md\", \"value\"]) {\n const v = o[key];\n if (typeof v === \"string\") return v;\n }\n }\n return undefined;\n}\n\n/* ------------------------------------------------------------------ */\n/* Source scanning — the text-level view the archive merge splices from */\n/* ------------------------------------------------------------------ */\n\n/** A string literal in LikeC4 source: its span in the text, and its unescaped value. */\nexport interface SourceLiteral {\n start: number;\n end: number;\n value: string;\n}\n\n/**\n * A LikeC4 source with everything that is not code blanked out: string\n * interiors and comments become spaces, newlines survive, and the result is\n * exactly as long as the input — so structure (braces, arrows, `#tags`) can be\n * read with plain string operations at offsets that are valid in the original.\n */\nexport interface MaskedSource {\n code: string;\n literals: SourceLiteral[];\n}\n\nexport function maskSource(text: string): MaskedSource {\n const out = text.split(\"\");\n const literals: SourceLiteral[] = [];\n type State = \"code\" | \"squote\" | \"dquote\" | \"lineComment\" | \"blockComment\";\n let state: State = \"code\";\n let litStart = -1;\n let lit = \"\";\n for (let i = 0; i < text.length; i += 1) {\n const ch = text[i]!;\n const next = text[i + 1];\n switch (state) {\n case \"code\":\n if (ch === \"'\" || ch === '\"') {\n state = ch === \"'\" ? \"squote\" : \"dquote\";\n litStart = i;\n lit = \"\";\n out[i] = \" \";\n } else if (ch === \"/\" && next === \"/\") {\n state = \"lineComment\";\n out[i] = \" \";\n } else if (ch === \"/\" && next === \"*\") {\n state = \"blockComment\";\n out[i] = \" \";\n }\n break;\n case \"squote\":\n case \"dquote\":\n if (ch === \"\\\\\" && i + 1 < text.length) {\n lit += text[i + 1]!;\n out[i] = \" \";\n out[i + 1] = \" \";\n i += 1;\n } else if (ch === (state === \"squote\" ? \"'\" : '\"')) {\n literals.push({ start: litStart, end: i + 1, value: lit });\n state = \"code\";\n out[i] = \" \";\n } else {\n lit += ch;\n if (ch !== \"\\n\") out[i] = \" \";\n }\n break;\n case \"lineComment\":\n if (ch === \"\\n\") state = \"code\";\n else out[i] = \" \";\n break;\n case \"blockComment\":\n if (ch === \"*\" && next === \"/\") {\n state = \"code\";\n out[i] = \" \";\n out[i + 1] = \" \";\n i += 1;\n } else if (ch !== \"\\n\") {\n out[i] = \" \";\n }\n break;\n }\n }\n return { code: out.join(\"\"), literals };\n}\n\n/** Index of the `}` matching the `{` at `open`, over MASKED code; -1 when unbalanced. */\nexport function matchBrace(code: string, open: number): number {\n let depth = 0;\n for (let i = open; i < code.length; i += 1) {\n if (code[i] === \"{\") depth += 1;\n else if (code[i] === \"}\") {\n depth -= 1;\n if (depth === 0) return i;\n }\n }\n return -1;\n}\n\n/** An element declaration located in source, under the id the computed model gives it. */\nexport interface ScannedElement {\n /** Full dotted id — `parent.child` for a nested declaration. */\n id: string;\n /** Span of the whole declaration: first head character to one past its end. */\n start: number;\n end: number;\n /** Its own body braces; -1 when the declaration has no body. */\n bodyOpen: number;\n bodyClose: number;\n /** Leading whitespace of the declaration's first line. */\n indent: string;\n}\n\n/** A relationship statement located in source, endpoints resolved to full ids (best effort). */\nexport interface ScannedRel {\n source: string;\n target: string;\n title?: string;\n op?: string;\n tags: string[];\n start: number;\n end: number;\n indent: string;\n}\n\nexport interface ScannedModel {\n /** The `model` block's own braces. */\n open: number;\n close: number;\n /** In document order. A nested declaration's span lies inside its parent's. */\n elements: ScannedElement[];\n rels: ScannedRel[];\n}\n\n/**\n * Locate every element and relationship declaration inside the top-level\n * `model { ... }` block of a LikeC4 source, with the byte range each one\n * occupies. This is the splice map `loam archive` merges the landscape with:\n * the parsed model says WHAT exists, this says WHERE its authored text is —\n * verbatim, so nothing the parser does not model (technology, style, links)\n * can be lost by rebuilding it.\n *\n * Both declaration forms are recognised — `name = kind ...` always, and\n * `kind name ...` for any kind the document's own `specification` block\n * declares. Relationship endpoints are resolved the way LikeC4 reads them:\n * innermost enclosing scope outward, then the top level, then a unique leaf\n * name anywhere; `it`/`this` (and an omitted source) mean the enclosing\n * element. A name that resolves nowhere is left as written — the caller's\n * match against the parsed model then fails loudly instead of guessing.\n *\n * Returns null when there is no model block. Callers only hand in documents\n * LikeC4 has already parsed, so the scan can trust balanced braces.\n */\nexport function scanModel(text: string): ScannedModel | null {\n const { code, literals } = maskSource(text);\n const m = /\\bmodel\\s*\\{/.exec(code);\n if (!m) return null;\n const open = m.index + m[0].length - 1;\n const close = matchBrace(code, open);\n if (close === -1) return null;\n\n // Element kinds from the specification block(s): the `<kind> <name>`\n // declaration form is recognisable only by its first token being a kind.\n const kinds = new Set<string>();\n for (const sm of code.matchAll(/\\bspecification\\s*\\{/g)) {\n const sClose = matchBrace(code, sm.index + sm[0].length - 1);\n if (sClose === -1) continue;\n for (const em of code.slice(sm.index, sClose).matchAll(/\\belement\\s+([A-Za-z_][\\w-]*)/g)) {\n kinds.add(em[1]!);\n }\n }\n\n const indentOf = (at: number): string => {\n const nl = text.lastIndexOf(\"\\n\", at - 1);\n const ws = text.slice(nl + 1, at);\n return /^[ \\t]*$/.test(ws) ? ws : \"\";\n };\n const literalIn = (from: number, to: number): SourceLiteral | undefined =>\n literals.find((l) => l.start >= from && l.end <= to);\n const keyedLiteral = (key: string, from: number, to: number): string | undefined => {\n if (from === -1) return undefined;\n const re = new RegExp(`\\\\b${key}\\\\b`, \"g\");\n re.lastIndex = from;\n const km = re.exec(code);\n if (km === null || km.index >= to) return undefined;\n return literalIn(km.index, to)?.value;\n };\n const trimmedEnd = (from: number, to: number): number => {\n let e = to;\n while (e > from && /\\s/.test(text[e - 1]!)) e -= 1;\n return e;\n };\n\n const elements: ScannedElement[] = [];\n interface RawRel {\n srcLit?: string;\n tgtLit: string;\n parent: string;\n start: number;\n end: number;\n indent: string;\n title?: string;\n op?: string;\n tags: string[];\n }\n const raw: RawRel[] = [];\n const stack: Array<{ id: string; el: ScannedElement }> = [];\n\n let i = open + 1;\n while (i < close) {\n const ch = code[i]!;\n if (/\\s/.test(ch)) {\n i += 1;\n continue;\n }\n if (ch === \"}\") {\n const frame = stack.pop();\n if (frame !== undefined) {\n frame.el.bodyClose = i;\n frame.el.end = i + 1;\n elements.push(frame.el);\n }\n i += 1;\n continue;\n }\n\n // A statement's head: up to its own `{`, the end of the line, or the\n // enclosing block's `}` on the same line — whichever comes first.\n const stmtStart = i;\n let headEnd = close;\n let opener = -1;\n for (let j = i; j < close; j += 1) {\n const c = code[j]!;\n if (c === \"\\n\" || c === \"}\") {\n headEnd = j;\n break;\n }\n if (c === \"{\") {\n opener = j;\n headEnd = j;\n break;\n }\n }\n const head = code.slice(stmtStart, headEnd);\n const parentId = stack.length > 0 ? stack[stack.length - 1]!.id : \"\";\n\n // Classify: `name = kind` first, then a relationship (the arrow cannot hide\n // in a string — the head is masked), then the `kind name` form, else a\n // property/tag line that is consumed whole, nested block included.\n let localName: string | null = null;\n const assign = /^([A-Za-z_]\\w*)\\s*=\\s*[A-Za-z_]/.exec(head);\n if (assign !== null) {\n localName = assign[1]!;\n } else if (!head.includes(\"->\")) {\n const toks = head.trim().split(/\\s+/);\n if (toks.length >= 2 && kinds.has(toks[0]!) && /^[A-Za-z_]\\w*$/.test(toks[1]!)) {\n localName = toks[1]!;\n }\n }\n\n if (localName !== null) {\n const id = parentId === \"\" ? localName : `${parentId}.${localName}`;\n const el: ScannedElement = {\n id,\n start: stmtStart,\n end: headEnd,\n bodyOpen: opener,\n bodyClose: -1,\n indent: indentOf(stmtStart),\n };\n if (opener !== -1) {\n stack.push({ id, el });\n i = opener + 1;\n } else {\n el.end = trimmedEnd(stmtStart, headEnd);\n elements.push(el);\n i = headEnd;\n }\n continue;\n }\n\n if (head.includes(\"->\")) {\n const bClose = opener === -1 ? -1 : matchBrace(code, opener);\n const stmtEnd = opener === -1 ? trimmedEnd(stmtStart, headEnd) : bClose === -1 ? close : bClose + 1;\n const am = /^([A-Za-z_][\\w.]*)?\\s*(?:-\\[[^\\]]*\\])?->\\s*([A-Za-z_][\\w.]*)/.exec(head);\n if (am !== null) {\n const arrowEnd = stmtStart + am[0].length;\n const bodyFrom = opener === -1 ? -1 : opener + 1;\n const bodyTo = opener === -1 ? -1 : stmtEnd - 1;\n const title = literalIn(arrowEnd, headEnd)?.value ?? keyedLiteral(\"title\", bodyFrom, bodyTo);\n const tags: string[] = [];\n for (const tm of code.slice(stmtStart, stmtEnd).matchAll(/#([\\w-]+)/g)) tags.push(tm[1]!);\n raw.push({\n srcLit: am[1],\n tgtLit: am[2]!,\n parent: parentId,\n start: stmtStart,\n end: stmtEnd,\n indent: indentOf(stmtStart),\n title,\n op: keyedLiteral(\"op\", bodyFrom, bodyTo),\n tags,\n });\n }\n i = Math.max(stmtEnd, headEnd);\n continue;\n }\n\n if (opener !== -1) {\n const bClose = matchBrace(code, opener);\n i = bClose === -1 ? close : bClose + 1;\n } else {\n i = headEnd;\n }\n }\n\n // Resolve relationship endpoints against everything declared anywhere in the\n // model — LikeC4 resolution is not order-dependent, so neither is this.\n const ids = new Set(elements.map((e) => e.id));\n const byLeaf = new Map<string, string[]>();\n for (const e of elements) {\n const leaf = e.id.slice(e.id.lastIndexOf(\".\") + 1);\n byLeaf.set(leaf, [...(byLeaf.get(leaf) ?? []), e.id]);\n }\n const resolve = (lit: string | undefined, enclosing: string): string => {\n if (lit === undefined || lit === \"it\" || lit === \"this\") return enclosing;\n const segs = lit.split(\".\");\n let base: string | undefined;\n for (let scope = enclosing; scope !== \"\" && base === undefined; ) {\n if (ids.has(`${scope}.${segs[0]!}`)) base = `${scope}.${segs[0]!}`;\n const dot = scope.lastIndexOf(\".\");\n scope = dot === -1 ? \"\" : scope.slice(0, dot);\n }\n if (base === undefined && ids.has(segs[0]!)) base = segs[0]!;\n if (base === undefined) {\n const leafed = byLeaf.get(segs[0]!);\n if (leafed !== undefined && leafed.length === 1) base = leafed[0]!;\n }\n return [base ?? segs[0]!, ...segs.slice(1)].join(\".\");\n };\n\n const rels: ScannedRel[] = raw.map((r) => ({\n source: resolve(r.srcLit, r.parent),\n target: resolve(r.tgtLit, r.parent),\n title: r.title,\n op: r.op,\n tags: r.tags,\n start: r.start,\n end: r.end,\n indent: r.indent,\n }));\n\n elements.sort((a, b) => a.start - b.start);\n return { open, close, elements, rels };\n}\n"]}
1
+ {"version":3,"file":"likec4.js","sourceRoot":"","sources":["../../src/core/likec4.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { LikeC4 } from \"likec4\";\n\n/** A parse/validation issue reported by LikeC4. */\nexport interface LikeC4Error {\n message: string;\n line?: number;\n sourceFsPath?: string;\n}\n\n/** loam-neutral element view (flattened from the LikeC4 parsed model). */\nexport interface Elem {\n id: string;\n kind: string;\n title: string;\n description?: string;\n /**\n * The `services/<id>` directory this element stands for, from the element's\n * `metadata { service '...' }`. Absent on the elements nobody has bound —\n * see `elementService` for the fallback.\n */\n service?: string;\n tags: string[];\n}\n\n/**\n * The service directory an element stands for: an explicit\n * `metadata { service '<id>' }` binding wins, the title is the fallback.\n *\n * The fallback is what every docs repo written before the binding existed relies\n * on, and it is also the trap the binding exists to close: matching on the title\n * means renaming a box in a diagram silently unlinks it from its service, and\n * every check that joined the two just stops finding anything.\n */\nexport function elementService(e: Elem): string {\n return e.service ?? e.title;\n}\n\n/**\n * Every id an endpoint may be filed under, nearest first: `a.b.c`, `a.b`, `a`.\n *\n * A landscape does not have to draw a service as one opaque box. The moment\n * somebody models its containers — `paymentService.api`, `paymentService.worker`\n * — an edge drawn INTO a container is still an edge into the service, and every\n * join that groups by service has to know that. Resolving only the exact id is\n * what made those edges invisible: the spine check silently skipped them, and\n * the no-openapi grace treated a service with a dozen inbound container calls as\n * one nobody calls at all.\n */\nfunction ancestorIds(id: string): string[] {\n const out = [id];\n for (let dot = id.lastIndexOf(\".\"); dot !== -1; dot = id.lastIndexOf(\".\", dot - 1)) {\n out.push(id.slice(0, dot));\n }\n return out;\n}\n\n/**\n * A memoized `id -> service` resolver over one document.\n *\n * Resolution order, and why it is this order:\n *\n * 1. the nearest ancestor (the id itself first) carrying an explicit\n * `metadata { service '<id>' }` — a binding is a claim somebody wrote down,\n * so it outranks every guess;\n * 2. the nearest ancestor whose title names a REAL `services/<id>/` directory —\n * positive evidence from the filesystem, available only to callers that\n * hand in `known`;\n * 3. the element's own title, and finally the raw id — today's fallback, kept\n * LAST because it is the one that lies: it happily resolves\n * `paymentService.api` to \"api\", a service that has never existed.\n *\n * Built once per document and shared, because it is called inside loops over\n * every relationship and the walk up the ancestor chain is not free.\n */\nexport function serviceResolver(\n elements: Elem[],\n known?: ReadonlySet<string>,\n): (id: string) => string {\n const byId = new Map(elements.map((e) => [e.id, e]));\n const memo = new Map<string, string>();\n return (id: string): string => {\n const hit = memo.get(id);\n if (hit !== undefined) return hit;\n let answer: string | undefined;\n for (const candidate of ancestorIds(id)) {\n const e = byId.get(candidate);\n if (e?.service !== undefined) {\n answer = e.service;\n break;\n }\n }\n if (answer === undefined && known !== undefined) {\n for (const candidate of ancestorIds(id)) {\n const e = byId.get(candidate);\n if (e !== undefined && known.has(e.title)) {\n answer = e.title;\n break;\n }\n }\n }\n if (answer === undefined) {\n const self = byId.get(id);\n answer = self ? elementService(self) : id;\n }\n memo.set(id, answer);\n return answer;\n };\n}\n\n/**\n * The service a relationship endpoint belongs to. An id that names no element\n * resolves to itself, so a partial document degrades to the id rather than\n * throwing.\n *\n * `known` is the set of service directories that actually exist; pass it\n * wherever the caller has enumerated `services/`, so an edge into a modelled\n * container resolves to the service that owns it instead of to the container's\n * own title. Callers with a single document and no repository in hand omit it\n * and keep the pre-existing behaviour.\n */\nexport function serviceOf(elements: Elem[], id: string, known?: ReadonlySet<string>): string {\n return serviceResolver(elements, known)(id);\n}\n\n/** loam-neutral relationship view. */\nexport interface Rel {\n source: string;\n target: string;\n title?: string;\n /** OpenAPI operationId this call uses, from the relationship's `metadata { op '...' }`. */\n op?: string;\n /**\n * AsyncAPI message this edge PRODUCES, from `metadata { publishes '...' }`.\n * Resolved against the edge SOURCE's own contract: a service publishes what it\n * declares an `action: send` for.\n */\n publishes?: string;\n /**\n * AsyncAPI message this edge CONSUMES, from `metadata { consumes '...' }`.\n * Two keys rather than one directional `message`, because the async spine is\n * not symmetric with the HTTP one: there the PROVIDER owns the contract and\n * every consumer is checked against it, while here the PRODUCER owns the\n * message and the consumer lives in another repository entirely. Which side of\n * an edge owes the declaration has to be readable from the edge itself — the\n * arrow cannot say it, because a Kafka edge points at the broker from both\n * ends.\n */\n consumes?: string;\n tags: string[];\n}\n\nexport interface LoadedDoc {\n errors: LikeC4Error[];\n elements: Elem[];\n relationships: Rel[];\n}\n\n/**\n * The slice of a LikeC4 model loam reads: elements and relationships, and on\n * each of those only id/kind/title/description/tags/metadata.\n *\n * It is a type of its own, rather than an inline cast at the one call site,\n * because BOTH of LikeC4's model stages expose exactly this slice — which is\n * what makes the cheap stage a safe substitute for the expensive one. The\n * substitution is pinned in test/likec4-model-parity.test.ts, which flattens\n * both stages through `flattenModel` below and compares the results.\n */\nexport interface ReadableModel {\n elements: () => Iterable<{\n id: string;\n kind: string;\n title: string;\n description?: unknown;\n tags?: readonly string[];\n metadata?: unknown;\n }>;\n relationships: () => Iterable<{\n source: { id: string };\n target: { id: string };\n title?: string | null;\n tags?: readonly string[];\n metadata?: unknown;\n }>;\n}\n\n/** Flatten a LikeC4 model into loam's neutral `Elem`/`Rel` view. */\nexport function flattenModel(model: ReadableModel): { elements: Elem[]; relationships: Rel[] } {\n const elements: Elem[] = [...model.elements()].map((e) => ({\n id: e.id,\n kind: e.kind,\n title: e.title,\n description: descText(e.description),\n service: metaKey(e.metadata, \"service\"),\n tags: [...(e.tags ?? [])],\n }));\n const relationships: Rel[] = [...model.relationships()].map((r) => ({\n source: r.source.id,\n target: r.target.id,\n // LikeC4 reports an untitled edge as title: null — normalize to the declared `title?: string`.\n title: r.title ?? undefined,\n op: metaKey(r.metadata, \"op\"),\n publishes: metaKey(r.metadata, \"publishes\"),\n consumes: metaKey(r.metadata, \"consumes\"),\n tags: [...(r.tags ?? [])],\n }));\n return { elements, relationships };\n}\n\n/**\n * Load and validate a single self-contained `.likec4` document, in-process\n * (no external tool, no JVM). Returns validation errors and, if clean, the\n * flattened elements + relationships.\n */\nexport async function loadFile(path: string): Promise<LoadedDoc> {\n return loadSource(await readFile(path, \"utf8\"));\n}\n\n/**\n * `loadFile` for text that is not (yet) on disk — what the archive merge uses\n * to prove a computed landscape parses BEFORE anything is written.\n */\nexport async function loadSource(src: string): Promise<LoadedDoc> {\n // LikeC4 owns a Langium workspace and registers a process-exit listener for\n // it. Validation creates many short-lived instances, so every acquired\n // instance must be disposed even on an invalid document or a failed model\n // computation. Its logger is disabled because diagnostics are returned in\n // LoadedDoc instead of being written out-of-band to stderr.\n const likec4 = await LikeC4.fromSource(src, { logger: false });\n try {\n // Assigned, not cast: LikeC4 declares `getErrors()` as a non-nullable array\n // whose element type is structurally wider than ours, so the compiler can\n // check the shape here. The cast was the only thing standing between a\n // dependency renaming `message` and a build that still succeeded — and the\n // `?? []` it carried could never fire.\n const errors: LikeC4Error[] = likec4.getErrors();\n if (errors.length > 0) {\n return { errors, elements: [], relationships: [] };\n }\n\n // `parsedModel`, not `computedModel`: the computed stage additionally\n // computes every VIEW the document declares (and a default one when it\n // declares none), which loam never reads — it renders nothing. That work is\n // superlinear in the number of RELATIONSHIPS, so a landscape at fleet shape\n // turned `loam list` from under a second into minutes. The elements and\n // relationships below are identical either way.\n const model = (await likec4.parsedModel()) as ReadableModel;\n return { errors, ...flattenModel(model) };\n } finally {\n await likec4.dispose();\n }\n}\n\n/**\n * Read one string key out of a LikeC4 `metadata { ... }` block. Four keys carry\n * loam's spines: `op` on a relationship (the OpenAPI operationId it calls),\n * `publishes`/`consumes` on a relationship (the AsyncAPI message it produces or\n * receives), and `service` on an element (the services/<id> directory it stands\n * for). Every one of them is read by BOTH model readers — the parsed one here\n * and the text scanner `scanModel` uses for archive's splice map — because a key\n * only one of them sees is a key the merge silently drops. Elements\n * with no metadata come back as `{}`, so a missing key is indistinguishable from\n * a missing block — both mean \"not bound\".\n */\nfunction metaKey(m: unknown, key: string): string | undefined {\n if (m && typeof m === \"object\") {\n const v = (m as Record<string, unknown>)[key];\n if (typeof v === \"string\") return v;\n }\n return undefined;\n}\n\n/** LikeC4 descriptions can be a string or a rich-text object ({ txt } / { text } / { md }). */\nfunction descText(d: unknown): string | undefined {\n if (typeof d === \"string\") return d;\n if (d && typeof d === \"object\") {\n const o = d as Record<string, unknown>;\n for (const key of [\"txt\", \"text\", \"md\", \"value\"]) {\n const v = o[key];\n if (typeof v === \"string\") return v;\n }\n }\n return undefined;\n}\n\n/* ------------------------------------------------------------------ */\n/* Source scanning — the text-level view the archive merge splices from */\n/* ------------------------------------------------------------------ */\n\n/** A string literal in LikeC4 source: its span in the text, and its unescaped value. */\nexport interface SourceLiteral {\n start: number;\n end: number;\n value: string;\n}\n\n/**\n * A LikeC4 source with everything that is not code blanked out: string\n * interiors and comments become spaces, newlines survive, and the result is\n * exactly as long as the input — so structure (braces, arrows, `#tags`) can be\n * read with plain string operations at offsets that are valid in the original.\n */\nexport interface MaskedSource {\n code: string;\n literals: SourceLiteral[];\n}\n\nexport function maskSource(text: string): MaskedSource {\n const out = text.split(\"\");\n const literals: SourceLiteral[] = [];\n type State = \"code\" | \"squote\" | \"dquote\" | \"lineComment\" | \"blockComment\";\n let state: State = \"code\";\n let litStart = -1;\n let lit = \"\";\n for (let i = 0; i < text.length; i += 1) {\n const ch = text[i]!;\n const next = text[i + 1];\n switch (state) {\n case \"code\":\n if (ch === \"'\" || ch === '\"') {\n state = ch === \"'\" ? \"squote\" : \"dquote\";\n litStart = i;\n lit = \"\";\n out[i] = \" \";\n } else if (ch === \"/\" && next === \"/\") {\n state = \"lineComment\";\n out[i] = \" \";\n } else if (ch === \"/\" && next === \"*\") {\n state = \"blockComment\";\n out[i] = \" \";\n }\n break;\n case \"squote\":\n case \"dquote\":\n if (ch === \"\\\\\" && i + 1 < text.length) {\n lit += text[i + 1]!;\n out[i] = \" \";\n out[i + 1] = \" \";\n i += 1;\n } else if (ch === (state === \"squote\" ? \"'\" : '\"')) {\n literals.push({ start: litStart, end: i + 1, value: lit });\n state = \"code\";\n out[i] = \" \";\n } else {\n lit += ch;\n if (ch !== \"\\n\") out[i] = \" \";\n }\n break;\n case \"lineComment\":\n if (ch === \"\\n\") state = \"code\";\n else out[i] = \" \";\n break;\n case \"blockComment\":\n if (ch === \"*\" && next === \"/\") {\n state = \"code\";\n out[i] = \" \";\n out[i + 1] = \" \";\n i += 1;\n } else if (ch !== \"\\n\") {\n out[i] = \" \";\n }\n break;\n }\n }\n return { code: out.join(\"\"), literals };\n}\n\n/** Index of the `}` matching the `{` at `open`, over MASKED code; -1 when unbalanced. */\nexport function matchBrace(code: string, open: number): number {\n let depth = 0;\n for (let i = open; i < code.length; i += 1) {\n if (code[i] === \"{\") depth += 1;\n else if (code[i] === \"}\") {\n depth -= 1;\n if (depth === 0) return i;\n }\n }\n return -1;\n}\n\n/** An element declaration located in source, under the id the computed model gives it. */\nexport interface ScannedElement {\n /** Full dotted id — `parent.child` for a nested declaration. */\n id: string;\n /** Span of the whole declaration: first head character to one past its end. */\n start: number;\n end: number;\n /** Its own body braces; -1 when the declaration has no body. */\n bodyOpen: number;\n bodyClose: number;\n /** Leading whitespace of the declaration's first line. */\n indent: string;\n}\n\n/** A relationship statement located in source, endpoints resolved to full ids (best effort). */\nexport interface ScannedRel {\n source: string;\n target: string;\n title?: string;\n op?: string;\n /** `metadata { publishes '...' }` — see `Rel.publishes`. */\n publishes?: string;\n /** `metadata { consumes '...' }` — see `Rel.consumes`. */\n consumes?: string;\n tags: string[];\n start: number;\n end: number;\n indent: string;\n}\n\nexport interface ScannedModel {\n /** The `model` block's own braces. */\n open: number;\n close: number;\n /** In document order. A nested declaration's span lies inside its parent's. */\n elements: ScannedElement[];\n rels: ScannedRel[];\n}\n\n/**\n * Locate every element and relationship declaration inside the top-level\n * `model { ... }` block of a LikeC4 source, with the byte range each one\n * occupies. This is the splice map `loam archive` merges the landscape with:\n * the parsed model says WHAT exists, this says WHERE its authored text is —\n * verbatim, so nothing the parser does not model (technology, style, links)\n * can be lost by rebuilding it.\n *\n * Both declaration forms are recognised — `name = kind ...` always, and\n * `kind name ...` for any kind the document's own `specification` block\n * declares. Relationship endpoints are resolved the way LikeC4 reads them:\n * innermost enclosing scope outward, then the top level, then a unique leaf\n * name anywhere; `it`/`this` (and an omitted source) mean the enclosing\n * element. A name that resolves nowhere is left as written — the caller's\n * match against the parsed model then fails loudly instead of guessing.\n *\n * Returns null when there is no model block. Callers only hand in documents\n * LikeC4 has already parsed, so the scan can trust balanced braces.\n */\nexport function scanModel(text: string): ScannedModel | null {\n const { code, literals } = maskSource(text);\n const m = /\\bmodel\\s*\\{/.exec(code);\n if (!m) return null;\n const open = m.index + m[0].length - 1;\n const close = matchBrace(code, open);\n if (close === -1) return null;\n\n // Element kinds from the specification block(s): the `<kind> <name>`\n // declaration form is recognisable only by its first token being a kind.\n const kinds = new Set<string>();\n for (const sm of code.matchAll(/\\bspecification\\s*\\{/g)) {\n const sClose = matchBrace(code, sm.index + sm[0].length - 1);\n if (sClose === -1) continue;\n for (const em of code.slice(sm.index, sClose).matchAll(/\\belement\\s+([A-Za-z_][\\w-]*)/g)) {\n kinds.add(em[1]!);\n }\n }\n\n const indentOf = (at: number): string => {\n const nl = text.lastIndexOf(\"\\n\", at - 1);\n const ws = text.slice(nl + 1, at);\n return /^[ \\t]*$/.test(ws) ? ws : \"\";\n };\n const literalIn = (from: number, to: number): SourceLiteral | undefined =>\n literals.find((l) => l.start >= from && l.end <= to);\n const keyedLiteral = (key: string, from: number, to: number): string | undefined => {\n if (from === -1) return undefined;\n const re = new RegExp(`\\\\b${key}\\\\b`, \"g\");\n re.lastIndex = from;\n const km = re.exec(code);\n if (km === null || km.index >= to) return undefined;\n return literalIn(km.index, to)?.value;\n };\n const trimmedEnd = (from: number, to: number): number => {\n let e = to;\n while (e > from && /\\s/.test(text[e - 1]!)) e -= 1;\n return e;\n };\n\n const elements: ScannedElement[] = [];\n interface RawRel {\n srcLit?: string;\n tgtLit: string;\n parent: string;\n start: number;\n end: number;\n indent: string;\n title?: string;\n op?: string;\n publishes?: string;\n consumes?: string;\n tags: string[];\n }\n const raw: RawRel[] = [];\n const stack: Array<{ id: string; el: ScannedElement }> = [];\n\n let i = open + 1;\n while (i < close) {\n const ch = code[i]!;\n if (/\\s/.test(ch)) {\n i += 1;\n continue;\n }\n if (ch === \"}\") {\n const frame = stack.pop();\n if (frame !== undefined) {\n frame.el.bodyClose = i;\n frame.el.end = i + 1;\n elements.push(frame.el);\n }\n i += 1;\n continue;\n }\n\n // A statement's head: up to its own `{`, the end of the line, or the\n // enclosing block's `}` on the same line — whichever comes first.\n const stmtStart = i;\n let headEnd = close;\n let opener = -1;\n for (let j = i; j < close; j += 1) {\n const c = code[j]!;\n if (c === \"\\n\" || c === \"}\") {\n headEnd = j;\n break;\n }\n if (c === \"{\") {\n opener = j;\n headEnd = j;\n break;\n }\n }\n const head = code.slice(stmtStart, headEnd);\n const parentId = stack.length > 0 ? stack[stack.length - 1]!.id : \"\";\n\n // Classify: `name = kind` first, then a relationship (the arrow cannot hide\n // in a string — the head is masked), then the `kind name` form, else a\n // property/tag line that is consumed whole, nested block included.\n let localName: string | null = null;\n const assign = /^([A-Za-z_]\\w*)\\s*=\\s*[A-Za-z_]/.exec(head);\n if (assign !== null) {\n localName = assign[1]!;\n } else if (!head.includes(\"->\")) {\n const toks = head.trim().split(/\\s+/);\n if (toks.length >= 2 && kinds.has(toks[0]!) && /^[A-Za-z_]\\w*$/.test(toks[1]!)) {\n localName = toks[1]!;\n }\n }\n\n if (localName !== null) {\n const id = parentId === \"\" ? localName : `${parentId}.${localName}`;\n const el: ScannedElement = {\n id,\n start: stmtStart,\n end: headEnd,\n bodyOpen: opener,\n bodyClose: -1,\n indent: indentOf(stmtStart),\n };\n if (opener !== -1) {\n stack.push({ id, el });\n i = opener + 1;\n } else {\n el.end = trimmedEnd(stmtStart, headEnd);\n elements.push(el);\n i = headEnd;\n }\n continue;\n }\n\n if (head.includes(\"->\")) {\n const bClose = opener === -1 ? -1 : matchBrace(code, opener);\n const stmtEnd = opener === -1 ? trimmedEnd(stmtStart, headEnd) : bClose === -1 ? close : bClose + 1;\n const am = /^([A-Za-z_][\\w.]*)?\\s*(?:-\\[[^\\]]*\\])?->\\s*([A-Za-z_][\\w.]*)/.exec(head);\n if (am !== null) {\n const arrowEnd = stmtStart + am[0].length;\n const bodyFrom = opener === -1 ? -1 : opener + 1;\n const bodyTo = opener === -1 ? -1 : stmtEnd - 1;\n const title = literalIn(arrowEnd, headEnd)?.value ?? keyedLiteral(\"title\", bodyFrom, bodyTo);\n const tags: string[] = [];\n for (const tm of code.slice(stmtStart, stmtEnd).matchAll(/#([\\w-]+)/g)) tags.push(tm[1]!);\n raw.push({\n srcLit: am[1],\n tgtLit: am[2]!,\n parent: parentId,\n start: stmtStart,\n end: stmtEnd,\n indent: indentOf(stmtStart),\n title,\n op: keyedLiteral(\"op\", bodyFrom, bodyTo),\n publishes: keyedLiteral(\"publishes\", bodyFrom, bodyTo),\n consumes: keyedLiteral(\"consumes\", bodyFrom, bodyTo),\n tags,\n });\n }\n i = Math.max(stmtEnd, headEnd);\n continue;\n }\n\n if (opener !== -1) {\n const bClose = matchBrace(code, opener);\n i = bClose === -1 ? close : bClose + 1;\n } else {\n i = headEnd;\n }\n }\n\n // Resolve relationship endpoints against everything declared anywhere in the\n // model — LikeC4 resolution is not order-dependent, so neither is this.\n const ids = new Set(elements.map((e) => e.id));\n const byLeaf = new Map<string, string[]>();\n for (const e of elements) {\n const leaf = e.id.slice(e.id.lastIndexOf(\".\") + 1);\n byLeaf.set(leaf, [...(byLeaf.get(leaf) ?? []), e.id]);\n }\n const resolve = (lit: string | undefined, enclosing: string): string => {\n if (lit === undefined || lit === \"it\" || lit === \"this\") return enclosing;\n const segs = lit.split(\".\");\n let base: string | undefined;\n for (let scope = enclosing; scope !== \"\" && base === undefined; ) {\n if (ids.has(`${scope}.${segs[0]!}`)) base = `${scope}.${segs[0]!}`;\n const dot = scope.lastIndexOf(\".\");\n scope = dot === -1 ? \"\" : scope.slice(0, dot);\n }\n if (base === undefined && ids.has(segs[0]!)) base = segs[0]!;\n if (base === undefined) {\n const leafed = byLeaf.get(segs[0]!);\n if (leafed !== undefined && leafed.length === 1) base = leafed[0]!;\n }\n return [base ?? segs[0]!, ...segs.slice(1)].join(\".\");\n };\n\n const rels: ScannedRel[] = raw.map((r) => ({\n source: resolve(r.srcLit, r.parent),\n target: resolve(r.tgtLit, r.parent),\n title: r.title,\n op: r.op,\n publishes: r.publishes,\n consumes: r.consumes,\n tags: r.tags,\n start: r.start,\n end: r.end,\n indent: r.indent,\n }));\n\n elements.sort((a, b) => a.start - b.start);\n return { open, close, elements, rels };\n}\n"]}
@@ -580,6 +580,73 @@ async function gitIgnoredPaths(repoDir, rels) {
580
580
  child.stdin.end(rels.join("\0") + "\0");
581
581
  });
582
582
  }
583
+ /**
584
+ * Who git says is working in this repository — `Name <email>`, or null when git
585
+ * cannot answer.
586
+ *
587
+ * This is the only identity loam has, and it is deliberately a weak one: git
588
+ * config is a text file anybody can edit, so `vouched_by` is a name, not a
589
+ * signature, and it proves nothing cryptographically. What it does is make the
590
+ * claim ATTRIBUTABLE — a `status: verified` with nobody's name against it says
591
+ * only that some process wrote the word, which is exactly the state that let a
592
+ * vouch be indistinguishable from an agent's own draft. A reviewer reading the
593
+ * frontmatter, or `git log` on the docs repo, can now ask a specific person what
594
+ * they read. Signing is the real answer and it is not this one; nothing here
595
+ * pretends otherwise.
596
+ *
597
+ * `user.email` is what is required — it is the field git itself refuses to
598
+ * commit without, so a repository where a person has ever committed has one —
599
+ * and `user.name` rides along when it is set.
600
+ */
601
+ export async function gitIdentity(repoDir) {
602
+ // The environment first, because that is git's own precedence: GIT_COMMITTER_*
603
+ // overrides `user.*` for the identity git records, and GIT_AUTHOR_* behind it.
604
+ // Reading them is loam agreeing with git rather than inventing a second rule —
605
+ // and it is the only identity a CI job or a container has, where there is no
606
+ // `user.email` anywhere and running `git config --global` first would be a
607
+ // step nobody documents.
608
+ const fromEnv = identity(process.env.GIT_COMMITTER_NAME ?? process.env.GIT_AUTHOR_NAME, process.env.GIT_COMMITTER_EMAIL ?? process.env.GIT_AUTHOR_EMAIL);
609
+ if (fromEnv !== null)
610
+ return fromEnv;
611
+ const [name, email] = await Promise.all([
612
+ gitConfig(repoDir, "user.name"),
613
+ gitConfig(repoDir, "user.email"),
614
+ ]);
615
+ return identity(name ?? undefined, email ?? undefined);
616
+ }
617
+ /** `Name <email>`, or the bare email, or null — the one spelling of the stamp. */
618
+ function identity(name, email) {
619
+ const e = email?.trim();
620
+ if (e === undefined || e === "")
621
+ return null;
622
+ const n = name?.trim();
623
+ return n === undefined || n === "" ? e : `${n} <${e}>`;
624
+ }
625
+ /**
626
+ * One `git config --get` value, or null for every way it can fail to be one:
627
+ * unset (exit 1), not a repository, git not installed, a timeout. The caller
628
+ * distinguishes none of them, and should not — they all mean "git will not tell
629
+ * us who this is", and the fix is the same sentence in each case.
630
+ */
631
+ async function gitConfig(repoDir, key) {
632
+ return new Promise((done) => {
633
+ const child = spawn("git", ["config", "--get", key], {
634
+ cwd: repoDir,
635
+ stdio: ["ignore", "pipe", "ignore"],
636
+ timeout: GIT_TIMEOUT_MS,
637
+ });
638
+ let out = "";
639
+ child.stdout.setEncoding("utf8");
640
+ child.stdout.on("data", (chunk) => {
641
+ out += chunk;
642
+ });
643
+ child.on("error", () => done(null));
644
+ child.on("close", (code) => {
645
+ const value = out.trim();
646
+ done(code === 0 && value !== "" ? value : null);
647
+ });
648
+ });
649
+ }
583
650
  /* ------------------------------------------------------------------ */
584
651
  /* The per-file index: what `sources.stale` names */
585
652
  /* ------------------------------------------------------------------ */
@@ -1 +1 @@
1
- {"version":3,"file":"provenance.js","sourceRoot":"","sources":["../../src/core/provenance.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Provenance checks: is this artifact anchored to anything, and has anyone\n * vouched for it?\n *\n * SCHEMA.md has declared `status` / `owner` / `sources` / `last_verified` since\n * the beginning and nothing read them. They matter more now that the prose is\n * written by an agent: coherence proves the corpus agrees with itself, and only\n * `sources` says it has anything to do with the code.\n *\n * Absence and contradiction are graded differently on purpose. A missing field\n * is incompleteness — a warning, something to work through. A field that says\n * the wrong thing (a spec claiming to be another service, a status nobody\n * defined) is a bug, and gates.\n */\nimport { spawn } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport { existsSync } from \"node:fs\";\nimport { readdir, readFile, realpath, stat } from \"node:fs/promises\";\nimport { join, relative, resolve, sep } from \"node:path\";\nimport {\n listField,\n parseFrontmatter,\n rawBody,\n readFrontmatter,\n stringField,\n FEATURE_STATUSES,\n SERVICE_STATUSES,\n type Frontmatter,\n} from \"./frontmatter.js\";\nimport type { Finding } from \"./report.js\";\nimport { featurePaths, servicePaths, SPEC_AXES } from \"./repo.js\";\nimport { isPathInside, resolveInside } from \"./path-safety.js\";\n\n/** Fields every artifact is expected to carry, beyond its identity and status. */\nconst EXPECTED = [\"owner\"] as const;\n\nexport interface ProvenanceOptions {\n /**\n * The repo `sources` resolve against — the service repo loam is running in.\n * Undefined when the docs being validated describe some other repository, in\n * which case the paths are not ours to resolve and are left alone.\n */\n repoDir?: string;\n}\n\nexport async function serviceProvenance(\n docsDir: string,\n service: string,\n opts: ProvenanceOptions = {},\n): Promise<Finding[]> {\n const paths = servicePaths(docsDir, service);\n const findings: Finding[] = [];\n // Both requirement-carrying specs get the same pass: arch.spec.md follows\n // spec.md's frontmatter conventions exactly, so the checks are one loop —\n // only the label tells a reader which file moved. The pair comes from\n // SPEC_AXES rather than being respelled, so a third axis is graded here the\n // day it is declared instead of the day somebody notices.\n for (const { path, file } of SPEC_AXES.map((axis) => ({ path: paths[axis.key], file: axis.file }))) {\n if (!existsSync(path)) continue;\n const raw = await readFile(path, \"utf8\");\n const fm = parseFrontmatter(raw);\n const label = `${service}: ${file}`;\n findings.push(\n ...identityFindings(fm, {\n label,\n idField: \"service\",\n idValue: service,\n statuses: [...SERVICE_STATUSES],\n }),\n );\n // With no frontmatter at all, \"it names no sources\" says nothing the missing\n // header did not already say — and a malformed one proves nothing either:\n // `sources.absent` against a header nobody can read is the same false\n // advice as the field cascade, so frontmatter.malformed stands alone.\n if (fm.present && !fm.malformed) {\n findings.push(...(await sourceFindings(fm, service, label, opts.repoDir)));\n findings.push(...contentFindings(fm, raw, service, label));\n }\n }\n return findings;\n}\n\nexport async function featureProvenance(\n featureDir: string,\n featureId: string,\n): Promise<Finding[]> {\n const path = featurePaths(featureDir).intent;\n if (!existsSync(path)) return [];\n const fm = await readFrontmatter(path);\n return identityFindings(fm, {\n label: \"intent.md\",\n idField: \"feature\",\n idValue: featureId,\n statuses: [...FEATURE_STATUSES],\n });\n}\n\ninterface IdentitySpec {\n label: string;\n idField: \"service\" | \"feature\";\n idValue: string;\n statuses: string[];\n}\n\nfunction identityFindings(fm: Frontmatter, spec: IdentitySpec): Finding[] {\n const findings: Finding[] = [];\n if (!fm.present) {\n return [\n {\n severity: \"warn\",\n code: \"frontmatter.missing\",\n subject: spec.idValue,\n message: `${spec.label} has no frontmatter — no owner, no status, no link to the code it describes`,\n },\n ];\n }\n\n // One honest error instead of the cascade: with an unreadable header the\n // field checks below would grade owner/status/service \"missing\" and send the\n // author adding fields to a block YAML refuses to parse. Nothing can be\n // concluded from a header nobody can read — including that it lacks fields.\n if (fm.malformed) {\n return [\n {\n severity: \"error\",\n code: \"frontmatter.malformed\",\n subject: spec.idValue,\n message: `${spec.label} has frontmatter that does not parse as YAML — owner, status and sources are unreadable until the header is fixed`,\n },\n ];\n }\n\n const id = stringField(fm, spec.idField);\n if (id !== undefined && id !== spec.idValue) {\n findings.push({\n severity: \"error\",\n code: \"frontmatter.field-mismatch\",\n subject: spec.idValue,\n message: `${spec.label} declares ${spec.idField}: ${id}, but it lives under ${spec.idValue}`,\n });\n }\n\n const status = stringField(fm, \"status\");\n if (status !== undefined && !spec.statuses.includes(status)) {\n findings.push({\n severity: \"error\",\n code: \"frontmatter.status-unknown\",\n subject: spec.idValue,\n message: `${spec.label} has status '${status}' — expected one of ${spec.statuses.join(\", \")}`,\n });\n }\n\n const missing = [\n ...(id === undefined ? [spec.idField] : []),\n ...(status === undefined ? [\"status\"] : []),\n ...EXPECTED.filter((f) => stringField(fm, f) === undefined),\n ];\n if (missing.length > 0) {\n findings.push({\n severity: \"warn\",\n code: \"frontmatter.field-missing\",\n subject: spec.idValue,\n message: `${spec.label} is missing ${missing.length} frontmatter field(s)`,\n details: missing,\n text: { detailPrefix: \"- \" },\n });\n }\n return findings;\n}\n\nasync function sourceFindings(\n fm: Frontmatter,\n service: string,\n label: string,\n repoDir: string | undefined,\n): Promise<Finding[]> {\n const sources = listField(fm, \"sources\");\n if (sources.length === 0) {\n return [\n {\n severity: \"warn\",\n code: \"sources.absent\",\n message: `${label} names no sources — nothing ties it to the code, so nothing can tell you when it goes out of date`,\n },\n ];\n }\n // Someone else's repository: the paths describe a tree loam is not standing in.\n if (repoDir === undefined) return [];\n\n // Before existence: a pattern entry would \"not exist\" as a literal path, and\n // grading it missing would send the author fixing the wrong thing.\n const patterns = patternSources(sources);\n if (patterns.length > 0) {\n return [\n {\n severity: \"error\",\n code: \"sources.path-missing\",\n message: `${label}: ${patterns.length} source(s) are glob patterns — ${patterns.join(\", \")}. Patterns are no longer supported: name files or directories (a directory already covers everything beneath it).`,\n details: patterns,\n text: { detailPrefix: \"- \" },\n },\n ];\n }\n\n const unsafe = unsafeSources(repoDir, sources);\n if (unsafe.length > 0) {\n return [\n {\n severity: \"error\",\n code: \"sources.path-outside\",\n message: `${label}: ${unsafe.length} source(s) escape the service repo — ${unsafe.join(\", \")}. Sources must be relative paths contained by this repository, including through symlinks.`,\n details: unsafe,\n text: { detailPrefix: \"- \" },\n },\n ];\n }\n\n const missing = missingSources(repoDir, sources);\n if (missing.length > 0) {\n return [\n {\n severity: \"error\",\n code: \"sources.path-missing\",\n message: `${label}: ${missing.length} source(s) do not exist — ${missing.join(\", \")}`,\n details: missing,\n text: { detailPrefix: \"- \" },\n },\n ];\n }\n const resolved: Finding = {\n severity: \"ok\",\n code: \"sources.resolved\",\n message: `${label}: ${sources.length} source(s) resolve`,\n };\n\n // The walk happens here rather than inside the staleness branch below, even\n // though only staleness needs the hashes: what the walk REFUSED to follow is\n // a hole in the tie to the code whether or not anybody has vouched yet, and\n // an unvouched service is exactly where a reader is about to decide the\n // sources list is honest.\n const { digest, files, index, skipped } = await sourcesDigest(repoDir, sources);\n const findings: Finding[] = [resolved, ...skippedFindings(skipped, label)];\n\n // The paths are there; the question staleness answers is whether what is AT\n // them is still what somebody read.\n const stamped = stringField(fm, \"sources_digest\");\n if (stamped === undefined) {\n return [\n ...findings,\n {\n severity: \"warn\",\n code: \"sources.unvouched\",\n message: `${label}: no sources_digest — nobody has vouched for this against the code, so nothing can tell you when it goes stale. Run \\`loam vouch --service ${service}\\`.`,\n },\n ];\n }\n\n const since = stringField(fm, \"last_verified\") ?? \"it was stamped\";\n if (digest === stamped) {\n return [\n ...findings,\n {\n severity: \"ok\",\n code: \"sources.current\",\n message: `${label}: sources unchanged since ${since} (${files.length} file(s), digest ${digest})`,\n },\n ];\n }\n\n // What MOVED, when the stamp recorded enough to say. Repeating the frontmatter's\n // own `sources` entries back at a reader — which is all this finding could do\n // before `sources_files` existed — answers a question nobody asked: they wrote\n // that list, and \"one of these five directories changed somehow\" is where the\n // search starts, not where it ends.\n const moved = movedSources(decodeSourceIndex(stringField(fm, \"sources_files\")), index);\n return [\n ...findings,\n {\n // A warning, not an error: the doc may still be right, and only a person\n // can say. What loam knows is that nobody has looked since the code moved.\n severity: \"warn\",\n code: \"sources.stale\",\n message: `${label}: sources changed since ${since}${moved.summary} — re-read them and \\`loam vouch --service ${service}\\``,\n details: moved.paths ?? sources,\n text: { detailPrefix: \"- \" },\n },\n ];\n}\n\n/**\n * The paths the digest walk would not follow. Never silent: a symlink dropped\n * on the floor is a file whose content nobody is watching, while the document\n * beside it says `verified` — the digest cannot go stale over bytes it never\n * hashed, so the one thing the stamp promises is quietly untrue for them.\n *\n * A warning, not an error, and for the same reason staleness is: the doc may be\n * perfectly right about the code behind that link. What loam can say is that it\n * is not watching it.\n */\nfunction skippedFindings(skipped: SkippedSource[], label: string): Finding[] {\n if (skipped.length === 0) return [];\n return [\n {\n severity: \"warn\",\n code: \"sources.skipped\",\n message: `${label}: ${skipped.length} path(s) under the listed sources were not hashed — the digest says nothing about what is behind them`,\n details: skipped.map((s) => `${s.path} — ${s.reason}`),\n text: { detailPrefix: \"- \" },\n },\n ];\n}\n\n/**\n * The doc-side half of the freshness check. `sources_digest` says whether the\n * CODE moved since a person looked, and only that service's repo can recompute\n * it; `content_digest` says whether the DOCUMENT did, and needs nothing but\n * the document — so this runs wherever the spec is readable, `--service` and\n * `--all` alike. Without it, editing a spec after it was vouched left\n * `status: verified` standing over words nobody read — forged freshness, in\n * the exact agent-written-prose threat model this layer exists for.\n */\nfunction contentFindings(fm: Frontmatter, raw: string, service: string, label: string): Finding[] {\n if (stringField(fm, \"status\") !== \"verified\") return [];\n const stamped = stringField(fm, \"content_digest\");\n // A verified doc with no stamp predates the field. Quiet on purpose: the fix\n // is re-vouching, and grading years of pre-feature vouches as suspect would\n // drown the fleet in warnings nobody chose.\n if (stamped === undefined) return [];\n if (contentDigest(raw) === stamped) return [];\n const since = stringField(fm, \"last_verified\") ?? \"it was vouched\";\n return [\n {\n // A warning, not an error — the sources.stale doctrine: the doc changed\n // since it was vouched, and only a person can say whether verified still\n // holds of the new words.\n severity: \"warn\",\n code: \"content.stale\",\n message: `${label} changed since ${since} — the doc moved under its vouch, and only a person can say whether 'verified' still holds. Re-read it and \\`loam vouch --service ${service}\\`.`,\n },\n ];\n}\n\n/**\n * The local calendar day, for the two commands that date a stamp. A vouch or a\n * verification record is a person saying \"today I read this\", so it is their\n * date, not UTC's — `toISOString` files an evening in the Americas under\n * tomorrow. It sits here because this module already owns the stamp vocabulary\n * the date is written into.\n */\nexport function today(now: Date): string {\n const pad = (n: number): string => String(n).padStart(2, \"0\");\n return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;\n}\n\n/* ------------------------------------------------------------------ */\n/* The content digest */\n/* ------------------------------------------------------------------ */\n\n/** How much of the sha256 is written into the document. */\nconst DIGEST_LENGTH = 16;\n\n/** How many source files the digest reads at once — see `sourcesDigest`. */\nconst DIGEST_READ_BATCH = 32;\n\nexport interface SourcesDigest extends SourcesExpansion {\n /** The stamp that goes in `sources_digest`. */\n digest: string;\n /**\n * Per file, its own short sha — the record `sources_files` carries so a later\n * `sources.stale` can name what moved instead of guessing. Same order as `files`.\n */\n index: SourceIndexEntry[];\n}\n\n/** One file's share of the stamp: what it is called, and what it said. */\nexport interface SourceIndexEntry {\n /** Repo-relative, `/`-separated. */\n path: string;\n /** sha256 of its bytes, truncated the way `sources_digest` is. */\n sha: string;\n}\n\n/** A path the walk found but would not hash, and the reason a reader needs. */\nexport interface SkippedSource {\n /** Repo-relative, `/`-separated. */\n path: string;\n /** Why it was left out — the half of the sentence after the path. */\n reason: string;\n}\n\n/** What a `sources` list covers, and what it does not. */\nexport interface SourcesExpansion {\n /** Repo-relative paths, sorted — exactly what a digest would hash. */\n files: string[];\n /** Everything the walk refused to follow. Empty in the ordinary case. */\n skipped: SkippedSource[];\n /**\n * Set only when `files` is empty: the one sentence that says the list covers\n * nothing. `loam vouch` refuses with it, `loam validate` grades it — see\n * emptySourcesMessage for why they must be the same words.\n */\n empty?: string;\n}\n\n/**\n * Digest the CONTENT of the files `sources` names. The recipe is spelled out\n * because the value is written into documents, and anything reading them has to\n * be able to reproduce it:\n *\n * 1. expand each entry to repo-relative file paths (`/` separators) — a file\n * is itself, a directory everything beneath it, minus what the repository\n * does not consider its own (see collectSources) — sorted and de-duplicated;\n * 2. per file, `sha256(bytes)`;\n * 3. feed `<path>\\0<hex>\\n` for each file, in that order, into an outer\n * sha256;\n * 4. keep the first 16 hex characters.\n *\n * Content, not mtime: git does not preserve modification times, so after a\n * fresh clone every file looks changed and the check would be false positives\n * end to end. Bytes survive the clone. Hashing the path alongside the content\n * means a rename registers, which is what a reader of the doc would want.\n *\n * 64 bits is a change detector, not a seal — it answers \"did this move?\", and\n * an adversary who wants a collision can have one.\n */\nexport async function sourcesDigest(repoDir: string, sources: string[]): Promise<SourcesDigest> {\n const { found, skipped } = await collectSources(repoDir, sources);\n const outer = createHash(\"sha256\");\n const index: SourceIndexEntry[] = [];\n // Read in fixed batches, hash in array order. `found` is already sorted and\n // every file's own sha depends on nothing but its own bytes, so the sequence\n // fed to `outer` — and therefore the digest — is unchanged by construction;\n // only the waiting overlaps. A `sources: [src/]` over a real service repo is\n // thousands of files, and one-await-per-file spent nearly all of its time\n // idle. The batch is bounded rather than a single `Promise.all` over the\n // whole list because an unbounded fan-out over a source tree runs the process\n // out of file descriptors, which would turn a stamp into an EMFILE.\n for (let start = 0; start < found.length; start += DIGEST_READ_BATCH) {\n const hashed = await Promise.all(\n found.slice(start, start + DIGEST_READ_BATCH).map(async (file) => ({\n rel: file.rel,\n content: createHash(\"sha256\").update(await readFile(file.abs)).digest(\"hex\"),\n })),\n );\n for (const { rel, content } of hashed) {\n outer.update(`${rel}\\0${content}\\n`);\n index.push({ path: rel, sha: content.slice(0, DIGEST_LENGTH) });\n }\n }\n // `empty` is deliberately not set here: it is a sentence about a named\n // document, and a digest has no name to put in it. Callers that need it say\n // emptySourcesMessage(label, sources) — the same words, from one definition.\n return { digest: outer.digest(\"hex\").slice(0, DIGEST_LENGTH), files: index.map((e) => e.path), index, skipped };\n}\n\n/**\n * What a `sources` list actually covers — the digest's file set without the\n * hashing.\n *\n * Exported because two commands must give the SAME answer to \"does this list\n * cover anything?\". `loam vouch` refuses to stamp an expansion that covers no\n * files (a digest over nothing never changes, so the stamp would read as\n * current forever), and until `loam validate` could say the same thing in the\n * same words, an author got a green validate followed by a vouch that refused —\n * two commands contradicting each other about one document, with nothing in the\n * green run hinting at it.\n */\nexport async function expandSourceFiles(\n repoDir: string,\n sources: string[],\n label: string,\n): Promise<SourcesExpansion> {\n const { found, skipped } = await collectSources(repoDir, sources);\n const files = found.map((f) => f.rel);\n return { files, skipped, ...(files.length === 0 ? { empty: emptySourcesMessage(label, sources) } : {}) };\n}\n\n/**\n * The one sentence for \"these paths exist and cover no file\". One definition,\n * because `vouch`'s refusal and `validate`'s finding are the same diagnosis\n * about the same document and must not drift into two descriptions of it — the\n * author fixes it once, in the sources list.\n */\nexport function emptySourcesMessage(label: string, sources: string[]): string {\n return `${label}: the sources listed match no files — ${sources.join(\", \")}. A digest over nothing would read as current forever.`;\n}\n\n/**\n * The stamp `loam vouch` writes into `content_digest`: sha256 of the\n * document's own BODY — every byte after the frontmatter block (below the\n * closing `---` line and its newline; `rawBody` is the one definition of that\n * cut) — first 16 hex characters, the sources recipe's length.\n *\n * Byte-exact, no normalization. Body-only is load-bearing: vouch itself\n * rewrites the frontmatter as it stamps, and a later frontmatter-only edit\n * (another vouch, a corrected owner) must not read as the document moving.\n */\nexport function contentDigest(source: string): string {\n return createHash(\"sha256\").update(rawBody(source), \"utf8\").digest(\"hex\").slice(0, DIGEST_LENGTH);\n}\n\ninterface SourceFile {\n /** Repo-relative, `/`-separated — the spelling that goes into the hash. */\n rel: string;\n abs: string;\n}\n\n/**\n * The files a `sources` list names: a file is itself, a directory is everything\n * beneath it. Nothing else — glob patterns used to be matched here, by a\n * hand-rolled dialect that silently differed from the gitignore/minimatch\n * conventions authors assume, so a pattern quietly digested a DIFFERENT file\n * set than its author intended. Pattern-looking entries are now refused loudly\n * upstream (see patternSources); this function only ever sees literal paths.\n *\n * Three exclusions, all part of the digest recipe's contract:\n *\n * - dot-entries are skipped while walking — `.git` is not what the doc was\n * written from — though a path naming one outright is still honoured;\n * - `node_modules` is skipped the same way, unconditionally: it is the one\n * build input every ecosystem agrees is not source, and it is the one that\n * makes the walk take minutes;\n * - anything the repository's own `.gitignore` covers is dropped, when the\n * repo is a git checkout and git is on the PATH (see gitIgnoredPaths).\n *\n * The third is the one worth stating a reason for. `sources_digest` answers\n * \"did the code move since a person read it\", and BUILD OUTPUT moves on every\n * CI run without anybody touching the code — a `sources: [src/]` over a repo\n * that compiles into `src/generated/` went stale on a schedule, so the warning\n * that means \"re-read this\" arrived when nothing had been written. A signal\n * that fires every night is a signal people learn to close. Git's answer is\n * used rather than a list of our own because it is the answer the repository\n * already gives every other tool, and it is the author's to change.\n *\n * The fallback is deliberate: no git, no checkout, a git that errors — hash\n * everything. Missing an exclusion costs noise; inventing one costs a file the\n * stamp silently stops watching, and this walk exists to have nothing in that\n * category.\n */\nasync function collectSources(\n repoDir: string,\n sources: string[],\n): Promise<{ found: SourceFile[]; skipped: SkippedSource[] }> {\n const found = new Map<string, string>();\n const skipped = new Map<string, string>();\n const relOf = (abs: string): string => relative(repoDir, abs).split(sep).join(\"/\");\n\n // realpath once, up front: every containment question below is asked against\n // the resolved root, so a repo reached through a symlink (a worktree under\n // /var -> /private/var on macOS, say) does not make its own files look external.\n const repoReal = await realpath(repoDir).catch(() => resolve(repoDir));\n // The realpaths of the directories currently being walked — the cycle guard.\n // A STACK rather than a set of everything ever entered, because the two rules\n // answer different questions: a directory reachable by two spellings (a\n // `current -> versions/3` link beside the real tree) is content under both\n // names and belongs in the digest twice, while a directory reachable from\n // INSIDE itself is a loop. A global visited-set conflates them, and then which\n // spelling survives depends on the order readdir happened to return — a\n // digest that differs between two machines holding identical bytes.\n const walking: string[] = [];\n\n const walk = async (dir: string): Promise<void> => {\n const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);\n // Sorted, so a tree walks the same way on every filesystem.\n entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));\n for (const entry of entries) {\n if (entry.name.startsWith(\".\") || entry.name === \"node_modules\") continue;\n const child = join(dir, entry.name);\n if (entry.isDirectory()) await enterDir(child);\n else if (entry.isFile()) found.set(relOf(child), child);\n // readdir reports symlinks by lstat, so isDirectory() and isFile() are\n // BOTH false for one: before this branch existed a symlinked file, and a\n // whole subtree behind a symlinked directory, fell between the two arms\n // and left the digest without a word said. That is the failure this\n // module is here to prevent, arriving through its own walk.\n else if (entry.isSymbolicLink()) await follow(child);\n }\n };\n\n const enterDir = async (dir: string): Promise<void> => {\n const real = await realpath(dir).catch(() => null);\n if (real === null || walking.includes(real)) return;\n walking.push(real);\n try {\n await walk(dir);\n } finally {\n walking.pop();\n }\n };\n\n const follow = async (link: string): Promise<void> => {\n const real = await realpath(link).catch(() => null);\n if (real === null) {\n skipped.set(relOf(link), \"a symlink that does not resolve\");\n return;\n }\n // Outside the repo the file is not this service's to vouch for, and the\n // same rule already refuses it as a top-level `sources` entry\n // (sources.path-outside). Reported rather than dropped: an author who\n // vendored a sibling repo through a symlink needs to know the stamp stops\n // at the link.\n if (!isPathInside(repoReal, real)) {\n skipped.set(relOf(link), \"a symlink whose target is outside this repository\");\n return;\n }\n const info = await stat(link).catch(() => null);\n if (info === null) {\n skipped.set(relOf(link), \"a symlink that does not resolve\");\n return;\n }\n // A link's own spelling is what the doc's author wrote, so that is the path\n // that goes into the digest — the content comes from the target either way.\n if (info.isFile()) found.set(relOf(link), link);\n // A link that closes a loop is not reported: everything behind it is already\n // in the digest under the spelling it was reached by, so nothing has stopped\n // being watched. Only unhashed CONTENT is worth a warning.\n else if (info.isDirectory()) await enterDir(link);\n else skipped.set(relOf(link), \"a symlink to neither a file nor a directory\");\n };\n\n for (const source of sources) {\n const cleaned = source.trim();\n if (cleaned.length === 0) continue;\n const root = resolveInside(repoDir, cleaned, `source '${source}'`);\n const info = await stat(root).catch(() => null);\n if (info === null) continue;\n if (info.isFile()) found.set(relOf(root), root);\n else if (info.isDirectory()) await enterDir(root);\n }\n\n // One git invocation for the whole expansion, not one per file: the skipped\n // list rides along so an ignored symlink does not warn about a path the\n // repository already says is not its own.\n const ignored = await gitIgnoredPaths(repoDir, [...found.keys(), ...skipped.keys()]);\n for (const rel of ignored) {\n found.delete(rel);\n skipped.delete(rel);\n }\n\n return {\n // Plain codepoint order, not locale order: the digest has to be the same\n // everywhere it is computed.\n found: [...found.entries()]\n .map(([rel, abs]) => ({ rel, abs }))\n .sort((a, b) => (a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0)),\n skipped: [...skipped.entries()]\n .map(([path, reason]) => ({ path, reason }))\n .sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0)),\n };\n}\n\n/** How long the digest waits on git before deciding it learned nothing. */\nconst GIT_TIMEOUT_MS = 10_000;\n\n/**\n * Which of these repo-relative paths the repository ignores, asked once for the\n * whole list. `git check-ignore` is the authority on purpose — it reads\n * `.gitignore` at every level, `.git/info/exclude` and the user's global\n * excludes, and it already knows that a TRACKED file is not ignored no matter\n * what a pattern says, which is the distinction a re-implementation always gets\n * wrong.\n *\n * Every failure answers \"nothing is ignored\": exit 1 is git saying so, exit 128\n * is \"not a git repository\", a spawn error is git not being installed, and a\n * timeout is a repository too strange to wait for. None of them justify\n * dropping a file from a digest a person is going to sign.\n */\nasync function gitIgnoredPaths(repoDir: string, rels: string[]): Promise<Set<string>> {\n if (rels.length === 0) return new Set();\n return new Promise<Set<string>>((done) => {\n const child = spawn(\"git\", [\"check-ignore\", \"--stdin\", \"-z\"], {\n cwd: repoDir,\n stdio: [\"pipe\", \"pipe\", \"ignore\"],\n timeout: GIT_TIMEOUT_MS,\n });\n let out = \"\";\n child.stdout.setEncoding(\"utf8\");\n child.stdout.on(\"data\", (chunk: string) => {\n out += chunk;\n });\n child.on(\"error\", () => done(new Set()));\n child.on(\"close\", (code) => {\n // 0: some paths are ignored and are on stdout. 1: none are — an empty\n // answer, not a failure. Anything else: we learned nothing.\n done(code === 0 || code === 1 ? new Set(out.split(\"\\0\").filter((s) => s.length > 0)) : new Set());\n });\n // A git that died before reading its input hands us EPIPE; the close\n // handler above is what decides the outcome either way.\n child.stdin.on(\"error\", () => undefined);\n child.stdin.end(rels.join(\"\\0\") + \"\\0\");\n });\n}\n\n/* ------------------------------------------------------------------ */\n/* The per-file index: what `sources.stale` names */\n/* ------------------------------------------------------------------ */\n\n/**\n * How many files `sources_files` will list one by one before it gives up and\n * records only how many there were.\n *\n * A readability budget, not a correctness one: the index lives in the header of\n * a document a person reads, and a `sources: [src/]` over a legacy service can\n * expand to thousands of files. Past the limit `sources.stale` falls back to\n * repeating the `sources` entries — worse advice, but a spec.md whose\n * frontmatter is longer than its requirements is worse still.\n */\nconst SOURCE_INDEX_LIMIT = 100;\n\n/**\n * The `sources_files` value `loam vouch` stamps beside `sources_digest`: one\n * `<sha> <path>` line per file (sha256sum's layout, and its column order, so\n * the path may contain spaces), or just the file count once the list would run\n * past SOURCE_INDEX_LIMIT.\n *\n * Its whole purpose is the next `sources.stale`. `sources_digest` can say THAT\n * the code moved and never which part of it; with the index a reader gets the\n * added, removed and changed paths, which is the difference between re-reading\n * one file and re-reading a directory. It is also a readable git diff in the\n * docs repo: the vouch commit shows exactly which source files the stamp now\n * stands over.\n */\nexport function encodeSourceIndex(index: SourceIndexEntry[]): string {\n if (index.length > SOURCE_INDEX_LIMIT) return String(index.length);\n return index.map((e) => `${e.sha} ${e.path}`).join(\"\\n\");\n}\n\n/** What a stamped `sources_files` says: the per-file shas, the count, or nothing. */\ninterface StampedIndex {\n /** path -> sha, or null when the stamp did not record them. */\n entries: Map<string, string> | null;\n /** How many files the stamp covered, when that is knowable. */\n count?: number;\n}\n\n/**\n * Read back what `encodeSourceIndex` wrote. Anything unrecognised reads as \"the\n * stamp said nothing\" rather than as an empty index: a header nobody can parse\n * must not turn into a `sources.stale` claiming every file was deleted.\n */\nfunction decodeSourceIndex(stamped: string | undefined): StampedIndex {\n if (stamped === undefined) return { entries: null };\n const text = stamped.trim();\n if (text.length === 0) return { entries: null };\n if (/^\\d+$/.test(text)) return { entries: null, count: Number(text) };\n\n const entries = new Map<string, string>();\n for (const line of text.split(\"\\n\")) {\n const match = /^([0-9a-f]{16})\\s+(.+)$/.exec(line.trim());\n if (match === null) return { entries: null };\n entries.set(match[2]!, match[1]!);\n }\n return { entries, count: entries.size };\n}\n\n/** The paths that moved between a stamp and now, with a phrase for the message. */\ninterface MovedSources {\n /** Annotated paths, or undefined when the stamp did not record enough to say. */\n paths?: string[];\n /** Appended to the `sources.stale` message; empty when there is nothing to add. */\n summary: string;\n}\n\n/**\n * added / removed / changed, from the stamped index against the current one.\n * The annotation travels IN the detail line rather than in three separate\n * lists: a reader scanning a stale report wants the paths in one column, and\n * \"which of the three\" is one word wide.\n */\nfunction movedSources(stamp: StampedIndex, now: SourceIndexEntry[]): MovedSources {\n if (stamp.entries === null) {\n // No index, but the count alone still beats silence: \"12 files then, 14\n // now\" tells a reader to go looking for two new files.\n return {\n summary: stamp.count === undefined ? \"\" : ` (${stamp.count} file(s) then, ${now.length} now)`,\n };\n }\n const paths: string[] = [];\n const current = new Map(now.map((e) => [e.path, e.sha]));\n for (const [path, sha] of current) {\n const before = stamp.entries.get(path);\n if (before === undefined) paths.push(`added ${path}`);\n else if (before !== sha) paths.push(`changed ${path}`);\n }\n for (const path of stamp.entries.keys()) if (!current.has(path)) paths.push(`removed ${path}`);\n paths.sort();\n // An index that agrees with the current tree while the digest does not is a\n // contradiction (a truncation collision, or a hand-edited header). Say\n // nothing rather than print an empty list under a staleness warning.\n if (paths.length === 0) return { summary: \"\" };\n return { paths, summary: ` (${paths.length} path(s) moved)` };\n}\n\n/** The characters that made an entry a pattern under the glob dialect loam no longer ships. */\nconst PATTERN_CHARS = /[*?[]/;\n\n/**\n * The `sources` entries that look like glob patterns — anything containing\n * `*`, `?` or `[`. Patterns are refused loudly everywhere sources are consumed\n * (`loam vouch` refuses the run, `loam validate` grades an error), never\n * resolved: the glob dialect loam used to ship silently differed from the\n * gitignore/minimatch conventions authors assume — bracket classes literal,\n * its own `**` handling — so a pattern quietly digested a different file set\n * than intended, corrupting the staleness signal in both directions. A\n * directory already means \"everything beneath it\", so the fix is to name one.\n *\n * The cost accepted with the rule: a real file with `[` in its name (a Next.js\n * route, say) cannot be listed on its own — its parent directory covers it.\n */\nexport function patternSources(sources: string[]): string[] {\n return sources.filter((s) => PATTERN_CHARS.test(s.trim()));\n}\n\n/** The `sources` entries that resolve to nothing in this repo. */\nexport function missingSources(repoDir: string, sources: string[]): string[] {\n return sources.filter((s) => !sourceExists(repoDir, s));\n}\n\n/** Entries that are absolute, traverse upward, or escape through a symlink. */\nexport function unsafeSources(repoDir: string, sources: string[]): string[] {\n return sources.filter((source) => {\n const cleaned = source.trim();\n if (cleaned.length === 0) return false;\n try {\n resolveInside(repoDir, cleaned, `source '${source}'`);\n return false;\n } catch {\n return true;\n }\n });\n}\n\n/** Does a `sources` entry point at something real? Literal paths only, checked exactly. */\nfunction sourceExists(repoDir: string, source: string): boolean {\n const cleaned = source.trim();\n if (cleaned.length === 0) return false;\n try {\n return existsSync(resolveInside(repoDir, cleaned, `source '${source}'`));\n } catch {\n return false;\n }\n}\n"]}
1
+ {"version":3,"file":"provenance.js","sourceRoot":"","sources":["../../src/core/provenance.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Provenance checks: is this artifact anchored to anything, and has anyone\n * vouched for it?\n *\n * SCHEMA.md has declared `status` / `owner` / `sources` / `last_verified` since\n * the beginning and nothing read them. They matter more now that the prose is\n * written by an agent: coherence proves the corpus agrees with itself, and only\n * `sources` says it has anything to do with the code.\n *\n * Absence and contradiction are graded differently on purpose. A missing field\n * is incompleteness — a warning, something to work through. A field that says\n * the wrong thing (a spec claiming to be another service, a status nobody\n * defined) is a bug, and gates.\n */\nimport { spawn } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport { existsSync } from \"node:fs\";\nimport { readdir, readFile, realpath, stat } from \"node:fs/promises\";\nimport { join, relative, resolve, sep } from \"node:path\";\nimport {\n listField,\n parseFrontmatter,\n rawBody,\n readFrontmatter,\n stringField,\n FEATURE_STATUSES,\n SERVICE_STATUSES,\n type Frontmatter,\n} from \"./frontmatter.js\";\nimport type { Finding } from \"./report.js\";\nimport { featurePaths, servicePaths, SPEC_AXES } from \"./repo.js\";\nimport { isPathInside, resolveInside } from \"./path-safety.js\";\n\n/** Fields every artifact is expected to carry, beyond its identity and status. */\nconst EXPECTED = [\"owner\"] as const;\n\nexport interface ProvenanceOptions {\n /**\n * The repo `sources` resolve against — the service repo loam is running in.\n * Undefined when the docs being validated describe some other repository, in\n * which case the paths are not ours to resolve and are left alone.\n */\n repoDir?: string;\n}\n\nexport async function serviceProvenance(\n docsDir: string,\n service: string,\n opts: ProvenanceOptions = {},\n): Promise<Finding[]> {\n const paths = servicePaths(docsDir, service);\n const findings: Finding[] = [];\n // Both requirement-carrying specs get the same pass: arch.spec.md follows\n // spec.md's frontmatter conventions exactly, so the checks are one loop —\n // only the label tells a reader which file moved. The pair comes from\n // SPEC_AXES rather than being respelled, so a third axis is graded here the\n // day it is declared instead of the day somebody notices.\n for (const { path, file } of SPEC_AXES.map((axis) => ({ path: paths[axis.key], file: axis.file }))) {\n if (!existsSync(path)) continue;\n const raw = await readFile(path, \"utf8\");\n const fm = parseFrontmatter(raw);\n const label = `${service}: ${file}`;\n findings.push(\n ...identityFindings(fm, {\n label,\n idField: \"service\",\n idValue: service,\n statuses: [...SERVICE_STATUSES],\n }),\n );\n // With no frontmatter at all, \"it names no sources\" says nothing the missing\n // header did not already say — and a malformed one proves nothing either:\n // `sources.absent` against a header nobody can read is the same false\n // advice as the field cascade, so frontmatter.malformed stands alone.\n if (fm.present && !fm.malformed) {\n findings.push(...(await sourceFindings(fm, service, label, opts.repoDir)));\n findings.push(...contentFindings(fm, raw, service, label));\n }\n }\n return findings;\n}\n\nexport async function featureProvenance(\n featureDir: string,\n featureId: string,\n): Promise<Finding[]> {\n const path = featurePaths(featureDir).intent;\n if (!existsSync(path)) return [];\n const fm = await readFrontmatter(path);\n return identityFindings(fm, {\n label: \"intent.md\",\n idField: \"feature\",\n idValue: featureId,\n statuses: [...FEATURE_STATUSES],\n });\n}\n\ninterface IdentitySpec {\n label: string;\n idField: \"service\" | \"feature\";\n idValue: string;\n statuses: string[];\n}\n\nfunction identityFindings(fm: Frontmatter, spec: IdentitySpec): Finding[] {\n const findings: Finding[] = [];\n if (!fm.present) {\n return [\n {\n severity: \"warn\",\n code: \"frontmatter.missing\",\n subject: spec.idValue,\n message: `${spec.label} has no frontmatter — no owner, no status, no link to the code it describes`,\n },\n ];\n }\n\n // One honest error instead of the cascade: with an unreadable header the\n // field checks below would grade owner/status/service \"missing\" and send the\n // author adding fields to a block YAML refuses to parse. Nothing can be\n // concluded from a header nobody can read — including that it lacks fields.\n if (fm.malformed) {\n return [\n {\n severity: \"error\",\n code: \"frontmatter.malformed\",\n subject: spec.idValue,\n message: `${spec.label} has frontmatter that does not parse as YAML — owner, status and sources are unreadable until the header is fixed`,\n },\n ];\n }\n\n const id = stringField(fm, spec.idField);\n if (id !== undefined && id !== spec.idValue) {\n findings.push({\n severity: \"error\",\n code: \"frontmatter.field-mismatch\",\n subject: spec.idValue,\n message: `${spec.label} declares ${spec.idField}: ${id}, but it lives under ${spec.idValue}`,\n });\n }\n\n const status = stringField(fm, \"status\");\n if (status !== undefined && !spec.statuses.includes(status)) {\n findings.push({\n severity: \"error\",\n code: \"frontmatter.status-unknown\",\n subject: spec.idValue,\n message: `${spec.label} has status '${status}' — expected one of ${spec.statuses.join(\", \")}`,\n });\n }\n\n const missing = [\n ...(id === undefined ? [spec.idField] : []),\n ...(status === undefined ? [\"status\"] : []),\n ...EXPECTED.filter((f) => stringField(fm, f) === undefined),\n ];\n if (missing.length > 0) {\n findings.push({\n severity: \"warn\",\n code: \"frontmatter.field-missing\",\n subject: spec.idValue,\n message: `${spec.label} is missing ${missing.length} frontmatter field(s)`,\n details: missing,\n text: { detailPrefix: \"- \" },\n });\n }\n return findings;\n}\n\nasync function sourceFindings(\n fm: Frontmatter,\n service: string,\n label: string,\n repoDir: string | undefined,\n): Promise<Finding[]> {\n const sources = listField(fm, \"sources\");\n if (sources.length === 0) {\n return [\n {\n severity: \"warn\",\n code: \"sources.absent\",\n message: `${label} names no sources — nothing ties it to the code, so nothing can tell you when it goes out of date`,\n },\n ];\n }\n // Someone else's repository: the paths describe a tree loam is not standing in.\n if (repoDir === undefined) return [];\n\n // Before existence: a pattern entry would \"not exist\" as a literal path, and\n // grading it missing would send the author fixing the wrong thing.\n const patterns = patternSources(sources);\n if (patterns.length > 0) {\n return [\n {\n severity: \"error\",\n code: \"sources.path-missing\",\n message: `${label}: ${patterns.length} source(s) are glob patterns — ${patterns.join(\", \")}. Patterns are no longer supported: name files or directories (a directory already covers everything beneath it).`,\n details: patterns,\n text: { detailPrefix: \"- \" },\n },\n ];\n }\n\n const unsafe = unsafeSources(repoDir, sources);\n if (unsafe.length > 0) {\n return [\n {\n severity: \"error\",\n code: \"sources.path-outside\",\n message: `${label}: ${unsafe.length} source(s) escape the service repo — ${unsafe.join(\", \")}. Sources must be relative paths contained by this repository, including through symlinks.`,\n details: unsafe,\n text: { detailPrefix: \"- \" },\n },\n ];\n }\n\n const missing = missingSources(repoDir, sources);\n if (missing.length > 0) {\n return [\n {\n severity: \"error\",\n code: \"sources.path-missing\",\n message: `${label}: ${missing.length} source(s) do not exist — ${missing.join(\", \")}`,\n details: missing,\n text: { detailPrefix: \"- \" },\n },\n ];\n }\n const resolved: Finding = {\n severity: \"ok\",\n code: \"sources.resolved\",\n message: `${label}: ${sources.length} source(s) resolve`,\n };\n\n // The walk happens here rather than inside the staleness branch below, even\n // though only staleness needs the hashes: what the walk REFUSED to follow is\n // a hole in the tie to the code whether or not anybody has vouched yet, and\n // an unvouched service is exactly where a reader is about to decide the\n // sources list is honest.\n const { digest, files, index, skipped } = await sourcesDigest(repoDir, sources);\n const findings: Finding[] = [resolved, ...skippedFindings(skipped, label)];\n\n // The paths are there; the question staleness answers is whether what is AT\n // them is still what somebody read.\n const stamped = stringField(fm, \"sources_digest\");\n if (stamped === undefined) {\n return [\n ...findings,\n {\n severity: \"warn\",\n code: \"sources.unvouched\",\n message: `${label}: no sources_digest — nobody has vouched for this against the code, so nothing can tell you when it goes stale. Run \\`loam vouch --service ${service}\\`.`,\n },\n ];\n }\n\n const since = stringField(fm, \"last_verified\") ?? \"it was stamped\";\n if (digest === stamped) {\n return [\n ...findings,\n {\n severity: \"ok\",\n code: \"sources.current\",\n message: `${label}: sources unchanged since ${since} (${files.length} file(s), digest ${digest})`,\n },\n ];\n }\n\n // What MOVED, when the stamp recorded enough to say. Repeating the frontmatter's\n // own `sources` entries back at a reader — which is all this finding could do\n // before `sources_files` existed — answers a question nobody asked: they wrote\n // that list, and \"one of these five directories changed somehow\" is where the\n // search starts, not where it ends.\n const moved = movedSources(decodeSourceIndex(stringField(fm, \"sources_files\")), index);\n return [\n ...findings,\n {\n // A warning, not an error: the doc may still be right, and only a person\n // can say. What loam knows is that nobody has looked since the code moved.\n severity: \"warn\",\n code: \"sources.stale\",\n message: `${label}: sources changed since ${since}${moved.summary} — re-read them and \\`loam vouch --service ${service}\\``,\n details: moved.paths ?? sources,\n text: { detailPrefix: \"- \" },\n },\n ];\n}\n\n/**\n * The paths the digest walk would not follow. Never silent: a symlink dropped\n * on the floor is a file whose content nobody is watching, while the document\n * beside it says `verified` — the digest cannot go stale over bytes it never\n * hashed, so the one thing the stamp promises is quietly untrue for them.\n *\n * A warning, not an error, and for the same reason staleness is: the doc may be\n * perfectly right about the code behind that link. What loam can say is that it\n * is not watching it.\n */\nfunction skippedFindings(skipped: SkippedSource[], label: string): Finding[] {\n if (skipped.length === 0) return [];\n return [\n {\n severity: \"warn\",\n code: \"sources.skipped\",\n message: `${label}: ${skipped.length} path(s) under the listed sources were not hashed — the digest says nothing about what is behind them`,\n details: skipped.map((s) => `${s.path} — ${s.reason}`),\n text: { detailPrefix: \"- \" },\n },\n ];\n}\n\n/**\n * The doc-side half of the freshness check. `sources_digest` says whether the\n * CODE moved since a person looked, and only that service's repo can recompute\n * it; `content_digest` says whether the DOCUMENT did, and needs nothing but\n * the document — so this runs wherever the spec is readable, `--service` and\n * `--all` alike. Without it, editing a spec after it was vouched left\n * `status: verified` standing over words nobody read — forged freshness, in\n * the exact agent-written-prose threat model this layer exists for.\n */\nfunction contentFindings(fm: Frontmatter, raw: string, service: string, label: string): Finding[] {\n if (stringField(fm, \"status\") !== \"verified\") return [];\n const stamped = stringField(fm, \"content_digest\");\n // A verified doc with no stamp predates the field. Quiet on purpose: the fix\n // is re-vouching, and grading years of pre-feature vouches as suspect would\n // drown the fleet in warnings nobody chose.\n if (stamped === undefined) return [];\n if (contentDigest(raw) === stamped) return [];\n const since = stringField(fm, \"last_verified\") ?? \"it was vouched\";\n return [\n {\n // A warning, not an error — the sources.stale doctrine: the doc changed\n // since it was vouched, and only a person can say whether verified still\n // holds of the new words.\n severity: \"warn\",\n code: \"content.stale\",\n message: `${label} changed since ${since} — the doc moved under its vouch, and only a person can say whether 'verified' still holds. Re-read it and \\`loam vouch --service ${service}\\`.`,\n },\n ];\n}\n\n/**\n * The local calendar day, for the two commands that date a stamp. A vouch or a\n * verification record is a person saying \"today I read this\", so it is their\n * date, not UTC's — `toISOString` files an evening in the Americas under\n * tomorrow. It sits here because this module already owns the stamp vocabulary\n * the date is written into.\n */\nexport function today(now: Date): string {\n const pad = (n: number): string => String(n).padStart(2, \"0\");\n return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;\n}\n\n/* ------------------------------------------------------------------ */\n/* The content digest */\n/* ------------------------------------------------------------------ */\n\n/** How much of the sha256 is written into the document. */\nconst DIGEST_LENGTH = 16;\n\n/** How many source files the digest reads at once — see `sourcesDigest`. */\nconst DIGEST_READ_BATCH = 32;\n\nexport interface SourcesDigest extends SourcesExpansion {\n /** The stamp that goes in `sources_digest`. */\n digest: string;\n /**\n * Per file, its own short sha — the record `sources_files` carries so a later\n * `sources.stale` can name what moved instead of guessing. Same order as `files`.\n */\n index: SourceIndexEntry[];\n}\n\n/** One file's share of the stamp: what it is called, and what it said. */\nexport interface SourceIndexEntry {\n /** Repo-relative, `/`-separated. */\n path: string;\n /** sha256 of its bytes, truncated the way `sources_digest` is. */\n sha: string;\n}\n\n/** A path the walk found but would not hash, and the reason a reader needs. */\nexport interface SkippedSource {\n /** Repo-relative, `/`-separated. */\n path: string;\n /** Why it was left out — the half of the sentence after the path. */\n reason: string;\n}\n\n/** What a `sources` list covers, and what it does not. */\nexport interface SourcesExpansion {\n /** Repo-relative paths, sorted — exactly what a digest would hash. */\n files: string[];\n /** Everything the walk refused to follow. Empty in the ordinary case. */\n skipped: SkippedSource[];\n /**\n * Set only when `files` is empty: the one sentence that says the list covers\n * nothing. `loam vouch` refuses with it, `loam validate` grades it — see\n * emptySourcesMessage for why they must be the same words.\n */\n empty?: string;\n}\n\n/**\n * Digest the CONTENT of the files `sources` names. The recipe is spelled out\n * because the value is written into documents, and anything reading them has to\n * be able to reproduce it:\n *\n * 1. expand each entry to repo-relative file paths (`/` separators) — a file\n * is itself, a directory everything beneath it, minus what the repository\n * does not consider its own (see collectSources) — sorted and de-duplicated;\n * 2. per file, `sha256(bytes)`;\n * 3. feed `<path>\\0<hex>\\n` for each file, in that order, into an outer\n * sha256;\n * 4. keep the first 16 hex characters.\n *\n * Content, not mtime: git does not preserve modification times, so after a\n * fresh clone every file looks changed and the check would be false positives\n * end to end. Bytes survive the clone. Hashing the path alongside the content\n * means a rename registers, which is what a reader of the doc would want.\n *\n * 64 bits is a change detector, not a seal — it answers \"did this move?\", and\n * an adversary who wants a collision can have one.\n */\nexport async function sourcesDigest(repoDir: string, sources: string[]): Promise<SourcesDigest> {\n const { found, skipped } = await collectSources(repoDir, sources);\n const outer = createHash(\"sha256\");\n const index: SourceIndexEntry[] = [];\n // Read in fixed batches, hash in array order. `found` is already sorted and\n // every file's own sha depends on nothing but its own bytes, so the sequence\n // fed to `outer` — and therefore the digest — is unchanged by construction;\n // only the waiting overlaps. A `sources: [src/]` over a real service repo is\n // thousands of files, and one-await-per-file spent nearly all of its time\n // idle. The batch is bounded rather than a single `Promise.all` over the\n // whole list because an unbounded fan-out over a source tree runs the process\n // out of file descriptors, which would turn a stamp into an EMFILE.\n for (let start = 0; start < found.length; start += DIGEST_READ_BATCH) {\n const hashed = await Promise.all(\n found.slice(start, start + DIGEST_READ_BATCH).map(async (file) => ({\n rel: file.rel,\n content: createHash(\"sha256\").update(await readFile(file.abs)).digest(\"hex\"),\n })),\n );\n for (const { rel, content } of hashed) {\n outer.update(`${rel}\\0${content}\\n`);\n index.push({ path: rel, sha: content.slice(0, DIGEST_LENGTH) });\n }\n }\n // `empty` is deliberately not set here: it is a sentence about a named\n // document, and a digest has no name to put in it. Callers that need it say\n // emptySourcesMessage(label, sources) — the same words, from one definition.\n return { digest: outer.digest(\"hex\").slice(0, DIGEST_LENGTH), files: index.map((e) => e.path), index, skipped };\n}\n\n/**\n * What a `sources` list actually covers — the digest's file set without the\n * hashing.\n *\n * Exported because two commands must give the SAME answer to \"does this list\n * cover anything?\". `loam vouch` refuses to stamp an expansion that covers no\n * files (a digest over nothing never changes, so the stamp would read as\n * current forever), and until `loam validate` could say the same thing in the\n * same words, an author got a green validate followed by a vouch that refused —\n * two commands contradicting each other about one document, with nothing in the\n * green run hinting at it.\n */\nexport async function expandSourceFiles(\n repoDir: string,\n sources: string[],\n label: string,\n): Promise<SourcesExpansion> {\n const { found, skipped } = await collectSources(repoDir, sources);\n const files = found.map((f) => f.rel);\n return { files, skipped, ...(files.length === 0 ? { empty: emptySourcesMessage(label, sources) } : {}) };\n}\n\n/**\n * The one sentence for \"these paths exist and cover no file\". One definition,\n * because `vouch`'s refusal and `validate`'s finding are the same diagnosis\n * about the same document and must not drift into two descriptions of it — the\n * author fixes it once, in the sources list.\n */\nexport function emptySourcesMessage(label: string, sources: string[]): string {\n return `${label}: the sources listed match no files — ${sources.join(\", \")}. A digest over nothing would read as current forever.`;\n}\n\n/**\n * The stamp `loam vouch` writes into `content_digest`: sha256 of the\n * document's own BODY — every byte after the frontmatter block (below the\n * closing `---` line and its newline; `rawBody` is the one definition of that\n * cut) — first 16 hex characters, the sources recipe's length.\n *\n * Byte-exact, no normalization. Body-only is load-bearing: vouch itself\n * rewrites the frontmatter as it stamps, and a later frontmatter-only edit\n * (another vouch, a corrected owner) must not read as the document moving.\n */\nexport function contentDigest(source: string): string {\n return createHash(\"sha256\").update(rawBody(source), \"utf8\").digest(\"hex\").slice(0, DIGEST_LENGTH);\n}\n\ninterface SourceFile {\n /** Repo-relative, `/`-separated — the spelling that goes into the hash. */\n rel: string;\n abs: string;\n}\n\n/**\n * The files a `sources` list names: a file is itself, a directory is everything\n * beneath it. Nothing else — glob patterns used to be matched here, by a\n * hand-rolled dialect that silently differed from the gitignore/minimatch\n * conventions authors assume, so a pattern quietly digested a DIFFERENT file\n * set than its author intended. Pattern-looking entries are now refused loudly\n * upstream (see patternSources); this function only ever sees literal paths.\n *\n * Three exclusions, all part of the digest recipe's contract:\n *\n * - dot-entries are skipped while walking — `.git` is not what the doc was\n * written from — though a path naming one outright is still honoured;\n * - `node_modules` is skipped the same way, unconditionally: it is the one\n * build input every ecosystem agrees is not source, and it is the one that\n * makes the walk take minutes;\n * - anything the repository's own `.gitignore` covers is dropped, when the\n * repo is a git checkout and git is on the PATH (see gitIgnoredPaths).\n *\n * The third is the one worth stating a reason for. `sources_digest` answers\n * \"did the code move since a person read it\", and BUILD OUTPUT moves on every\n * CI run without anybody touching the code — a `sources: [src/]` over a repo\n * that compiles into `src/generated/` went stale on a schedule, so the warning\n * that means \"re-read this\" arrived when nothing had been written. A signal\n * that fires every night is a signal people learn to close. Git's answer is\n * used rather than a list of our own because it is the answer the repository\n * already gives every other tool, and it is the author's to change.\n *\n * The fallback is deliberate: no git, no checkout, a git that errors — hash\n * everything. Missing an exclusion costs noise; inventing one costs a file the\n * stamp silently stops watching, and this walk exists to have nothing in that\n * category.\n */\nasync function collectSources(\n repoDir: string,\n sources: string[],\n): Promise<{ found: SourceFile[]; skipped: SkippedSource[] }> {\n const found = new Map<string, string>();\n const skipped = new Map<string, string>();\n const relOf = (abs: string): string => relative(repoDir, abs).split(sep).join(\"/\");\n\n // realpath once, up front: every containment question below is asked against\n // the resolved root, so a repo reached through a symlink (a worktree under\n // /var -> /private/var on macOS, say) does not make its own files look external.\n const repoReal = await realpath(repoDir).catch(() => resolve(repoDir));\n // The realpaths of the directories currently being walked — the cycle guard.\n // A STACK rather than a set of everything ever entered, because the two rules\n // answer different questions: a directory reachable by two spellings (a\n // `current -> versions/3` link beside the real tree) is content under both\n // names and belongs in the digest twice, while a directory reachable from\n // INSIDE itself is a loop. A global visited-set conflates them, and then which\n // spelling survives depends on the order readdir happened to return — a\n // digest that differs between two machines holding identical bytes.\n const walking: string[] = [];\n\n const walk = async (dir: string): Promise<void> => {\n const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);\n // Sorted, so a tree walks the same way on every filesystem.\n entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));\n for (const entry of entries) {\n if (entry.name.startsWith(\".\") || entry.name === \"node_modules\") continue;\n const child = join(dir, entry.name);\n if (entry.isDirectory()) await enterDir(child);\n else if (entry.isFile()) found.set(relOf(child), child);\n // readdir reports symlinks by lstat, so isDirectory() and isFile() are\n // BOTH false for one: before this branch existed a symlinked file, and a\n // whole subtree behind a symlinked directory, fell between the two arms\n // and left the digest without a word said. That is the failure this\n // module is here to prevent, arriving through its own walk.\n else if (entry.isSymbolicLink()) await follow(child);\n }\n };\n\n const enterDir = async (dir: string): Promise<void> => {\n const real = await realpath(dir).catch(() => null);\n if (real === null || walking.includes(real)) return;\n walking.push(real);\n try {\n await walk(dir);\n } finally {\n walking.pop();\n }\n };\n\n const follow = async (link: string): Promise<void> => {\n const real = await realpath(link).catch(() => null);\n if (real === null) {\n skipped.set(relOf(link), \"a symlink that does not resolve\");\n return;\n }\n // Outside the repo the file is not this service's to vouch for, and the\n // same rule already refuses it as a top-level `sources` entry\n // (sources.path-outside). Reported rather than dropped: an author who\n // vendored a sibling repo through a symlink needs to know the stamp stops\n // at the link.\n if (!isPathInside(repoReal, real)) {\n skipped.set(relOf(link), \"a symlink whose target is outside this repository\");\n return;\n }\n const info = await stat(link).catch(() => null);\n if (info === null) {\n skipped.set(relOf(link), \"a symlink that does not resolve\");\n return;\n }\n // A link's own spelling is what the doc's author wrote, so that is the path\n // that goes into the digest — the content comes from the target either way.\n if (info.isFile()) found.set(relOf(link), link);\n // A link that closes a loop is not reported: everything behind it is already\n // in the digest under the spelling it was reached by, so nothing has stopped\n // being watched. Only unhashed CONTENT is worth a warning.\n else if (info.isDirectory()) await enterDir(link);\n else skipped.set(relOf(link), \"a symlink to neither a file nor a directory\");\n };\n\n for (const source of sources) {\n const cleaned = source.trim();\n if (cleaned.length === 0) continue;\n const root = resolveInside(repoDir, cleaned, `source '${source}'`);\n const info = await stat(root).catch(() => null);\n if (info === null) continue;\n if (info.isFile()) found.set(relOf(root), root);\n else if (info.isDirectory()) await enterDir(root);\n }\n\n // One git invocation for the whole expansion, not one per file: the skipped\n // list rides along so an ignored symlink does not warn about a path the\n // repository already says is not its own.\n const ignored = await gitIgnoredPaths(repoDir, [...found.keys(), ...skipped.keys()]);\n for (const rel of ignored) {\n found.delete(rel);\n skipped.delete(rel);\n }\n\n return {\n // Plain codepoint order, not locale order: the digest has to be the same\n // everywhere it is computed.\n found: [...found.entries()]\n .map(([rel, abs]) => ({ rel, abs }))\n .sort((a, b) => (a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0)),\n skipped: [...skipped.entries()]\n .map(([path, reason]) => ({ path, reason }))\n .sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0)),\n };\n}\n\n/** How long the digest waits on git before deciding it learned nothing. */\nconst GIT_TIMEOUT_MS = 10_000;\n\n/**\n * Which of these repo-relative paths the repository ignores, asked once for the\n * whole list. `git check-ignore` is the authority on purpose — it reads\n * `.gitignore` at every level, `.git/info/exclude` and the user's global\n * excludes, and it already knows that a TRACKED file is not ignored no matter\n * what a pattern says, which is the distinction a re-implementation always gets\n * wrong.\n *\n * Every failure answers \"nothing is ignored\": exit 1 is git saying so, exit 128\n * is \"not a git repository\", a spawn error is git not being installed, and a\n * timeout is a repository too strange to wait for. None of them justify\n * dropping a file from a digest a person is going to sign.\n */\nasync function gitIgnoredPaths(repoDir: string, rels: string[]): Promise<Set<string>> {\n if (rels.length === 0) return new Set();\n return new Promise<Set<string>>((done) => {\n const child = spawn(\"git\", [\"check-ignore\", \"--stdin\", \"-z\"], {\n cwd: repoDir,\n stdio: [\"pipe\", \"pipe\", \"ignore\"],\n timeout: GIT_TIMEOUT_MS,\n });\n let out = \"\";\n child.stdout.setEncoding(\"utf8\");\n child.stdout.on(\"data\", (chunk: string) => {\n out += chunk;\n });\n child.on(\"error\", () => done(new Set()));\n child.on(\"close\", (code) => {\n // 0: some paths are ignored and are on stdout. 1: none are — an empty\n // answer, not a failure. Anything else: we learned nothing.\n done(code === 0 || code === 1 ? new Set(out.split(\"\\0\").filter((s) => s.length > 0)) : new Set());\n });\n // A git that died before reading its input hands us EPIPE; the close\n // handler above is what decides the outcome either way.\n child.stdin.on(\"error\", () => undefined);\n child.stdin.end(rels.join(\"\\0\") + \"\\0\");\n });\n}\n\n/**\n * Who git says is working in this repository — `Name <email>`, or null when git\n * cannot answer.\n *\n * This is the only identity loam has, and it is deliberately a weak one: git\n * config is a text file anybody can edit, so `vouched_by` is a name, not a\n * signature, and it proves nothing cryptographically. What it does is make the\n * claim ATTRIBUTABLE — a `status: verified` with nobody's name against it says\n * only that some process wrote the word, which is exactly the state that let a\n * vouch be indistinguishable from an agent's own draft. A reviewer reading the\n * frontmatter, or `git log` on the docs repo, can now ask a specific person what\n * they read. Signing is the real answer and it is not this one; nothing here\n * pretends otherwise.\n *\n * `user.email` is what is required — it is the field git itself refuses to\n * commit without, so a repository where a person has ever committed has one —\n * and `user.name` rides along when it is set.\n */\nexport async function gitIdentity(repoDir: string): Promise<string | null> {\n // The environment first, because that is git's own precedence: GIT_COMMITTER_*\n // overrides `user.*` for the identity git records, and GIT_AUTHOR_* behind it.\n // Reading them is loam agreeing with git rather than inventing a second rule —\n // and it is the only identity a CI job or a container has, where there is no\n // `user.email` anywhere and running `git config --global` first would be a\n // step nobody documents.\n const fromEnv = identity(\n process.env.GIT_COMMITTER_NAME ?? process.env.GIT_AUTHOR_NAME,\n process.env.GIT_COMMITTER_EMAIL ?? process.env.GIT_AUTHOR_EMAIL,\n );\n if (fromEnv !== null) return fromEnv;\n const [name, email] = await Promise.all([\n gitConfig(repoDir, \"user.name\"),\n gitConfig(repoDir, \"user.email\"),\n ]);\n return identity(name ?? undefined, email ?? undefined);\n}\n\n/** `Name <email>`, or the bare email, or null — the one spelling of the stamp. */\nfunction identity(name: string | undefined, email: string | undefined): string | null {\n const e = email?.trim();\n if (e === undefined || e === \"\") return null;\n const n = name?.trim();\n return n === undefined || n === \"\" ? e : `${n} <${e}>`;\n}\n\n/**\n * One `git config --get` value, or null for every way it can fail to be one:\n * unset (exit 1), not a repository, git not installed, a timeout. The caller\n * distinguishes none of them, and should not — they all mean \"git will not tell\n * us who this is\", and the fix is the same sentence in each case.\n */\nasync function gitConfig(repoDir: string, key: string): Promise<string | null> {\n return new Promise<string | null>((done) => {\n const child = spawn(\"git\", [\"config\", \"--get\", key], {\n cwd: repoDir,\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n timeout: GIT_TIMEOUT_MS,\n });\n let out = \"\";\n child.stdout.setEncoding(\"utf8\");\n child.stdout.on(\"data\", (chunk: string) => {\n out += chunk;\n });\n child.on(\"error\", () => done(null));\n child.on(\"close\", (code) => {\n const value = out.trim();\n done(code === 0 && value !== \"\" ? value : null);\n });\n });\n}\n\n/* ------------------------------------------------------------------ */\n/* The per-file index: what `sources.stale` names */\n/* ------------------------------------------------------------------ */\n\n/**\n * How many files `sources_files` will list one by one before it gives up and\n * records only how many there were.\n *\n * A readability budget, not a correctness one: the index lives in the header of\n * a document a person reads, and a `sources: [src/]` over a legacy service can\n * expand to thousands of files. Past the limit `sources.stale` falls back to\n * repeating the `sources` entries — worse advice, but a spec.md whose\n * frontmatter is longer than its requirements is worse still.\n */\nconst SOURCE_INDEX_LIMIT = 100;\n\n/**\n * The `sources_files` value `loam vouch` stamps beside `sources_digest`: one\n * `<sha> <path>` line per file (sha256sum's layout, and its column order, so\n * the path may contain spaces), or just the file count once the list would run\n * past SOURCE_INDEX_LIMIT.\n *\n * Its whole purpose is the next `sources.stale`. `sources_digest` can say THAT\n * the code moved and never which part of it; with the index a reader gets the\n * added, removed and changed paths, which is the difference between re-reading\n * one file and re-reading a directory. It is also a readable git diff in the\n * docs repo: the vouch commit shows exactly which source files the stamp now\n * stands over.\n */\nexport function encodeSourceIndex(index: SourceIndexEntry[]): string {\n if (index.length > SOURCE_INDEX_LIMIT) return String(index.length);\n return index.map((e) => `${e.sha} ${e.path}`).join(\"\\n\");\n}\n\n/** What a stamped `sources_files` says: the per-file shas, the count, or nothing. */\ninterface StampedIndex {\n /** path -> sha, or null when the stamp did not record them. */\n entries: Map<string, string> | null;\n /** How many files the stamp covered, when that is knowable. */\n count?: number;\n}\n\n/**\n * Read back what `encodeSourceIndex` wrote. Anything unrecognised reads as \"the\n * stamp said nothing\" rather than as an empty index: a header nobody can parse\n * must not turn into a `sources.stale` claiming every file was deleted.\n */\nfunction decodeSourceIndex(stamped: string | undefined): StampedIndex {\n if (stamped === undefined) return { entries: null };\n const text = stamped.trim();\n if (text.length === 0) return { entries: null };\n if (/^\\d+$/.test(text)) return { entries: null, count: Number(text) };\n\n const entries = new Map<string, string>();\n for (const line of text.split(\"\\n\")) {\n const match = /^([0-9a-f]{16})\\s+(.+)$/.exec(line.trim());\n if (match === null) return { entries: null };\n entries.set(match[2]!, match[1]!);\n }\n return { entries, count: entries.size };\n}\n\n/** The paths that moved between a stamp and now, with a phrase for the message. */\ninterface MovedSources {\n /** Annotated paths, or undefined when the stamp did not record enough to say. */\n paths?: string[];\n /** Appended to the `sources.stale` message; empty when there is nothing to add. */\n summary: string;\n}\n\n/**\n * added / removed / changed, from the stamped index against the current one.\n * The annotation travels IN the detail line rather than in three separate\n * lists: a reader scanning a stale report wants the paths in one column, and\n * \"which of the three\" is one word wide.\n */\nfunction movedSources(stamp: StampedIndex, now: SourceIndexEntry[]): MovedSources {\n if (stamp.entries === null) {\n // No index, but the count alone still beats silence: \"12 files then, 14\n // now\" tells a reader to go looking for two new files.\n return {\n summary: stamp.count === undefined ? \"\" : ` (${stamp.count} file(s) then, ${now.length} now)`,\n };\n }\n const paths: string[] = [];\n const current = new Map(now.map((e) => [e.path, e.sha]));\n for (const [path, sha] of current) {\n const before = stamp.entries.get(path);\n if (before === undefined) paths.push(`added ${path}`);\n else if (before !== sha) paths.push(`changed ${path}`);\n }\n for (const path of stamp.entries.keys()) if (!current.has(path)) paths.push(`removed ${path}`);\n paths.sort();\n // An index that agrees with the current tree while the digest does not is a\n // contradiction (a truncation collision, or a hand-edited header). Say\n // nothing rather than print an empty list under a staleness warning.\n if (paths.length === 0) return { summary: \"\" };\n return { paths, summary: ` (${paths.length} path(s) moved)` };\n}\n\n/** The characters that made an entry a pattern under the glob dialect loam no longer ships. */\nconst PATTERN_CHARS = /[*?[]/;\n\n/**\n * The `sources` entries that look like glob patterns — anything containing\n * `*`, `?` or `[`. Patterns are refused loudly everywhere sources are consumed\n * (`loam vouch` refuses the run, `loam validate` grades an error), never\n * resolved: the glob dialect loam used to ship silently differed from the\n * gitignore/minimatch conventions authors assume — bracket classes literal,\n * its own `**` handling — so a pattern quietly digested a different file set\n * than intended, corrupting the staleness signal in both directions. A\n * directory already means \"everything beneath it\", so the fix is to name one.\n *\n * The cost accepted with the rule: a real file with `[` in its name (a Next.js\n * route, say) cannot be listed on its own — its parent directory covers it.\n */\nexport function patternSources(sources: string[]): string[] {\n return sources.filter((s) => PATTERN_CHARS.test(s.trim()));\n}\n\n/** The `sources` entries that resolve to nothing in this repo. */\nexport function missingSources(repoDir: string, sources: string[]): string[] {\n return sources.filter((s) => !sourceExists(repoDir, s));\n}\n\n/** Entries that are absolute, traverse upward, or escape through a symlink. */\nexport function unsafeSources(repoDir: string, sources: string[]): string[] {\n return sources.filter((source) => {\n const cleaned = source.trim();\n if (cleaned.length === 0) return false;\n try {\n resolveInside(repoDir, cleaned, `source '${source}'`);\n return false;\n } catch {\n return true;\n }\n });\n}\n\n/** Does a `sources` entry point at something real? Literal paths only, checked exactly. */\nfunction sourceExists(repoDir: string, source: string): boolean {\n const cleaned = source.trim();\n if (cleaned.length === 0) return false;\n try {\n return existsSync(resolveInside(repoDir, cleaned, `source '${source}'`));\n } catch {\n return false;\n }\n}\n"]}
package/dist/core/repo.js CHANGED
@@ -97,6 +97,7 @@ export function servicePaths(docsDir, service) {
97
97
  spec: join(dir, "spec.md"),
98
98
  archSpec: join(dir, "arch.spec.md"),
99
99
  openapi: join(dir, "openapi.yaml"),
100
+ asyncapi: join(dir, "asyncapi.yaml"),
100
101
  runbook: join(dir, "runbook.md"),
101
102
  health: join(dir, "health.yaml"),
102
103
  adrsDir: join(dir, "adrs"),
@@ -297,6 +298,7 @@ export async function listServices(docsDir, context) {
297
298
  model: existsSync(p.model),
298
299
  spec: existsSync(p.spec),
299
300
  openapi: existsSync(p.openapi),
301
+ asyncapi: existsSync(p.asyncapi),
300
302
  runbook: existsSync(p.runbook),
301
303
  health: existsSync(p.health),
302
304
  },