import { createHash } from "node:crypto"; import { readFile } from "node:fs/promises"; import { resolve } from "node:path"; import { knowledgeContentDigest, knowledgeRoute, loadKnowledgeGraph, type KnowledgeEntry, } from "../src/knowledge.ts"; import { loadKnowledgePolicy, scanKnowledgeDraft } from "./knowledge-policy.ts"; interface StyleFinding { severity: "review"; code: string; detail: string; } async function verifyTechnicalAuthorization(entry: KnowledgeEntry): Promise> { if (entry.reviewBasis !== "technical-publication-authorization") return []; const authorization = entry.publicationAuthorization; if (!authorization) { return [{ slug: entry.slug, severity: "block", code: "missing-technical-authorization", detail: "technical publication entry has no route-scoped authorization metadata", }]; } try { const receiptBytes = await readFile(resolve(process.cwd(), authorization.receiptPath)); const digest = `sha256:${createHash("sha256").update(receiptBytes).digest("hex")}`; const receipt = JSON.parse(receiptBytes.toString("utf8")) as Record; const recordedAt = typeof receipt.recordedAt === "string" ? new Date(receipt.recordedAt).toISOString() : undefined; const valid = digest === authorization.receiptDigest && digest === entry.reviewReceiptDigest && receipt.kind === authorization.kind && receipt.entrySlug === entry.slug && receipt.route === knowledgeRoute(entry) && receipt.authorizedBy === authorization.authorizedBy && recordedAt === authorization.recordedAt.toISOString() && receipt.scope === authorization.scope && receipt.exactRenderReviewed === false && receipt.implementationReviewedBy === entry.implementationReviewedBy; return valid ? [] : [{ slug: entry.slug, severity: "block", code: "technical-authorization-receipt-mismatch", detail: "tracked authorization receipt bytes or fields do not match approved frontmatter", }]; } catch (error) { return [{ slug: entry.slug, severity: "block", code: "technical-authorization-receipt-unreadable", detail: error instanceof Error ? error.message : String(error), }]; } } function scanKnowledgeStyle(entry: Awaited>["entries"][number]): StyleFinding[] { const findings: StyleFinding[] = []; const body = entry.body.trim(); const referenceLike = entry.kind !== "journal" && entry.kind !== "lesson"; if (entry.kind !== "journal" && /^#{1,6}\s/.test(body)) { findings.push({ severity: "review", code: "missing-standalone-lead", detail: "non-journal page begins with a heading instead of a self-contained prose lead", }); } const editorialHeadings = [ "Questions to expand", "Why this topic now", "Short version", "Boundary-check note", ]; for (const heading of editorialHeadings) { if (new RegExp(`^#{2,6}\\s+${heading.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\$&")}\\s*$`, "im").test(body)) { findings.push({ severity: "review", code: "visible-editorial-scaffolding", detail: `reader-facing editorial heading: ${heading}`, }); } } const lead = body.split(/^#{2,6}\s/m, 1)[0]; if (referenceLike && /\b(?:I|me|my|mine|we|us|our|ours)\b/i.test(lead)) { findings.push({ severity: "review", code: "first-person-reference-lead", detail: "reference-style lead uses first-person voice; attribute the perspective or use neutral prose", }); } if ( entry.kind !== "journal" && entry.related.length > 0 && !/\[[^\]]+\]\(\/knowledge\/[a-z0-9-]+(?:\/[a-z0-9-]+)*\)/.test(body) ) { findings.push({ severity: "review", code: "metadata-only-connections", detail: "related Knowledge pages are present only in metadata, with no reader-facing body link", }); } return findings; } async function main(): Promise { const includeDrafts = process.argv.includes("--include-drafts"); const graph = await loadKnowledgeGraph({ root: resolve(process.cwd(), "knowledge"), includeDrafts, }); const policy = loadKnowledgePolicy(); const authorizationFindings = (await Promise.all( graph.entries.map((entry) => verifyTechnicalAuthorization(entry)), )).flat(); const findings = [...authorizationFindings, ...graph.entries.flatMap((entry) => { const entryFindings = scanKnowledgeDraft(entry.body, policy).map((finding) => ({ slug: entry.slug, ...finding, })); entryFindings.push(...scanKnowledgeStyle(entry).map((finding) => ({ slug: entry.slug, ...finding, }))); const contentDigest = knowledgeContentDigest(entry); if (!entry.draft && entry.reviewedContentDigest !== contentDigest) { entryFindings.push({ slug: entry.slug, severity: "block", code: "reviewed-content-digest-mismatch", detail: "published Markdown no longer matches the content digest reviewed for release", }); } if (entry.kind === "person" && !policy.allowedPeopleSlugs.includes(entry.slug)) { entryFindings.push({ slug: entry.slug, severity: "block", code: "unapproved-person-entry", detail: "person slug is not explicitly allowlisted", }); } if (entry.kind === "person" && entry.claimMode !== "factual") { entryFindings.push({ slug: entry.slug, severity: "block", code: "person-entry-mode", detail: "person entries must remain factual", }); } const hasReaderFacingLink = /\[[^\]]+\]\((?:https?:\/\/|\/)[^)]+\)/.test(entry.body) || entry.sources.length > 0 || entry.related.length > 0 || entry.backlinks.length > 0; if (!hasReaderFacingLink) { entryFindings.push({ slug: entry.slug, severity: "block", code: "zero-link-entry", detail: "entry has no inline, source, related, or backlink path for readers", }); } return entryFindings; })]; const blocking = findings.filter((finding) => finding.severity === "block"); const contentDigests = Object.fromEntries( graph.entries.map((entry) => [entry.slug, knowledgeContentDigest(entry)]), ); console.log(JSON.stringify({ entries: graph.entries.length, drafts: graph.entries.filter((entry) => entry.draft).length, published: graph.entries.filter((entry) => !entry.draft).length, graphErrors: graph.errors, findings, contentDigests, }, null, 2)); if (graph.errors.length > 0 || blocking.length > 0) process.exit(1); } main().catch((error) => { console.error(error); process.exit(1); });