From 541b9e1fca7919e5e0e09ff98aafb7922af5b8f1 Mon Sep 17 00:00:00 2001 From: andriypolandki Date: Tue, 7 Jul 2026 16:06:17 -0700 Subject: [PATCH] docs(selfhost): add website docs accuracy audit checklist (#1829) --- .../src/lib/selfhost-docs-audit.ts | 246 ++++++++++++++++++ apps/gittensory-ui/src/routeTree.gen.ts | 22 ++ apps/gittensory-ui/src/routes/docs.index.tsx | 1 + .../routes/docs.maintainer-self-hosting.tsx | 6 + .../routes/docs.self-hosting-docs-audit.tsx | 168 ++++++++++++ .../docs-selfhost-audit-checklist.test.ts | 102 ++++++++ 6 files changed, 545 insertions(+) create mode 100644 apps/gittensory-ui/src/lib/selfhost-docs-audit.ts create mode 100644 apps/gittensory-ui/src/routes/docs.self-hosting-docs-audit.tsx create mode 100644 test/unit/docs-selfhost-audit-checklist.test.ts diff --git a/apps/gittensory-ui/src/lib/selfhost-docs-audit.ts b/apps/gittensory-ui/src/lib/selfhost-docs-audit.ts new file mode 100644 index 0000000000..333f9a2bff --- /dev/null +++ b/apps/gittensory-ui/src/lib/selfhost-docs-audit.ts @@ -0,0 +1,246 @@ +/** Shared manifest for the self-host docs accuracy audit (#1829). Imported by the docs page and CI drift tests. */ + +export type SelfHostDocsPageLink = { + title: string; + path: string; + routeFile: string; +}; + +export const SELFHOST_DOCS_PAGES = [ + { + title: "Quickstart", + path: "/docs/self-hosting-quickstart", + routeFile: "docs.self-hosting-quickstart.tsx", + }, + { + title: "Configuration", + path: "/docs/self-hosting-configuration", + routeFile: "docs.self-hosting-configuration.tsx", + }, + { + title: "GitHub App and Orb", + path: "/docs/self-hosting-github-app", + routeFile: "docs.self-hosting-github-app.tsx", + }, + { + title: "AI providers", + path: "/docs/self-hosting-ai-providers", + routeFile: "docs.self-hosting-ai-providers.tsx", + }, + { + title: "REES enrichment", + path: "/docs/self-hosting-rees", + routeFile: "docs.self-hosting-rees.tsx", + }, + { + title: "REES analyzer reference", + path: "/docs/self-hosting-rees-analyzers", + routeFile: "docs.self-hosting-rees-analyzers.tsx", + }, + { title: "RAG indexing", path: "/docs/self-hosting-rag", routeFile: "docs.self-hosting-rag.tsx" }, + { + title: "Operations", + path: "/docs/self-hosting-operations", + routeFile: "docs.self-hosting-operations.tsx", + }, + { + title: "Backup and scaling", + path: "/docs/self-hosting-backup-scaling", + routeFile: "docs.self-hosting-backup-scaling.tsx", + }, + { + title: "Releases and images", + path: "/docs/self-hosting-releases", + routeFile: "docs.self-hosting-releases.tsx", + }, + { + title: "Release checklist", + path: "/docs/self-hosting-release-checklist", + routeFile: "docs.self-hosting-release-checklist.tsx", + }, + { + title: "Security", + path: "/docs/self-hosting-security", + routeFile: "docs.self-hosting-security.tsx", + }, + { + title: "Troubleshooting", + path: "/docs/self-hosting-troubleshooting", + routeFile: "docs.self-hosting-troubleshooting.tsx", + }, + { + title: "Docs accuracy audit", + path: "/docs/self-hosting-docs-audit", + routeFile: "docs.self-hosting-docs-audit.tsx", + }, +] as const; + +export type SelfHostDocsPath = (typeof SELFHOST_DOCS_PAGES)[number]["path"]; + +export type SelfHostSourceOfTruthRow = { + topic: string; + runtimeSources: readonly string[]; + docsPath: SelfHostDocsPath; + driftGuard?: string; + notes?: string; +}; + +export type LooseDocsRow = { + path: string; + role: string; + action: "keep" | "link-only" | "consolidated"; + websiteDocsPath?: SelfHostDocsPath; + notes?: string; +}; + +export const SELFHOST_SOURCE_OF_TRUTH_ROWS: readonly SelfHostSourceOfTruthRow[] = [ + { + topic: "Compose stack and profiles", + runtimeSources: ["docker-compose.yml", ".env.selfhost.example", ".env.example"], + docsPath: "/docs/self-hosting-quickstart", + notes: "Profiles (postgres, rees, observability, backup) and conservative first-boot defaults.", + }, + { + topic: "Env vars (exhaustive list)", + runtimeSources: [ + "scripts/gen-selfhost-env-reference.mjs", + "apps/gittensory-ui/src/lib/selfhost-env-reference.ts", + ], + docsPath: "/docs/self-hosting-configuration", + driftGuard: "selfhost-env-reference-script.test.ts", + notes: "Generated from every env.SOMETHING read; npm run selfhost:env-reference:check in CI.", + }, + { + topic: "GitHub App manifest and setup wizard", + runtimeSources: ["src/selfhost/setup-wizard.ts", ".env.selfhost.example"], + docsPath: "/docs/self-hosting-github-app", + driftGuard: "setup-wizard-docs-parity.test.ts", + }, + { + topic: "Activation and onboarding paths", + runtimeSources: ["src/server.ts", "config/examples/global.gittensory.yml"], + docsPath: "/docs/self-hosting-configuration", + driftGuard: "docs-selfhost-activation-paths.test.ts", + }, + { + topic: "AI providers and unsafe Codex opt-in", + runtimeSources: ["src/selfhost/ai-config.ts", "src/selfhost/ai.ts"], + docsPath: "/docs/self-hosting-ai-providers", + notes: "Codex PR review is fail-closed unless GITTENSORY_ENABLE_UNSAFE_CODEX_REVIEWER=1.", + }, + { + topic: "REES sidecar (compose profile)", + runtimeSources: ["docker-compose.yml", "review-enrichment/Dockerfile"], + docsPath: "/docs/self-hosting-rees", + notes: + "Analyzer metadata tables live on the REES analyzer page; generation is a separate roadmap item.", + }, + { + topic: "RAG / embeddings / Qdrant", + runtimeSources: ["src/selfhost/qdrant-vectorize.ts", "docker-compose.yml"], + docsPath: "/docs/self-hosting-rag", + }, + { + topic: "Update, rollback, and deploy scripts", + runtimeSources: [ + "scripts/deploy-selfhost-image.sh", + "scripts/deploy-selfhost-prebuilt.sh", + "scripts/selfhost-post-update-check.sh", + ], + docsPath: "/docs/self-hosting-operations", + driftGuard: "docs-selfhost-update-rollback.test.ts", + notes: "Migrations are forward-only; rollback is image-only.", + }, + { + topic: "Runner temp storage (multi-runner)", + runtimeSources: ["docker-compose.yml"], + docsPath: "/docs/self-hosting-operations", + driftGuard: "docs-selfhost-operations-runner-tmpdir.test.ts", + }, + { + topic: "Backup, restore, and Litestream", + runtimeSources: ["scripts/backup.sh", "scripts/verify-backup.sh", "scripts/backup-metrics.sh"], + docsPath: "/docs/self-hosting-backup-scaling", + driftGuard: "selfhost-backup-script.test.ts", + }, + { + topic: "Official orb-v* releases and GHCR tags", + runtimeSources: [".github/workflows/release-selfhost.yml", "Dockerfile"], + docsPath: "/docs/self-hosting-releases", + notes: + "Release workflow uploads source maps to Sentry; maps never ship inside the runtime image.", + }, + { + topic: "Release smoke matrix and image-contents audit", + runtimeSources: ["scripts/smoke-selfhost.sh", "Dockerfile", ".dockerignore"], + docsPath: "/docs/self-hosting-release-checklist", + driftGuard: "docs-selfhost-release-checklist-event-names.test.ts", + }, + { + topic: "Sentry (opt-in, operator-owned DSN)", + runtimeSources: ["src/selfhost/sentry.ts", "docker-compose.yml"], + docsPath: "/docs/self-hosting-operations", + driftGuard: "docs-selfhost-sentry-observability.test.ts", + notes: "Sentry is off by default until SENTRY_DSN or SENTRY_DSN_FILE is set.", + }, + { + topic: "OTEL metrics/traces and Grafana dashboards", + runtimeSources: [ + "src/selfhost/otel.ts", + "scripts/validate-observability-configs.mjs", + "grafana/dashboards/", + "prometheus/rules/alerts.yml", + ], + docsPath: "/docs/self-hosting-operations", + driftGuard: "docs-selfhost-troubleshooting-metric-names.test.ts", + notes: "Observability profile is optional; npm run selfhost:validate-observability in CI.", + }, + { + topic: "Security boundaries and secret handling", + runtimeSources: ["src/selfhost/private-config.ts", "src/selfhost/preflight.ts"], + docsPath: "/docs/self-hosting-security", + }, + { + topic: "Prometheus alert runbooks", + runtimeSources: ["prometheus/rules/alerts.yml", "src/selfhost/metrics.ts"], + docsPath: "/docs/self-hosting-troubleshooting", + driftGuard: "docs-selfhost-troubleshooting-metric-names.test.ts", + }, +] as const; + +export const LOOSE_DOCS_ROWS: readonly LooseDocsRow[] = [ + { + path: "CONVERGENCE_RUNBOOK.md", + role: "Native-port convergence and hosted Cloudflare inventory (#1030 / #1826).", + action: "keep", + notes: "Intentional root runbook — not a duplicate of the website self-host section.", + }, + { + path: "config/examples/README.md", + role: "Private config mount examples and template pointers.", + action: "link-only", + websiteDocsPath: "/docs/self-hosting-configuration", + }, + { + path: "review-enrichment/README.md", + role: "REES analyzer developer notes for contributors.", + action: "link-only", + websiteDocsPath: "/docs/self-hosting-rees", + }, + { + path: "packages/gittensory-miner/DEPLOYMENT.md", + role: "Miner CLI deployment — explicitly not the self-host review stack.", + action: "keep", + }, +] as const; + +export const SELFHOST_DOCS_VALIDATION_COMMANDS = [ + "npm run docs:drift-check", + "npm run selfhost:env-reference:check", + "npm run selfhost:validate-observability", + "npm run ui:lint", + "npm run ui:typecheck", + "npm run ui:test", + "npm run ui:build", + "npm run test:ci", +] as const; diff --git a/apps/gittensory-ui/src/routeTree.gen.ts b/apps/gittensory-ui/src/routeTree.gen.ts index 78d409c9f7..cc373ea43a 100644 --- a/apps/gittensory-ui/src/routeTree.gen.ts +++ b/apps/gittensory-ui/src/routeTree.gen.ts @@ -35,6 +35,7 @@ import { Route as DocsSelfHostingRagRouteImport } from './routes/docs.self-hosti import { Route as DocsSelfHostingQuickstartRouteImport } from './routes/docs.self-hosting-quickstart' import { Route as DocsSelfHostingOperationsRouteImport } from './routes/docs.self-hosting-operations' import { Route as DocsSelfHostingGithubAppRouteImport } from './routes/docs.self-hosting-github-app' +import { Route as DocsSelfHostingDocsAuditRouteImport } from './routes/docs.self-hosting-docs-audit' import { Route as DocsSelfHostingConfigurationRouteImport } from './routes/docs.self-hosting-configuration' import { Route as DocsSelfHostingBackupScalingRouteImport } from './routes/docs.self-hosting-backup-scaling' import { Route as DocsSelfHostingAiProvidersRouteImport } from './routes/docs.self-hosting-ai-providers' @@ -205,6 +206,12 @@ const DocsSelfHostingGithubAppRoute = path: '/self-hosting-github-app', getParentRoute: () => DocsRoute, } as any) +const DocsSelfHostingDocsAuditRoute = + DocsSelfHostingDocsAuditRouteImport.update({ + id: '/self-hosting-docs-audit', + path: '/self-hosting-docs-audit', + getParentRoute: () => DocsRoute, + } as any) const DocsSelfHostingConfigurationRoute = DocsSelfHostingConfigurationRouteImport.update({ id: '/self-hosting-configuration', @@ -419,6 +426,7 @@ export interface FileRoutesByFullPath { '/docs/self-hosting-ai-providers': typeof DocsSelfHostingAiProvidersRoute '/docs/self-hosting-backup-scaling': typeof DocsSelfHostingBackupScalingRoute '/docs/self-hosting-configuration': typeof DocsSelfHostingConfigurationRoute + '/docs/self-hosting-docs-audit': typeof DocsSelfHostingDocsAuditRoute '/docs/self-hosting-github-app': typeof DocsSelfHostingGithubAppRoute '/docs/self-hosting-operations': typeof DocsSelfHostingOperationsRoute '/docs/self-hosting-quickstart': typeof DocsSelfHostingQuickstartRoute @@ -477,6 +485,7 @@ export interface FileRoutesByTo { '/docs/self-hosting-ai-providers': typeof DocsSelfHostingAiProvidersRoute '/docs/self-hosting-backup-scaling': typeof DocsSelfHostingBackupScalingRoute '/docs/self-hosting-configuration': typeof DocsSelfHostingConfigurationRoute + '/docs/self-hosting-docs-audit': typeof DocsSelfHostingDocsAuditRoute '/docs/self-hosting-github-app': typeof DocsSelfHostingGithubAppRoute '/docs/self-hosting-operations': typeof DocsSelfHostingOperationsRoute '/docs/self-hosting-quickstart': typeof DocsSelfHostingQuickstartRoute @@ -539,6 +548,7 @@ export interface FileRoutesById { '/docs/self-hosting-ai-providers': typeof DocsSelfHostingAiProvidersRoute '/docs/self-hosting-backup-scaling': typeof DocsSelfHostingBackupScalingRoute '/docs/self-hosting-configuration': typeof DocsSelfHostingConfigurationRoute + '/docs/self-hosting-docs-audit': typeof DocsSelfHostingDocsAuditRoute '/docs/self-hosting-github-app': typeof DocsSelfHostingGithubAppRoute '/docs/self-hosting-operations': typeof DocsSelfHostingOperationsRoute '/docs/self-hosting-quickstart': typeof DocsSelfHostingQuickstartRoute @@ -602,6 +612,7 @@ export interface FileRouteTypes { | '/docs/self-hosting-ai-providers' | '/docs/self-hosting-backup-scaling' | '/docs/self-hosting-configuration' + | '/docs/self-hosting-docs-audit' | '/docs/self-hosting-github-app' | '/docs/self-hosting-operations' | '/docs/self-hosting-quickstart' @@ -660,6 +671,7 @@ export interface FileRouteTypes { | '/docs/self-hosting-ai-providers' | '/docs/self-hosting-backup-scaling' | '/docs/self-hosting-configuration' + | '/docs/self-hosting-docs-audit' | '/docs/self-hosting-github-app' | '/docs/self-hosting-operations' | '/docs/self-hosting-quickstart' @@ -721,6 +733,7 @@ export interface FileRouteTypes { | '/docs/self-hosting-ai-providers' | '/docs/self-hosting-backup-scaling' | '/docs/self-hosting-configuration' + | '/docs/self-hosting-docs-audit' | '/docs/self-hosting-github-app' | '/docs/self-hosting-operations' | '/docs/self-hosting-quickstart' @@ -938,6 +951,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof DocsSelfHostingGithubAppRouteImport parentRoute: typeof DocsRoute } + '/docs/self-hosting-docs-audit': { + id: '/docs/self-hosting-docs-audit' + path: '/self-hosting-docs-audit' + fullPath: '/docs/self-hosting-docs-audit' + preLoaderRoute: typeof DocsSelfHostingDocsAuditRouteImport + parentRoute: typeof DocsRoute + } '/docs/self-hosting-configuration': { id: '/docs/self-hosting-configuration' path: '/self-hosting-configuration' @@ -1238,6 +1258,7 @@ interface DocsRouteChildren { DocsSelfHostingAiProvidersRoute: typeof DocsSelfHostingAiProvidersRoute DocsSelfHostingBackupScalingRoute: typeof DocsSelfHostingBackupScalingRoute DocsSelfHostingConfigurationRoute: typeof DocsSelfHostingConfigurationRoute + DocsSelfHostingDocsAuditRoute: typeof DocsSelfHostingDocsAuditRoute DocsSelfHostingGithubAppRoute: typeof DocsSelfHostingGithubAppRoute DocsSelfHostingOperationsRoute: typeof DocsSelfHostingOperationsRoute DocsSelfHostingQuickstartRoute: typeof DocsSelfHostingQuickstartRoute @@ -1274,6 +1295,7 @@ const DocsRouteChildren: DocsRouteChildren = { DocsSelfHostingAiProvidersRoute: DocsSelfHostingAiProvidersRoute, DocsSelfHostingBackupScalingRoute: DocsSelfHostingBackupScalingRoute, DocsSelfHostingConfigurationRoute: DocsSelfHostingConfigurationRoute, + DocsSelfHostingDocsAuditRoute: DocsSelfHostingDocsAuditRoute, DocsSelfHostingGithubAppRoute: DocsSelfHostingGithubAppRoute, DocsSelfHostingOperationsRoute: DocsSelfHostingOperationsRoute, DocsSelfHostingQuickstartRoute: DocsSelfHostingQuickstartRoute, diff --git a/apps/gittensory-ui/src/routes/docs.index.tsx b/apps/gittensory-ui/src/routes/docs.index.tsx index d57d64eeed..1e77b9addd 100644 --- a/apps/gittensory-ui/src/routes/docs.index.tsx +++ b/apps/gittensory-ui/src/routes/docs.index.tsx @@ -71,6 +71,7 @@ const AUDIENCES: Audience[] = [ primary: { to: "/docs/beta-onboarding", label: "Beta onboarding" }, links: [ { to: "/docs/maintainer-self-hosting", label: "Self-host reviews" }, + { to: "/docs/self-hosting-docs-audit", label: "Self-host docs audit" }, { to: "/docs/maintainer-install-trust", label: "Install & trust guide" }, { to: "/docs/github-app", label: "GitHub App configuration" }, { to: "/docs/maintainer-workflow", label: "Maintainer workflow" }, diff --git a/apps/gittensory-ui/src/routes/docs.maintainer-self-hosting.tsx b/apps/gittensory-ui/src/routes/docs.maintainer-self-hosting.tsx index 702dc653f9..8297da56dd 100644 --- a/apps/gittensory-ui/src/routes/docs.maintainer-self-hosting.tsx +++ b/apps/gittensory-ui/src/routes/docs.maintainer-self-hosting.tsx @@ -100,6 +100,12 @@ const SECTION_LINKS = [ "Review not firing, REES silent, AI unavailable, RAG empty, queue stuck, and webhook failures.", to: "/docs/self-hosting-troubleshooting", }, + { + title: "Docs accuracy audit", + description: + "Checklist mapping website docs to docker-compose, env, release, observability, and backup sources of truth.", + to: "/docs/self-hosting-docs-audit", + }, ] as const; function MaintainerSelfHosting() { diff --git a/apps/gittensory-ui/src/routes/docs.self-hosting-docs-audit.tsx b/apps/gittensory-ui/src/routes/docs.self-hosting-docs-audit.tsx new file mode 100644 index 0000000000..55169920f4 --- /dev/null +++ b/apps/gittensory-ui/src/routes/docs.self-hosting-docs-audit.tsx @@ -0,0 +1,168 @@ +import { createFileRoute, Link } from "@tanstack/react-router"; + +import { DocsPage } from "@/components/site/docs-page"; +import { Callout, CodeBlock } from "@/components/site/primitives"; +import { + LOOSE_DOCS_ROWS, + SELFHOST_DOCS_PAGES, + SELFHOST_DOCS_VALIDATION_COMMANDS, + SELFHOST_SOURCE_OF_TRUTH_ROWS, +} from "@/lib/selfhost-docs-audit"; + +export const Route = createFileRoute("/docs/self-hosting-docs-audit")({ + head: () => ({ + meta: [ + { title: "Self-host docs accuracy audit — Gittensory docs" }, + { + name: "description", + content: + "Checklist mapping self-host website docs to runtime sources of truth — compose defaults, env vars, releases, observability, backup, and drift guards.", + }, + { property: "og:title", content: "Self-host docs accuracy audit — Gittensory docs" }, + { + property: "og:description", + content: + "Checklist mapping self-host website docs to runtime sources of truth — compose defaults, env vars, releases, observability, backup, and drift guards.", + }, + { property: "og:url", content: "/docs/self-hosting-docs-audit" }, + ], + links: [{ rel: "canonical", href: "/docs/self-hosting-docs-audit" }], + }), + component: SelfHostingDocsAudit, +}); + +function SelfHostingDocsAudit() { + return ( + + + This page is the in-repo paper trail for the self-host docs audit under roadmap{" "} + #1819. REES analyzer + metadata generation is tracked separately on the REES roadmap — the analyzer reference page + covers names and shapes; auto-generated metadata tables are out of scope here. + + +

Website docs navigation

+

+ Self-hosting docs live on the website under{" "} + Self-hosted reviews. Every child page below + is linked from that index and from the maintainer docs hub. +

+ + +

Source-of-truth checklist

+

+ When you change runtime behavior, update the docs page and extend the drift + guard test when one exists. Env vars must stay aligned with{" "} + npm run selfhost:env-reference; observability configs with{" "} + npm run selfhost:validate-observability. +

+
+ + + + + + + + + + + {SELFHOST_SOURCE_OF_TRUTH_ROWS.map((row) => ( + + + + + + + ))} + +
TopicRuntime sourcesDocs pageDrift guard
+
{row.topic}
+ {row.notes ? ( +

{row.notes}

+ ) : null} +
+
    + {row.runtimeSources.map((source) => ( +
  • + {source} +
  • + ))} +
+
+ {row.docsPath.replace("/docs/", "")} + + {row.driftGuard ? {`test/unit/${row.driftGuard}`} : "—"} +
+
+ +

Defaults, optional services, and experimental surfaces

+ + +

Loose docs consolidation

+

+ Root-level markdown outside the website must either link to the canonical website page or + stay intentionally separate (convergence runbooks, contributor notes). Do not duplicate + self-host operator instructions in a second place that will drift. +

+ + +

Validation commands

+

Run from the repo root before merging docs or cutting an orb release:

+ + + Spot-check documented shell commands against the current docker-compose.yml{" "} + profiles and release scripts when you touch operator-facing prose — CI drift guards cover + the highest-risk surfaces but not every copy-pasted example. + +
+ ); +} diff --git a/test/unit/docs-selfhost-audit-checklist.test.ts b/test/unit/docs-selfhost-audit-checklist.test.ts new file mode 100644 index 0000000000..8ef960e99c --- /dev/null +++ b/test/unit/docs-selfhost-audit-checklist.test.ts @@ -0,0 +1,102 @@ +import { existsSync, readFileSync } from "node:fs"; +import { resolve } from "node:path"; +import { describe, expect, it } from "vitest"; +import { isPublicSafeText } from "../../src/signals/redaction"; +import { + LOOSE_DOCS_ROWS, + SELFHOST_DOCS_PAGES, + SELFHOST_DOCS_VALIDATION_COMMANDS, + SELFHOST_SOURCE_OF_TRUTH_ROWS, +} from "../../apps/gittensory-ui/src/lib/selfhost-docs-audit"; + +const REPO_ROOT = resolve(import.meta.dirname, "../.."); +const ROUTES_DIR = resolve(REPO_ROOT, "apps/gittensory-ui/src/routes"); +const AUDIT_PAGE = resolve(ROUTES_DIR, "docs.self-hosting-docs-audit.tsx"); +const MAINTAINER_INDEX = resolve(ROUTES_DIR, "docs.maintainer-self-hosting.tsx"); +const PACKAGE_JSON = resolve(REPO_ROOT, "package.json"); + +function repoPath(relativePath: string): string { + return resolve(REPO_ROOT, relativePath); +} + +describe("self-host docs accuracy audit (#1829)", () => { + const auditSource = readFileSync(AUDIT_PAGE, "utf8"); + const maintainerSource = readFileSync(MAINTAINER_INDEX, "utf8"); + const packageJson = JSON.parse(readFileSync(PACKAGE_JSON, "utf8")) as { scripts: Record }; + + it("manifest lists every self-host docs route file on disk", () => { + for (const page of SELFHOST_DOCS_PAGES) { + expect(existsSync(resolve(ROUTES_DIR, page.routeFile)), page.routeFile).toBe(true); + } + }); + + it("every runtime source in the checklist exists in the repo", () => { + const missing: string[] = []; + for (const row of SELFHOST_SOURCE_OF_TRUTH_ROWS) { + for (const source of row.runtimeSources) { + const path = repoPath(source); + if (!existsSync(path)) missing.push(source); + } + } + expect(missing).toEqual([]); + }); + + it("every drift-guard test file referenced by the checklist exists", () => { + const guards = [ + ...new Set( + SELFHOST_SOURCE_OF_TRUTH_ROWS.map((row) => row.driftGuard).filter((g): g is string => Boolean(g)), + ), + ]; + expect(guards.length).toBeGreaterThan(5); + for (const guard of guards) { + expect(existsSync(repoPath(`test/unit/${guard}`)), guard).toBe(true); + } + }); + + it("every validation command in the checklist is a real npm script", () => { + for (const command of SELFHOST_DOCS_VALIDATION_COMMANDS) { + const match = /^npm run (\S+)/.exec(command); + expect(match, command).not.toBeNull(); + expect(packageJson.scripts[match![1]!], command).toBeDefined(); + } + }); + + it("maintainer index and docs hub link to the audit page", () => { + expect(maintainerSource).toContain("/docs/self-hosting-docs-audit"); + expect(readFileSync(resolve(ROUTES_DIR, "docs.index.tsx"), "utf8")).toContain( + "/docs/self-hosting-docs-audit", + ); + }); + + it("documents Sentry as opt-in with an operator-owned DSN", () => { + expect(auditSource).toMatch(/opt-in and off by default/i); + expect(auditSource).toContain("SENTRY_DSN"); + expect(auditSource).toContain("SENTRY_DSN_FILE"); + }); + + it("records the loose-docs consolidation plan with canonical website links", () => { + const manifestSource = readFileSync( + resolve(REPO_ROOT, "apps/gittensory-ui/src/lib/selfhost-docs-audit.ts"), + "utf8", + ); + for (const row of LOOSE_DOCS_ROWS) { + expect(existsSync(repoPath(row.path)), row.path).toBe(true); + expect(manifestSource).toContain(row.path); + expect(auditSource).toContain("LOOSE_DOCS_ROWS"); + } + const linkOnly = LOOSE_DOCS_ROWS.filter((row) => row.websiteDocsPath); + expect(linkOnly.length).toBeGreaterThan(0); + for (const row of linkOnly) { + expect(manifestSource).toContain(row.websiteDocsPath!); + } + }); + + it("excludes REES analyzer auto-metadata from this audit scope", () => { + const normalized = auditSource.replace(/\s+/g, " "); + expect(normalized).toMatch(/REES analyzer metadata generation is tracked separately/i); + }); + + it("is public-safe end-to-end per the canonical sanitizer", () => { + expect(isPublicSafeText(auditSource)).toBe(true); + }); +});