diff --git a/.github/workflows/knip.yml b/.github/workflows/knip.yml
index 713650633..1b8518654 100644
--- a/.github/workflows/knip.yml
+++ b/.github/workflows/knip.yml
@@ -30,7 +30,11 @@ jobs:
- run: pnpm db:generate
# v1.4.42 — exports + types tiers promoted to enforcing. The
- # `knip.json` ignore-block carries the shadcn UI surface and the
+ # `knip.jsonc` ignore-block carries the shadcn UI surface and the
# `src/lib/validations/**` contract types; everything else has
# to clear or get justified there.
+ #
+ # This pass cannot see a module whose only importer is its own test —
+ # vitest is a knip plugin, so test files are entries. The header of
+ # `knip.jsonc` demonstrates that and names the pass that does see it.
- run: pnpm knip --reporter compact
diff --git a/docs/self-hosting/geolite2.md b/docs/self-hosting/geolite2.md
index 8de132921..b9dacd0a0 100644
--- a/docs/self-hosting/geolite2.md
+++ b/docs/self-hosting/geolite2.md
@@ -100,9 +100,16 @@ not immediately. A weekly `geoipupdate` followed by
The databases are downloaded under your own MaxMind account, so their
terms are between you and MaxMind — read what your account agrees to at
-. HealthLog's own attribution
-("this product includes GeoLite2 data created by MaxMind") is already on
-the `/about` page and covers an instance that uses them.
+. That agreement, the GeoLite
+End User License Agreement, incorporates CC BY-SA 4.0 by reference and
+controls wherever the two disagree; both require you to attribute the
+data to MaxMind if you make it available to anyone else.
+
+HealthLog's own attribution ("This product includes GeoLite2 data
+created by MaxMind, available from https://www.maxmind.com") is already
+on the `/about` page and covers an instance that uses them. Keep that
+page reachable — it is what carries the attribution for your deployment,
+and it is served without a session for exactly that reason.
## Troubleshooting
diff --git a/e2e/v1427-public-pages.spec.ts b/e2e/v1427-public-pages.spec.ts
index 8fbb313fa..a9e62b753 100644
--- a/e2e/v1427-public-pages.spec.ts
+++ b/e2e/v1427-public-pages.spec.ts
@@ -41,6 +41,12 @@ test.describe("v1.4.27 — public pages", () => {
// GeoLite2 attribution is the load-bearing reason this page exists.
await expect(page.getByText(/GeoLite2/).first()).toBeVisible();
await expect(page.getByText(/MaxMind/).first()).toBeVisible();
+ // Both licences, because naming only the Creative Commons one understates
+ // the terms: the GeoLite EULA is what governs and CC BY-SA 4.0 rides
+ // inside it.
+ await expect(
+ page.getByText(/GeoLite End User License Agreement/i),
+ ).toBeVisible();
await expect(page.getByText(/Attribution-ShareAlike 4\.0/i)).toBeVisible();
});
diff --git a/knip.json b/knip.json
deleted file mode 100644
index 4e233cf9a..000000000
--- a/knip.json
+++ /dev/null
@@ -1,77 +0,0 @@
-{
- "$schema": "https://unpkg.com/knip@6/schema.json",
- "entry": [
- "src/app/**/page.tsx",
- "src/app/**/layout.tsx",
- "src/app/**/route.ts",
- "src/app/**/loading.tsx",
- "src/app/**/not-found.tsx",
- "src/app/**/error.tsx",
- "src/app/**/global-error.tsx",
- "src/app/**/template.tsx",
- "src/app/**/opengraph-image.tsx",
- "src/app/**/twitter-image.tsx",
- "src/app/**/icon.tsx",
- "src/app/**/apple-icon.tsx",
- "src/app/**/manifest.ts",
- "src/app/**/robots.ts",
- "src/app/**/sitemap.ts",
- "src/app/**/default.tsx",
- "src/proxy.ts",
- "src/instrumentation.ts",
- "src/lib/jobs/**/*.ts",
- "src/lib/ai/coach/eval/judge.ts",
- "scripts/**/*.ts",
- "scripts/**/*.mjs",
- "e2e/**/*.spec.ts",
- "e2e/**/*.test.ts",
- "tests/**/*.test.ts",
- "tests/**/setup.ts",
- "vitest.config.ts",
- "vitest.config.integration.ts",
- "playwright.config.ts",
- "next.config.ts",
- "eslint.config.*",
- "knip.json"
- ],
- "project": [
- "src/**/*.{ts,tsx}",
- "scripts/**/*.{ts,mjs}",
- "tests/**/*.ts",
- "e2e/**/*.ts"
- ],
- "ignore": [
- "src/generated/**",
- "prisma/**",
- "public/**",
- ".next/**",
- "**/__mocks__/**",
- "e2e/setup/test-helpers.ts",
- "src/lib/logging/index.ts"
- ],
- "ignoreDependencies": [
- "@types/node",
- "@types/react",
- "@types/react-dom",
- "@hookform/resolvers",
- "react-hook-form",
- "@prisma/client",
- "next-themes",
- "@vitejs/plugin-react",
- "shadcn",
- "tailwindcss",
- "testcontainers",
- "tw-animate-css",
- "@napi-rs/canvas",
- "@openai/codex-security",
- "postcss"
- ],
- "ignoreBinaries": ["docker", "docker-compose", "pg_isready", "tsx", "cosign"],
- "ignoreExportsUsedInFile": true,
- "ignoreIssues": {
- "src/components/ui/**": ["exports", "types"],
- "src/lib/validations/**": ["types"],
- "src/hooks/_internal/use-query-client-safe.ts": ["exports"],
- "src/lib/google-health/sync.ts": ["exports"]
- }
-}
diff --git a/knip.jsonc b/knip.jsonc
new file mode 100644
index 000000000..e6bac4e8f
--- /dev/null
+++ b/knip.jsonc
@@ -0,0 +1,131 @@
+// Dead-code configuration. JSONC rather than JSON so this can say what the
+// gate proves — the previous JSON file could not, and the difference matters
+// more here than anywhere else in the repo.
+//
+// ## What `pnpm knip` (the CI gate in .github/workflows/knip.yml) proves
+//
+// A module, export or dependency that nothing in the repository reaches is a
+// build failure. That is a real property and it catches the ordinary case: a
+// helper left behind after its last caller was rewritten.
+//
+// ## What it does NOT prove, demonstrated
+//
+// It does not prove a module is still reachable from production. Vitest is a
+// knip plugin here, so `src/**/__tests__/**` are entry files, and a module
+// whose ONLY importer is its own test counts as used — forever. Planting
+// `src/lib/dead-probe.ts` with a single test importing it left `pnpm knip` at
+// exit 0. Nothing in the default pass can see that class of dead code, so do
+// not read a green Dead-code gate as "every module is still wired to
+// something that ships".
+//
+// ## The pass that does see it
+//
+// pnpm knip --production --include files
+//
+// Production mode drops the plugin-supplied test entries and keeps only the
+// entry and project patterns suffixed with `!` below — the app router tree,
+// the proxy, instrumentation, the pg-boss workers, and `scripts/` (maintainer
+// tooling is not production, but it is a legitimate non-test consumer, so a
+// module reached only from a script is not the thing we are hunting). The `!`
+// suffix is inert in the default pass, so both modes read one config.
+//
+// This is a review tool, not a second gate, for two reasons written down so
+// the next reader does not have to rediscover them:
+//
+// 1. It reports 29 files today. Some are test-only by construction (the
+// guard helpers under `src/__tests__/helpers/`, the settings section
+// harness) and some are deliberately unreachable from production
+// (`src/lib/ai/mock-client.ts` is excluded structurally by the
+// `PROVIDER_CHAIN_TYPES` allowlist). Baselining the rest without
+// triaging each one would encode noise as an invariant.
+// 2. It has false positives. `src/lib/mood/note-backfill.ts` is imported by
+// `scripts/backfill-mood-note-column.ts` through the `@/` alias and is
+// still reported, because `scripts/` sits outside the tsconfig that
+// defines the alias. The default pass hides that resolution failure
+// behind the module's test importer.
+//
+// Run it in the deep-audit step, the same way
+// `scripts/audit-column-readers.ts` is run: read the list, judge each entry,
+// do not treat the exit code as a verdict.
+{
+ "$schema": "https://unpkg.com/knip@6/schema.json",
+ // `!` marks an entry as production. Read the block above before adding or
+ // removing one — the suffix is what separates the two passes.
+ "entry": [
+ "src/app/**/page.tsx!",
+ "src/app/**/layout.tsx!",
+ "src/app/**/route.ts!",
+ "src/app/**/loading.tsx!",
+ "src/app/**/not-found.tsx!",
+ "src/app/**/error.tsx!",
+ "src/app/**/global-error.tsx!",
+ "src/app/**/template.tsx!",
+ "src/app/**/opengraph-image.tsx!",
+ "src/app/**/twitter-image.tsx!",
+ "src/app/**/icon.tsx!",
+ "src/app/**/apple-icon.tsx!",
+ "src/app/**/manifest.ts!",
+ "src/app/**/robots.ts!",
+ "src/app/**/sitemap.ts!",
+ "src/app/**/default.tsx!",
+ "src/proxy.ts!",
+ "src/instrumentation.ts!",
+ "src/lib/jobs/**/*.ts!",
+ "src/lib/ai/coach/eval/judge.ts!",
+ "scripts/**/*.ts!",
+ "scripts/**/*.mjs!",
+ "e2e/**/*.spec.ts",
+ "e2e/**/*.test.ts",
+ "tests/**/*.test.ts",
+ "tests/**/setup.ts",
+ "vitest.config.ts",
+ "vitest.config.integration.ts",
+ "playwright.config.ts",
+ "next.config.ts!",
+ "eslint.config.*",
+ "knip.jsonc",
+ ],
+ "project": [
+ "src/**/*.{ts,tsx}!",
+ "scripts/**/*.{ts,mjs}!",
+ "tests/**/*.ts",
+ "e2e/**/*.ts",
+ ],
+ "ignore": [
+ "src/generated/**",
+ "prisma/**",
+ "public/**",
+ ".next/**",
+ "**/__mocks__/**",
+ "e2e/setup/test-helpers.ts",
+ "src/lib/logging/index.ts",
+ ],
+ "ignoreDependencies": [
+ "@types/node",
+ "@types/react",
+ "@types/react-dom",
+ "@hookform/resolvers",
+ "react-hook-form",
+ "@prisma/client",
+ "next-themes",
+ "@vitejs/plugin-react",
+ "shadcn",
+ "tailwindcss",
+ "testcontainers",
+ "tw-animate-css",
+ "@napi-rs/canvas",
+ "@openai/codex-security",
+ "postcss",
+ ],
+ "ignoreBinaries": ["docker", "docker-compose", "pg_isready", "tsx", "cosign"],
+ // Scoped to a single file: an export used only inside its own module is not
+ // a finding. It is also why the default pass cannot answer the question the
+ // production pass answers — this setting is about exports, not reachability.
+ "ignoreExportsUsedInFile": true,
+ "ignoreIssues": {
+ "src/components/ui/**": ["exports", "types"],
+ "src/lib/validations/**": ["types"],
+ "src/hooks/_internal/use-query-client-safe.ts": ["exports"],
+ "src/lib/google-health/sync.ts": ["exports"],
+ },
+}
diff --git a/src/__tests__/account-payload-consumer-guard.test.ts b/src/__tests__/account-payload-consumer-guard.test.ts
index 86cb77d27..e4c2fda78 100644
--- a/src/__tests__/account-payload-consumer-guard.test.ts
+++ b/src/__tests__/account-payload-consumer-guard.test.ts
@@ -55,6 +55,12 @@ const CONSUMER_ROOTS = [
join(SRC, "app"),
];
+/**
+ * Pruned on purpose: the generated Prisma client, build output, vendored
+ * dependencies, and the suites themselves are not the tree under audit. Note
+ * what is NOT pruned — this walk descends into dot-prefixed directories, so
+ * `src/app/.well-known` is inside the sweep. `fs.globSync` would drop it.
+ */
const SKIP_DIRS = new Set([
"__tests__",
"__mocks__",
diff --git a/src/__tests__/acting-account-boundary-guard.test.ts b/src/__tests__/acting-account-boundary-guard.test.ts
index 3d9690e76..7081f57cc 100644
--- a/src/__tests__/acting-account-boundary-guard.test.ts
+++ b/src/__tests__/acting-account-boundary-guard.test.ts
@@ -15,19 +15,17 @@
*/
import { describe, it, expect } from "vitest";
import { readFileSync } from "node:fs";
-import { globSync } from "node:fs";
-import { join, sep } from "node:path";
+import { join } from "node:path";
+
+import { walkSourceFiles } from "./helpers/source-files";
const SRC = join(process.cwd(), "src");
function sourceFiles(): string[] {
- return globSync("**/*.{ts,tsx}", { cwd: SRC })
- .filter(
- (p) => !p.startsWith(`generated${sep}`) && !p.startsWith("generated/"),
- )
+ return walkSourceFiles(SRC, { floor: 3000 })
+ .filter((p) => !p.startsWith("generated/"))
.filter((p) => !p.includes("__tests__"))
.filter((p) => !p.endsWith(".test.ts") && !p.endsWith(".test.tsx"))
- .map((p) => p.split(sep).join("/"))
.sort();
}
diff --git a/src/__tests__/ai-consent-enforcement-guard.test.ts b/src/__tests__/ai-consent-enforcement-guard.test.ts
index ccdb21426..25b5173ab 100644
--- a/src/__tests__/ai-consent-enforcement-guard.test.ts
+++ b/src/__tests__/ai-consent-enforcement-guard.test.ts
@@ -33,20 +33,18 @@
*/
import { describe, it, expect } from "vitest";
import { readFileSync } from "node:fs";
-import { join, sep } from "node:path";
-import { globSync } from "node:fs";
+import { join } from "node:path";
+
+import { walkSourceFiles } from "./helpers/source-files";
const SRC = join(process.cwd(), "src");
/** Every non-test `.ts` / `.tsx` under `src/`, minus the generated client. */
function sourceFiles(): string[] {
- return globSync("**/*.{ts,tsx}", { cwd: SRC })
- .filter(
- (p) => !p.startsWith(`generated${sep}`) && !p.startsWith("generated/"),
- )
+ return walkSourceFiles(SRC, { floor: 3000 })
+ .filter((p) => !p.startsWith("generated/"))
.filter((p) => !p.includes("__tests__"))
.filter((p) => !p.endsWith(".test.ts") && !p.endsWith(".test.tsx"))
- .map((p) => p.split(sep).join("/"))
.sort();
}
diff --git a/src/__tests__/bearer-scope-enforcement-guard.test.ts b/src/__tests__/bearer-scope-enforcement-guard.test.ts
index b1c721cbb..ffd5175ee 100644
--- a/src/__tests__/bearer-scope-enforcement-guard.test.ts
+++ b/src/__tests__/bearer-scope-enforcement-guard.test.ts
@@ -14,8 +14,9 @@
*/
import { describe, it, expect } from "vitest";
import { readFileSync } from "node:fs";
-import { join, sep } from "node:path";
-import { globSync } from "node:fs";
+import { join } from "node:path";
+
+import { walkSourceFiles } from "./helpers/source-files";
const SRC = join(process.cwd(), "src");
@@ -24,13 +25,10 @@ const SRC = join(process.cwd(), "src");
* client (9 MB; never read it) and test files themselves.
*/
function sourceFiles(): string[] {
- return globSync("**/*.{ts,tsx}", { cwd: SRC })
- .filter(
- (p) => !p.startsWith(`generated${sep}`) && !p.startsWith("generated/"),
- )
+ return walkSourceFiles(SRC, { floor: 3000 })
+ .filter((p) => !p.startsWith("generated/"))
.filter((p) => !p.includes("__tests__"))
.filter((p) => !p.endsWith(".test.ts") && !p.endsWith(".test.tsx"))
- .map((p) => p.split(sep).join("/"))
.sort();
}
diff --git a/src/__tests__/cycle-verdict-single-decider-guard.test.ts b/src/__tests__/cycle-verdict-single-decider-guard.test.ts
index 4d39d23bc..4214e2a0d 100644
--- a/src/__tests__/cycle-verdict-single-decider-guard.test.ts
+++ b/src/__tests__/cycle-verdict-single-decider-guard.test.ts
@@ -28,6 +28,12 @@ const ROOT = process.cwd();
/** Client-side code: the component tree, the hooks, and the app's pages. */
const CLIENT_ROOTS = ["src/components", "src/hooks", "src/app"];
+/**
+ * Pruned on purpose: the generated Prisma client, build output, vendored
+ * dependencies, and the suites themselves are not the tree under audit. Note
+ * what is NOT pruned — this walk descends into dot-prefixed directories, so
+ * `src/app/.well-known` is inside the sweep. `fs.globSync` would drop it.
+ */
const SKIP_DIRS = new Set(["__tests__", "node_modules", ".next", "generated"]);
function walk(dir: string, out: string[] = []): string[] {
diff --git a/src/__tests__/daily-reads-refetch-guard.test.ts b/src/__tests__/daily-reads-refetch-guard.test.ts
index 3bc244c37..a466e4f23 100644
--- a/src/__tests__/daily-reads-refetch-guard.test.ts
+++ b/src/__tests__/daily-reads-refetch-guard.test.ts
@@ -25,8 +25,9 @@
*/
import { describe, it, expect } from "vitest";
import { readFileSync } from "node:fs";
-import { join, sep } from "node:path";
-import { globSync } from "node:fs";
+import { join } from "node:path";
+
+import { walkSourceFiles } from "./helpers/source-files";
const SRC = join(process.cwd(), "src");
@@ -49,13 +50,10 @@ const INVALIDATION =
const REFETCH = /refetchInactiveDailyReads\(/g;
function sourceFiles(): string[] {
- return globSync("**/*.{ts,tsx}", { cwd: SRC })
- .filter(
- (p) => !p.startsWith(`generated${sep}`) && !p.startsWith("generated/"),
- )
+ return walkSourceFiles(SRC, { floor: 3000 })
+ .filter((p) => !p.startsWith("generated/"))
.filter((p) => !p.includes("__tests__"))
.filter((p) => !p.endsWith(".test.ts") && !p.endsWith(".test.tsx"))
- .map((p) => p.split(sep).join("/"))
.sort();
}
@@ -108,6 +106,13 @@ const NO_REFETCH_ALLOWLIST = [
].sort();
describe("daily-reads refetch guard", () => {
+ // A sweep that finds nothing agrees with every allowlist, so the size of
+ // the tree being read is asserted before anything is concluded from it.
+ // Pinned below the real source-file count with headroom, not at one.
+ it("reads the tree it claims to sweep", () => {
+ expect(sourceFiles().length).toBeGreaterThan(1500);
+ });
+
it("the blessed helper pairs the bundle invalidation with the inactive refetch", () => {
const src = read(FACTORY);
// The one canonical entry point every intake surface routes through.
diff --git a/src/__tests__/destructive-control-guard.test.ts b/src/__tests__/destructive-control-guard.test.ts
index a23fdcdd8..bbface639 100644
--- a/src/__tests__/destructive-control-guard.test.ts
+++ b/src/__tests__/destructive-control-guard.test.ts
@@ -49,8 +49,10 @@
* red at the call site.
*/
import { describe, it, expect } from "vitest";
-import { readFileSync, globSync } from "node:fs";
-import { join, sep } from "node:path";
+import { readFileSync } from "node:fs";
+import { join } from "node:path";
+
+import { walkSourceFiles } from "./helpers/source-files";
const SRC = join(process.cwd(), "src");
@@ -558,8 +560,7 @@ const CONTROL_ROOTS = ["app/", "components/", "hooks/"];
const EXCLUDED_PREFIXES = ["app/api/", "generated/", "lib/"];
function sourceFiles(): string[] {
- return globSync("**/*.{ts,tsx}", { cwd: SRC })
- .map((p) => p.split(sep).join("/"))
+ return walkSourceFiles(SRC, { floor: 3000 })
.filter((p) => !p.includes("__tests__"))
.filter((p) => !p.endsWith(".test.ts") && !p.endsWith(".test.tsx"))
.sort();
diff --git a/src/__tests__/display-transform-rounding-guard.test.ts b/src/__tests__/display-transform-rounding-guard.test.ts
index ce02fa091..d0d44e943 100644
--- a/src/__tests__/display-transform-rounding-guard.test.ts
+++ b/src/__tests__/display-transform-rounding-guard.test.ts
@@ -23,9 +23,11 @@
* are pinned to a named allowlist, so a new converted-value surface
* cannot quietly join them.
*/
-import { readFileSync, globSync } from "node:fs";
+import { readFileSync } from "node:fs";
import { join } from "node:path";
+import { walkSourceFiles } from "./helpers/source-files";
+
import { describe, it, expect } from "vitest";
import {
@@ -69,7 +71,7 @@ const RAW_SCALE_READ =
/\b\w*[Tt]ransform(?:For\([^)]*\))?\??\.(factor|offset)\b/;
function sourceFiles(): string[] {
- return globSync("**/*.{ts,tsx}", { cwd: SRC }).filter(
+ return walkSourceFiles(SRC, { floor: 3000 }).filter(
(rel) =>
// Never walk the Prisma client, and skip the suites that legitimately
// exercise the raw arithmetic they are asserting about.
diff --git a/src/__tests__/dynamic-key-exhaustiveness.test.ts b/src/__tests__/dynamic-key-exhaustiveness.test.ts
index d5d2af4ac..7b5a539d9 100644
--- a/src/__tests__/dynamic-key-exhaustiveness.test.ts
+++ b/src/__tests__/dynamic-key-exhaustiveness.test.ts
@@ -132,6 +132,12 @@ const REGISTRY: readonly KeySpace[] = [
];
describe("dynamic-key exhaustiveness (enum-derived)", () => {
+ // Six shipped locales. An empty read of `messages/` would register zero
+ // per-locale cases and the suite would report green over nothing.
+ it("reads every shipped locale bundle", () => {
+ expect(LOCALES.length).toBeGreaterThanOrEqual(6);
+ });
+
it("discovers all six shipped locales", () => {
expect(LOCALES.map((l) => l.locale).sort()).toEqual([
"de",
diff --git a/src/__tests__/e2e-route-cleanup-guard.test.ts b/src/__tests__/e2e-route-cleanup-guard.test.ts
index 0e7613914..0a9d7af58 100644
--- a/src/__tests__/e2e-route-cleanup-guard.test.ts
+++ b/src/__tests__/e2e-route-cleanup-guard.test.ts
@@ -14,8 +14,10 @@
* a runtime property, proven by the suite itself.
*/
import { describe, it, expect } from "vitest";
-import { readFileSync, globSync } from "node:fs";
-import { join, sep } from "node:path";
+import { readFileSync } from "node:fs";
+import { join } from "node:path";
+
+import { walkSourceFiles } from "./helpers/source-files";
const E2E = join(process.cwd(), "e2e");
@@ -33,9 +35,7 @@ const DIRECT_IMPORT_ALLOWLIST: Record = {
};
function e2eFiles(): string[] {
- return globSync("**/*.ts", { cwd: E2E })
- .map((p) => p.split(sep).join("/"))
- .sort();
+ return walkSourceFiles(E2E, { floor: 40, extensions: [".ts"] }).sort();
}
function read(rel: string): string {
diff --git a/src/__tests__/external-id-stability-guard.test.ts b/src/__tests__/external-id-stability-guard.test.ts
index 8e3abe525..c21c1ead7 100644
--- a/src/__tests__/external-id-stability-guard.test.ts
+++ b/src/__tests__/external-id-stability-guard.test.ts
@@ -23,20 +23,18 @@
*/
import { describe, it, expect } from "vitest";
import { readFileSync } from "node:fs";
-import { join, sep } from "node:path";
-import { globSync } from "node:fs";
+import { join } from "node:path";
+
+import { walkSourceFiles } from "./helpers/source-files";
const SRC = join(process.cwd(), "src");
const VALIDATOR_MODULE = "@/lib/validations/external-id";
function sourceFiles(): string[] {
- return globSync("**/*.{ts,tsx}", { cwd: SRC })
- .filter(
- (p) => !p.startsWith(`generated${sep}`) && !p.startsWith("generated/"),
- )
+ return walkSourceFiles(SRC, { floor: 3000 })
+ .filter((p) => !p.startsWith("generated/"))
.filter((p) => !p.includes("__tests__"))
.filter((p) => !p.endsWith(".test.ts") && !p.endsWith(".test.tsx"))
- .map((p) => p.split(sep).join("/"))
.sort();
}
diff --git a/src/__tests__/helpers/source-files.ts b/src/__tests__/helpers/source-files.ts
new file mode 100644
index 000000000..0fb711277
--- /dev/null
+++ b/src/__tests__/helpers/source-files.ts
@@ -0,0 +1,75 @@
+/**
+ * The one file walker the structural guards share.
+ *
+ * ## Why this exists rather than `fs.globSync`
+ *
+ * A guard that freezes a discovered set against an allowlist is only as
+ * honest as its idea of "every source file". `fs.globSync` has a rule that
+ * silently narrows that idea: a glob's `*` never matches a leading dot, so
+ * `**\/*.{ts,tsx}` skips every dot-prefixed directory without saying so.
+ *
+ * `src/app/.well-known/` holds three live route modules — the two OAuth
+ * discovery documents the MCP server publishes and the
+ * apple-app-site-association handler. Every guard built on `globSync` walked
+ * straight past all three and reported a clean sweep over a tree with a hole
+ * in it. Demonstrated, not assumed: an unlisted `getSession()` call planted
+ * in `app/.well-known/oauth-authorization-server/route.ts` left
+ * `session-surface-guard` at 9 passed, and fails it once the walk goes
+ * through here.
+ *
+ * `readdirSync(root, { recursive: true })` has no dot rule. It is the reason
+ * `delegable-surface-guard.test.ts` already walks by hand; this helper makes
+ * that the shared behaviour instead of one guard's private correction.
+ *
+ * ## Why `floor` is required and not optional
+ *
+ * The other way a sweep goes quiet is by finding nothing at all: an empty
+ * match set agrees with an empty allowlist, and the guard passes on a tree it
+ * never read. A wrong root, a renamed directory, or a walk that throws away
+ * every entry all look exactly like compliance. So every caller states the
+ * size it expects to walk, pinned below the real count with headroom, and a
+ * walk that comes back smaller throws instead of reporting a clean sweep.
+ * The floor covers the walk; a guard that filters further is still on the
+ * hook for a floor over its own narrowed set.
+ *
+ * ## What it returns
+ *
+ * Posix-separated paths relative to `root`, sorted, so a guard's output and
+ * its failure message read the same on every platform. Directory entries are
+ * dropped by the extension filter.
+ *
+ * ## What it does not do
+ *
+ * It applies no exclusions of its own — not `generated/`, not `__tests__`,
+ * not `.test.ts`. Each guard states its own, because what counts as out of
+ * scope is the guard's claim to make, not the walker's.
+ *
+ * And it must not be rooted at the repository root. Descending into
+ * dot-prefixed directories is the whole point, but at the root that means
+ * `.git` and — on a maintainer's machine mid-release — the `.wt-*` sibling
+ * worktrees, each a full second copy of the tree that would double every
+ * match. Every caller roots at `src/`, `e2e/` or a subtree of one.
+ */
+import { readdirSync } from "node:fs";
+import { sep } from "node:path";
+
+export function walkSourceFiles(
+ root: string,
+ options: { floor: number; extensions?: readonly string[] },
+): string[] {
+ const extensions = options.extensions ?? [".ts", ".tsx"];
+ const files = readdirSync(root, { recursive: true })
+ .map((entry) => String(entry).split(sep).join("/"))
+ .filter((rel) => extensions.some((ext) => rel.endsWith(ext)))
+ .sort();
+
+ if (files.length < options.floor) {
+ throw new Error(
+ `walkSourceFiles(${root}) found ${files.length} file(s) matching ` +
+ `${extensions.join(", ")}, below the stated floor of ${options.floor}. ` +
+ `A sweep this small is a broken walk, not a clean tree — check the ` +
+ `root before lowering the floor.`,
+ );
+ }
+ return files;
+}
diff --git a/src/__tests__/i18n-call-site-coverage.test.ts b/src/__tests__/i18n-call-site-coverage.test.ts
index e236f05eb..c4969259c 100644
--- a/src/__tests__/i18n-call-site-coverage.test.ts
+++ b/src/__tests__/i18n-call-site-coverage.test.ts
@@ -27,6 +27,12 @@ const ROOT = join(__dirname, "../..");
const SRC = join(ROOT, "src");
const EN_BUNDLE_PATH = join(ROOT, "messages/en.json");
+/**
+ * Pruned on purpose: the generated Prisma client, build output, vendored
+ * dependencies, and the suites themselves are not the tree under audit. Note
+ * what is NOT pruned — this walk descends into dot-prefixed directories, so
+ * `src/app/.well-known` is inside the sweep. `fs.globSync` would drop it.
+ */
const SKIP_DIRS = new Set(["__tests__", "node_modules", ".next", "generated"]);
function walk(dir: string, out: string[] = []): string[] {
diff --git a/src/__tests__/i18n-drift-guard.test.ts b/src/__tests__/i18n-drift-guard.test.ts
index f1348fc4f..e373f1380 100644
--- a/src/__tests__/i18n-drift-guard.test.ts
+++ b/src/__tests__/i18n-drift-guard.test.ts
@@ -115,6 +115,12 @@ const REQUIRED_KEY_SETS: Array<{
];
describe("v1.4.27 B6 i18n drift-guard — required keys present in every locale", () => {
+ // Six shipped locales. An empty read of `messages/` would register zero
+ // per-locale cases and the suite would report green over nothing.
+ it("reads every shipped locale bundle", () => {
+ expect(LOCALES.length).toBeGreaterThanOrEqual(6);
+ });
+
for (const { label, required } of REQUIRED_KEY_SETS) {
describe(label, () => {
it.each(LOCALES)(
diff --git a/src/__tests__/i18n-reverse-coverage.test.ts b/src/__tests__/i18n-reverse-coverage.test.ts
index c55e5dae6..1feac2a38 100644
--- a/src/__tests__/i18n-reverse-coverage.test.ts
+++ b/src/__tests__/i18n-reverse-coverage.test.ts
@@ -35,6 +35,12 @@ const ROOT = join(__dirname, "../..");
const SRC = join(ROOT, "src");
const EN_BUNDLE_PATH = join(ROOT, "messages/en.json");
+/**
+ * Pruned on purpose: the generated Prisma client, build output, vendored
+ * dependencies, and the suites themselves are not the tree under audit. Note
+ * what is NOT pruned — this walk descends into dot-prefixed directories, so
+ * `src/app/.well-known` is inside the sweep. `fs.globSync` would drop it.
+ */
const SKIP_DIRS = new Set(["__tests__", "node_modules", ".next", "generated"]);
/**
diff --git a/src/__tests__/inventory-write-isolation.test.ts b/src/__tests__/inventory-write-isolation.test.ts
index 6e38fb16b..b24c43bcb 100644
--- a/src/__tests__/inventory-write-isolation.test.ts
+++ b/src/__tests__/inventory-write-isolation.test.ts
@@ -22,6 +22,12 @@ import { join, relative, sep } from "node:path";
const ROOT = join(__dirname, "../..");
const SRC = join(ROOT, "src");
+/**
+ * Pruned on purpose: the generated Prisma client, build output, vendored
+ * dependencies, and the suites themselves are not the tree under audit. Note
+ * what is NOT pruned — this walk descends into dot-prefixed directories, so
+ * `src/app/.well-known` is inside the sweep. `fs.globSync` would drop it.
+ */
const SKIP_DIRS = new Set(["__tests__", "node_modules", ".next", "generated"]);
/**
@@ -61,6 +67,13 @@ function walk(dir: string, out: string[] = []): string[] {
}
describe("medicationInventoryItem write isolation", () => {
+ // A sweep that finds nothing agrees with every allowlist, so the size of
+ // the tree being read is asserted before anything is concluded from it.
+ // Pinned below the real source-file count with headroom, not at one.
+ it("reads the tree it claims to sweep", () => {
+ expect(walk(SRC).length).toBeGreaterThan(1500);
+ });
+
it("no file outside the consumption module + inventory surfaces writes inventory items", () => {
const offenders: string[] = [];
for (const file of walk(SRC)) {
diff --git a/src/__tests__/medications-efficacy-verb-guard.test.ts b/src/__tests__/medications-efficacy-verb-guard.test.ts
index a85e3db8c..867491e68 100644
--- a/src/__tests__/medications-efficacy-verb-guard.test.ts
+++ b/src/__tests__/medications-efficacy-verb-guard.test.ts
@@ -80,6 +80,12 @@ function flatten(obj: unknown, prefix: string, out: [string, string][]): void {
}
describe("medications.efficacy — banned verdict/advice verbs", () => {
+ // Six shipped locales. An empty read of `messages/` would register zero
+ // per-locale cases and the suite would report green over nothing.
+ it("reads every shipped locale bundle", () => {
+ expect(LOCALES.length).toBeGreaterThanOrEqual(6);
+ });
+
it.each(LOCALES)("%s carries no forbidden verb", (file) => {
const bundle = JSON.parse(readFileSync(join(MESSAGES, file), "utf8")) as {
medications?: { efficacy?: unknown };
diff --git a/src/__tests__/migration-number-uniqueness.test.ts b/src/__tests__/migration-number-uniqueness.test.ts
index de7ea3c53..14bc18ed4 100644
--- a/src/__tests__/migration-number-uniqueness.test.ts
+++ b/src/__tests__/migration-number-uniqueness.test.ts
@@ -64,6 +64,9 @@ describe("prisma migrations — numeric prefixes are unique", () => {
const numbered = readdirSync(MIGRATIONS_DIR, {
withFileTypes: true,
}).filter((e) => e.isDirectory() && NUMBERED_MIGRATION.test(e.name));
- expect(numbered.length).toBeGreaterThan(0);
+ // Not `> 0`: a walk that returns a single directory is as broken as one
+ // that returns none, and both would pass a zero floor. Pinned below the
+ // real migration count with headroom.
+ expect(numbered.length).toBeGreaterThan(200);
});
});
diff --git a/src/__tests__/report-selection-egress-guard.test.ts b/src/__tests__/report-selection-egress-guard.test.ts
index 0601165d1..8f516f69c 100644
--- a/src/__tests__/report-selection-egress-guard.test.ts
+++ b/src/__tests__/report-selection-egress-guard.test.ts
@@ -19,6 +19,12 @@ import { readFileSync, readdirSync, statSync } from "node:fs";
import { join } from "node:path";
const SRC = join(__dirname, "..");
+/**
+ * Pruned on purpose: the generated Prisma client, build output, vendored
+ * dependencies, and the suites themselves are not the tree under audit. Note
+ * what is NOT pruned — this walk descends into dot-prefixed directories, so
+ * `src/app/.well-known` is inside the sweep. `fs.globSync` would drop it.
+ */
const SKIP_DIRS = new Set(["__tests__", "node_modules", ".next", "generated"]);
/**
@@ -52,6 +58,13 @@ function stripComments(source: string): string {
}
describe("doctor-report aggregator egress", () => {
+ // A sweep that finds nothing agrees with every allowlist, so the size of
+ // the tree being read is asserted before anything is concluded from it.
+ // Pinned below the real source-file count with headroom, not at one.
+ it("reads the tree it claims to sweep", () => {
+ expect(walk(SRC).length).toBeGreaterThan(1500);
+ });
+
it("keeps the set of aggregator callers frozen", () => {
const found: string[] = [];
for (const file of walk(SRC)) {
diff --git a/src/__tests__/session-surface-guard.test.ts b/src/__tests__/session-surface-guard.test.ts
index 067bd97d3..0d49280d7 100644
--- a/src/__tests__/session-surface-guard.test.ts
+++ b/src/__tests__/session-surface-guard.test.ts
@@ -34,8 +34,9 @@
*/
import { describe, it, expect } from "vitest";
import { readFileSync } from "node:fs";
-import { join, sep } from "node:path";
-import { globSync } from "node:fs";
+import { join } from "node:path";
+
+import { walkSourceFiles } from "./helpers/source-files";
const SRC = join(process.cwd(), "src");
@@ -46,8 +47,8 @@ const SRC = join(process.cwd(), "src");
* `app/`) and never read — see CLAUDE.md.
*/
function appFiles(): string[] {
- return globSync("app/**/*.{ts,tsx}", { cwd: SRC })
- .map((p) => p.split(sep).join("/"))
+ return walkSourceFiles(SRC, { floor: 3000 })
+ .filter((p) => p.startsWith("app/"))
.filter((p) => !p.includes("__tests__"))
.filter((p) => !p.endsWith(".test.ts") && !p.endsWith(".test.tsx"))
.sort();
diff --git a/src/__tests__/step-up-elevation-guard.test.ts b/src/__tests__/step-up-elevation-guard.test.ts
index 7b0581c8f..a8c4ec8b6 100644
--- a/src/__tests__/step-up-elevation-guard.test.ts
+++ b/src/__tests__/step-up-elevation-guard.test.ts
@@ -24,19 +24,17 @@
*/
import { describe, it, expect } from "vitest";
import { readFileSync } from "node:fs";
-import { join, sep } from "node:path";
-import { globSync } from "node:fs";
+import { join } from "node:path";
+
+import { walkSourceFiles } from "./helpers/source-files";
const SRC = join(process.cwd(), "src");
function sourceFiles(): string[] {
- return globSync("**/*.{ts,tsx}", { cwd: SRC })
- .filter(
- (p) => !p.startsWith(`generated${sep}`) && !p.startsWith("generated/"),
- )
+ return walkSourceFiles(SRC, { floor: 3000 })
+ .filter((p) => !p.startsWith("generated/"))
.filter((p) => !p.includes("__tests__"))
.filter((p) => !p.endsWith(".test.ts") && !p.endsWith(".test.tsx"))
- .map((p) => p.split(sep).join("/"))
.sort();
}
@@ -120,6 +118,13 @@ const ELEVATION_ROUTES = [
].sort();
describe("S1 — the elevation-accepting route set is frozen", () => {
+ // A sweep that finds nothing agrees with every allowlist, so the size of
+ // the tree being read is asserted before anything is concluded from it.
+ // Pinned below the real source-file count with headroom, not at one.
+ it("reads the tree it claims to sweep", () => {
+ expect(sourceFiles().length).toBeGreaterThan(1500);
+ });
+
it("only the known MFA-management mutations call the gate", () => {
const found = callers("requireMfaManagementAuth", API_HANDLER).filter(
(rel) => rel !== "lib/api-handler.ts",
diff --git a/src/__tests__/success-affordance-guard.test.ts b/src/__tests__/success-affordance-guard.test.ts
index d200f6a9a..75d1669f8 100644
--- a/src/__tests__/success-affordance-guard.test.ts
+++ b/src/__tests__/success-affordance-guard.test.ts
@@ -493,6 +493,13 @@ function markerCounts(
}
describe("success affordance — owned by the outcome module, pinned everywhere else", () => {
+ // A sweep that finds nothing agrees with every allowlist, so the size of
+ // the tree being read is asserted before anything is concluded from it.
+ // Pinned below the real component-file count with headroom, not at one.
+ it("reads the tree it claims to sweep", () => {
+ expect(componentSourceFiles().length).toBeGreaterThan(400);
+ });
+
it("lets no unlisted component reach for the success affordance", () => {
const unlisted = componentSourceFiles()
.filter((path) => !path.startsWith(PRESENTATION_DIR))
diff --git a/src/__tests__/unit-preference-display-guard.test.ts b/src/__tests__/unit-preference-display-guard.test.ts
index 10e0598d7..b42229280 100644
--- a/src/__tests__/unit-preference-display-guard.test.ts
+++ b/src/__tests__/unit-preference-display-guard.test.ts
@@ -11,9 +11,10 @@
* Mutation check: add `unit="kg"` back to any swept page and this goes red.
*/
import { readFileSync } from "node:fs";
-import { globSync } from "node:fs";
import { join } from "node:path";
+import { walkSourceFiles } from "./helpers/source-files";
+
import { describe, it, expect } from "vitest";
import {
@@ -74,9 +75,11 @@ const HEIGHT_CONTROL = join(
function sweptFiles(): string[] {
return [
join(SRC, "app", "page-client.tsx"),
- ...globSync("app/insights/**/page.tsx", { cwd: SRC }).map((rel) =>
- join(SRC, rel),
- ),
+ ...walkSourceFiles(SRC, { floor: 700, extensions: [".tsx"] })
+ .filter(
+ (rel) => rel.startsWith("app/insights/") && rel.endsWith("/page.tsx"),
+ )
+ .map((rel) => join(SRC, rel)),
...TARGET_SURFACES,
];
}
diff --git a/src/__tests__/vorsorge-due-bucket-single-home-guard.test.ts b/src/__tests__/vorsorge-due-bucket-single-home-guard.test.ts
index 19811de76..bc960b9cf 100644
--- a/src/__tests__/vorsorge-due-bucket-single-home-guard.test.ts
+++ b/src/__tests__/vorsorge-due-bucket-single-home-guard.test.ts
@@ -18,8 +18,10 @@
* settles is that there is only one of it.
*/
import { describe, it, expect } from "vitest";
-import { readFileSync, globSync } from "node:fs";
-import { join, sep } from "node:path";
+import { readFileSync } from "node:fs";
+import { join } from "node:path";
+
+import { walkSourceFiles } from "./helpers/source-files";
const SRC = join(process.cwd(), "src");
@@ -71,8 +73,7 @@ const FIX_THE_ZONE = [
].join(" ");
function sourceFiles(): string[] {
- return globSync("**/*.{ts,tsx}", { cwd: SRC })
- .map((p) => p.split(sep).join("/"))
+ return walkSourceFiles(SRC, { floor: 3000 })
.filter((p) => !p.startsWith("generated/"))
.filter((p) => !p.includes("__tests__/"))
.sort();
@@ -94,6 +95,13 @@ function declaresTheBucket(source: string): boolean {
}
describe("the checkup due-day bucket has one home", () => {
+ // A sweep that finds nothing agrees with every allowlist, so the size of
+ // the tree being read is asserted before anything is concluded from it.
+ // Pinned below the real source-file count with headroom, not at one.
+ it("reads the tree it claims to sweep", () => {
+ expect(sourceFiles().length).toBeGreaterThan(1500);
+ });
+
it("is declared in exactly one file", () => {
const declaring = sourceFiles().filter((rel) =>
declaresTheBucket(read(rel)),
diff --git a/src/__tests__/workout-canonical-identity-guard.test.ts b/src/__tests__/workout-canonical-identity-guard.test.ts
index 7c976d1ec..73fb493fb 100644
--- a/src/__tests__/workout-canonical-identity-guard.test.ts
+++ b/src/__tests__/workout-canonical-identity-guard.test.ts
@@ -21,8 +21,9 @@
*/
import { describe, it, expect } from "vitest";
import { readFileSync } from "node:fs";
-import { globSync } from "node:fs";
-import { join, sep } from "node:path";
+import { join } from "node:path";
+
+import { walkSourceFiles } from "./helpers/source-files";
const SRC = join(process.cwd(), "src");
@@ -52,8 +53,7 @@ const IDENTITY_OWNER: Record = {
const EXPECTED_CALL_SITES = Object.keys(IDENTITY_OWNER).sort();
function sourceFiles(): string[] {
- return globSync("**/*.{ts,tsx}", { cwd: SRC })
- .map((p) => p.split(sep).join("/"))
+ return walkSourceFiles(SRC, { floor: 3000 })
.filter((p) => !p.startsWith("generated/"))
.filter((p) => !p.includes("__tests__"))
.filter((p) => !p.endsWith(".test.ts") && !p.endsWith(".test.tsx"))
@@ -81,6 +81,13 @@ function invokesPicker(source: string): boolean {
}
describe("the picker's call-site set is frozen", () => {
+ // A sweep that finds nothing agrees with every allowlist, so the size of
+ // the tree being read is asserted before anything is concluded from it.
+ // Pinned below the real source-file count with headroom, not at one.
+ it("reads the tree it claims to sweep", () => {
+ expect(sourceFiles().length).toBeGreaterThan(1500);
+ });
+
it("has exactly the call sites this file acknowledges", () => {
const found = sourceFiles()
.filter((rel) => rel !== PICKER)
diff --git a/src/__tests__/workout-sport-provenance-guard.test.ts b/src/__tests__/workout-sport-provenance-guard.test.ts
index d0a34e2a3..47af62008 100644
--- a/src/__tests__/workout-sport-provenance-guard.test.ts
+++ b/src/__tests__/workout-sport-provenance-guard.test.ts
@@ -20,20 +20,18 @@
*/
import { describe, it, expect } from "vitest";
import { readFileSync } from "node:fs";
-import { join, sep } from "node:path";
-import { globSync } from "node:fs";
+import { join } from "node:path";
+
+import { walkSourceFiles } from "./helpers/source-files";
const SRC = join(process.cwd(), "src");
/** Every non-test `.ts` / `.tsx` under `src/`, minus the generated client. */
function sourceFiles(): string[] {
- return globSync("**/*.{ts,tsx}", { cwd: SRC })
- .filter(
- (p) => !p.startsWith(`generated${sep}`) && !p.startsWith("generated/"),
- )
+ return walkSourceFiles(SRC, { floor: 3000 })
+ .filter((p) => !p.startsWith("generated/"))
.filter((p) => !p.includes("__tests__"))
.filter((p) => !p.endsWith(".test.ts") && !p.endsWith(".test.tsx"))
- .map((p) => p.split(sep).join("/"))
.sort();
}
@@ -131,6 +129,13 @@ const READ_TIME_NARROWERS = [
];
describe("T1 — the set of sport-label mappers is frozen", () => {
+ // A sweep that finds nothing agrees with every allowlist, so the size of
+ // the tree being read is asserted before anything is concluded from it.
+ // Pinned below the real source-file count with headroom, not at one.
+ it("reads the tree it claims to sweep", () => {
+ expect(sourceFiles().length).toBeGreaterThan(1500);
+ });
+
it("every exported sport-type resolver is registered", () => {
const found = new Set();
for (const rel of sourceFiles()) {
diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx
index 0c499ef03..45d3e0e4e 100644
--- a/src/app/about/page.tsx
+++ b/src/app/about/page.tsx
@@ -8,9 +8,11 @@ import Link from "next/link";
*
* 1. The runtime image bundles MaxMind GeoLite2-City and
* GeoLite2-ASN databases for offline IP-to-location and
- * IP-to-ASN lookups. The CC BY-SA 4.0 licence requires the
- * attribution to be reachable from the running application,
- * not just the source repository.
+ * IP-to-ASN lookups. They ship under the GeoLite End User
+ * License Agreement, which incorporates CC BY-SA 4.0 by
+ * reference and controls over it; both want the attribution
+ * reachable from the running application, not just from the
+ * source repository.
* 2. A general home for open-source credits as the project
* accumulates more third-party data sources (already true for
* the upcoming ICD-10 reference table in the iOS Health import
@@ -35,7 +37,7 @@ import Link from "next/link";
// weakening the CSP with `unsafe-inline`.
export const dynamic = "force-dynamic";
-const LAST_UPDATED = "2026-05-15";
+const LAST_UPDATED = "2026-08-02";
export const metadata: Metadata = {
title: "About — HealthLog",
@@ -119,7 +121,9 @@ export default function AboutPage() {
>
github.com/MBombeck/HealthLog
{" "}
- and is licensed under the GNU Affero General Public License v3.0.
+ and is licensed under the PolyForm Noncommercial License 1.0.0.
+ Releases up to and including v1.15.18 were published under AGPL-3.0
+ and stay available under it.
@@ -156,6 +160,15 @@ export default function AboutPage() {
) power the offline IP-to-location and IP-to-carrier lookups that
decorate the admin login-overview audit table. They are distributed
under the{" "}
+
+ GeoLite End User License Agreement
+
+ , which incorporates the{" "}
Creative Commons Attribution-ShareAlike 4.0 International License
-
- .
+ {" "}
+ by reference and controls wherever the two disagree. The attribution
+ above is what that licence asks for.