Files
HealthLog/knip.jsonc
T
Marc-André Bombeck 900a5c4f1f perf: stop keeping folded samples and re-reading duplicates
The nightly folds soft-deleted every raw Apple Health sample they folded
into an hourly or daily stats row and kept it for 75 days. Nothing ever
read those rows as deletions; they only held a re-upload of the sample
off, and they were most of the measurements table and its indexes.

Ingest now refuses such a re-upload directly: a raw sample of a dense or
mean type, old enough to have been folded, whose hour or day a live
stats row covers, is a duplicate with reason folded_window. The batch
route and the Apple Health export import apply the same rule. With that
in place the dense retention and the daily-mean consolidation delete the
raw rows outright, and a new compaction-tombstone-purge job removes the
backlog: 5,000 rows a batch, one account at a time under the restore
lock, a short pause between batches, resumable because the predicate is
idempotent, queued at every boot and continuing itself while it makes
progress. A person's own deletions are never in its predicate. The sync
feed leaves compaction tombstones out and a restore does not write them
back; all three classify through the same function.

The batch route answers an exact re-sent sample without a transaction:
one read over the batch's sample ids, live or deleted, decides what the
reconciler would have decided, at the same point in the loop, so every
status, reason, counter and the cross-source merge stay as they were. A
batch of duplicates only no longer queues PR detection, a reminder sweep
or the detection audit row. Caches are evicted only for a foreground or
manual sync or a batch that wrote a hand-entered reading, and marked
stale otherwise; the targets cache follows the same split.

Also:
- POST /api/admin/maintenance/measurements queues VACUUM (ANALYZE) and a
  REINDEX INDEX CONCURRENTLY per index, largest first, on a connection of
  its own; it refuses while the purge runs, drops invalid leftover copies
  and stops at the first failed rebuild. Runbook in docs/ops.
- The pure volume queues keep their finished jobs for 96 hours instead
  of seven days, never less than the failure window plus a day.
- work_mem is set per app connection from DATABASE_WORK_MEM (16MB), also
  when the statement timeout is disabled.
- The PR-detection fallback tick re-scans only accounts whose readings
  or workouts changed since the previous tick.
- Type discovery for the status refill walks the live-rows index as a
  loose index scan instead of grouping every live row.
- The host metrics chart polls every five minutes, like its sampler.
- The batch route records per-type HealthKit arrivals.
2026-10-08 08:41:25 +02:00

180 lines
7.7 KiB
JSON

// 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 22 files as of 2026-08-31. Some are test-only by
// construction (the guard helpers under `src/__tests__/helpers/`, the
// settings section harness, a score fixture) 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.
//
// Re-measure the count when you touch this block. It said 21 until the
// v1.38.0 score fixture landed, and a number nobody re-runs drifts
// quietly until the whole paragraph reads as guesswork.
//
// One entry is on that list ON PURPOSE and should stay:
// `src/lib/signals/adapters/metric-status.ts`. Its only importer is
// `src/lib/signals/__tests__/registry-invariants.test.ts`, which is
// exactly what the module is for — it projects the signal registry onto
// the `MetricStatusMeta` shape so that test can assert the two agree
// field-for-field and catch future drift. Deleting it to clear the line
// would remove a check rather than ballast. The production pass is right
// about the mechanics here and wrong about the conclusion, which is the
// whole reason this is a review tool and not a gate.
// 2. It has false positives, though not always the same ones.
// `src/lib/pwa/sw-cache-policy.ts` is the standing example: it runs in
// every browser, `public/sw.js` mirrors its logic by hand because a
// service worker cannot import app modules, and `sw.js:83` names that
// lockstep. Nothing static can see that edge.
//
// This slot used to cite `src/lib/mood/note-backfill.ts`, reported
// because `scripts/` sits outside the tsconfig that defines the `@/`
// alias. That one no longer appears — the module and its importing
// script both still exist, so the resolution improved under us. Left
// here as a note rather than deleted, because it is the shape of the
// problem: an example that heals silently makes the surrounding
// reasoning look wrong when someone checks it.
//
// 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",
// v1.42 release-branch contract stubs: each is created ahead of its
// consumers so the packages built in parallel share one signature. Drop
// a line the moment its first consumer lands; the release integration
// checklist removes whatever is left before the tag.
"src/lib/environment/air-quality-contract.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",
"postcss",
],
"ignoreBinaries": [
"docker",
"docker-compose",
"pg_isready",
"tsx",
"cosign",
// The runtime GeoLite2 fetch extracts in pure Node, but its test builds a
// MaxMind-shaped tar.gz fixture with the POSIX `tar` (present in CI + dev).
"tar",
],
// 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.
//
// It had a second effect nobody asked for, worth knowing because it means
// the gate under-reported for a year. Until knip 6.28.0 this setting also
// exempted every RE-EXPORT: a barrel line was unconditionally marked
// "referenced in its own file", so `export { x } from "./x"` that nothing
// imported was never reported. knip 6.28.0 (PR webpro-nl/knip#1895, fixing
// issue #1894 — `--fix` deleted the source export and left the barrel line
// behind, emitting code that did not compile) dropped the exemption. The
// upgrade from 6.16.1 took this repo from 0 findings to 206 on unchanged
// code; all but two were barrel lines with no importer, and they are gone
// now. The suppression was entirely gated on this setting — with it off,
// 6.16.1 and 6.32.2 agree exactly. So a green Dead-code gate on any knip
// before 6.28.0 said nothing about re-exports.
"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"],
},
}