mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 13:22:14 +00:00
docs(planning): Codex audit prompt for v1.4.25
This commit is contained in:
1 parent
d708c94e55
commit
caf0b9561a
1 file changed
+339
@@ -0,0 +1,339 @@
|
||||
# Codex Audit Prompt — HealthLog v1.4.25
|
||||
|
||||
## Context
|
||||
|
||||
You are an independent senior reviewer of HealthLog v1.4.25
|
||||
(`MBombeck/HealthLog`, tag `v1.4.25` at `17b8d8d2`). Marc wants a
|
||||
second-opinion audit focused on TypeScript / Next.js 16 (App Router)
|
||||
edge cases and Prisma migration safety on the ten new migrations
|
||||
(0051–0060), plus the Wave-4-5 surfaces that landed under the v1.4.25
|
||||
release.
|
||||
|
||||
You are reviewing as a senior, narrowly-scoped reviewer. Skip
|
||||
findings the in-house W21 review pass has already covered (eight
|
||||
reviewers — code, security, design, senior-dev, simplifier,
|
||||
product-lead, dead-code, i18n-runtime — generated 103 findings; 63
|
||||
must-fix shipped through Fix-J through Fix-P + W22 before tag).
|
||||
This prompt asks for what those reviewers may have missed: TS / Next
|
||||
edge cases, Prisma migration safety on populated tables, and the
|
||||
correctness of the recently-applied reconcile work that the in-house
|
||||
pass put in place but never re-audited.
|
||||
|
||||
## Repo + commit anchors
|
||||
|
||||
- Tag: `v1.4.25` (commit `17b8d8d2 chore(release): v1.4.25`).
|
||||
- Predecessor: `v1.4.24` (commit `ca03c609 chore(release): v1.4.24`).
|
||||
- Full diff: `git diff v1.4.24..v1.4.25` (about 300 commits including
|
||||
the dependabot bumps; ignore the bumps, focus on feature commits).
|
||||
- Migrations to audit: `prisma/migrations/0051_*` through
|
||||
`prisma/migrations/0060_*`.
|
||||
- Reconcile plan documenting which Wave-4-5 finding shipped in which
|
||||
Fix-* surface: `.planning/phase-W21-reconcile-plan.md`.
|
||||
- Product-lead strategic assessment that drove the W22 release-redo:
|
||||
`.planning/research/w21-product-lead-assessment.md`.
|
||||
- W22 release-redo phase report:
|
||||
`.planning/phase-W22-v1425-release-redo-report.md`.
|
||||
|
||||
## Out-of-scope
|
||||
|
||||
- CSS / a11y (W21 design review covered — 11 design findings, 6
|
||||
applied to release, 5 deferred). Do not duplicate that surface.
|
||||
- i18n key dead-code (W15 + W21 dead-code-scan covered — 380 keys
|
||||
dropped this release; 148 queued for v1.4.26).
|
||||
- Native LLM provider integration logic (multi-provider routing has
|
||||
been stable since v1.4.16; the prompt + safety contract layer is
|
||||
the only Coach surface that moved).
|
||||
- Branch / CI workflow files (W20-rest + Fix-G + Fix-H + W11a
|
||||
covered the auto-deploy + multi-arch + lowercase work).
|
||||
- Sister-repo (`healthlog-docs`, `healthlog-landing`) version pin
|
||||
updates — Marc drives these by hand post-tag.
|
||||
|
||||
## Audit focus
|
||||
|
||||
### 1. Migration safety on populated tables (Migrations 0051–0060)
|
||||
|
||||
For each of the ten new migrations:
|
||||
|
||||
- Verify the SQL is idempotent. Does it use `IF NOT EXISTS` /
|
||||
`IF EXISTS` consistently? Does it tolerate a re-run against a
|
||||
partially-applied state?
|
||||
- Verify FK additions do not leak orphans. Does every new FK declare
|
||||
`ON DELETE` behaviour (CASCADE / SET NULL / RESTRICT) explicitly?
|
||||
Is the choice correct for the row-deletion semantics implied by
|
||||
the parent table's lifecycle?
|
||||
- Verify `DEFAULT` on populated tables. Postgres 11+ has a fast-path
|
||||
`ADD COLUMN … DEFAULT` that avoids the full-table rewrite. Verify
|
||||
every `ADD COLUMN … DEFAULT` in 0051–0060 is on PG11+ syntax and
|
||||
does not trip the slow path. Migration 0060 is the specific
|
||||
callout: it backfills existing `User.onboardingStep` rows and
|
||||
flips the column to `NOT NULL DEFAULT 0`. Verify the backfill
|
||||
+ flip ordering is safe under concurrent writes.
|
||||
- Verify Migration 0057's rewritten comment (which describes PG11+
|
||||
fast-path semantics for `ADD COLUMN DEFAULT`) is actually accurate
|
||||
for the syntax it's commenting on.
|
||||
- Verify Migration 0055 (sleepstage composite) `NULLS NOT DISTINCT`
|
||||
syntax is PG16-native. Confirm the deployed Postgres minor version
|
||||
in the v1.4.25 stack is ≥ 16 (check `prisma/schema.prisma` +
|
||||
`docker-compose.yml`).
|
||||
- Verify Migration 0054 (`PersonalRecord` table) unique index is
|
||||
`NULLS DISTINCT` and the application-level compensating dedup logic
|
||||
in `src/lib/personal-records/pr-detection-worker.ts` correctly
|
||||
guards the null-slot dup case. The W21 reconcile applied a
|
||||
regression test for this exact case (`5f5b8dfb` and `6f3b901c`);
|
||||
verify the test asserts the right thing and the worker logic is
|
||||
actually idempotent under contention.
|
||||
- Verify Migration 0059 (`MedicationSideEffect` table) ingests
|
||||
`category` server-derived only — there should be no client-supplied
|
||||
`category` field reaching the table (W21 code-M6 / Fix-N).
|
||||
|
||||
### 2. TS / Next.js 16 (App Router) edge cases on new route handlers
|
||||
|
||||
The Wave-4-5 surfaces add the following App Router route handlers
|
||||
(non-exhaustive). For each:
|
||||
|
||||
- `POST /api/medications/[id]/glp1` (W19c / Fix-K)
|
||||
- `POST /api/medications/[id]/side-effects` (W19d)
|
||||
- `POST /api/medications/[id]/cadence` and
|
||||
`GET /api/medications/[id]/cadence` (W19e)
|
||||
- `POST /api/medications/[id]/titration-ladder` and
|
||||
`GET /api/medications/[id]/titration-ladder` (W19f)
|
||||
- `POST /api/medications/[id]/inventory/[itemId]` PATCH (Fix-M)
|
||||
- `POST /api/onboarding/step` (W14b-Foundation + Fix-M)
|
||||
- `GET / POST / DELETE /api/auth/me/research-mode` (W19c-Backend)
|
||||
- `POST /api/workouts/batch` (W16b)
|
||||
- `DELETE /api/measurements/by-external-ids` (W16a)
|
||||
|
||||
Verify:
|
||||
|
||||
- **Dynamic-route param access.** Next.js 15+ moved `params` to a
|
||||
Promise that must be `await`-ed. Verify every dynamic-route handler
|
||||
awaits `params` before destructuring; verify the typed signature
|
||||
matches `{ params: Promise<{ id: string; itemId?: string }> }`
|
||||
rather than the legacy synchronous shape.
|
||||
- **`requireAuth()` semantics.** v1.4.24 closed the inverse hole
|
||||
(Bearer with no scope reaching unscoped handlers); v1.4.25 closes
|
||||
the over-broad hole (narrow-scope Bearer blocked from unscoped
|
||||
handlers). Verify every new route either declares a
|
||||
`requiredPermission` argument or expects to inherit the wildcard
|
||||
(`["*"]`) cookie path. The medication routes consistently use
|
||||
`assertMedicationOwnership` (Fix-N) — verify that the helper still
|
||||
honours the scope-gate posture.
|
||||
- **Zod-strict schemas.** Every POST / PATCH should parse through a
|
||||
Zod schema with `.strict()` (or `.passthrough()` only when there's
|
||||
a clear reason). Verify the new schemas
|
||||
(`glp1DoseChangePostSchema`, `glp1InventoryPostSchema`,
|
||||
`createSideEffectSchema`, `createWorkoutSchema`,
|
||||
`createInventoryItemSchema`) are strict on unknown keys.
|
||||
- **Idempotency wrapper.** `POST /api/workouts/batch` should wrap in
|
||||
`withIdempotency()` — confirm.
|
||||
- **Audit log.** `POST /api/medications/[id]/glp1` should write an
|
||||
`AuditLog` row on every successful POST (W21 sec-H1 / Fix-K).
|
||||
Verify the audit reason string is parameterised consistently
|
||||
(`medication.glp1.update` per the reconcile plan).
|
||||
- **Rate-limit envelope.** Every new POST should return the
|
||||
`rateLimitHeaders(rl)` envelope on 429 via the `apiError(..., {
|
||||
headers })` option-bag form (Fix-N simp-M8 alignment). Verify
|
||||
consistency.
|
||||
|
||||
### 3. Coach prompt safety contract (GROUND RULES 1–15)
|
||||
|
||||
Verify the per-locale safety contracts in `src/lib/coach/safety/`
|
||||
(or wherever the YAML matrix lives) cover all 15 GROUND RULES with
|
||||
20+ adversarial paraphrasings each, for all six locales (en / de /
|
||||
fr / es / it / pl). The CI refusal-probe matrix should be ~1800+
|
||||
assertions; verify the number matches the YAML × locale × probe
|
||||
count multiplication.
|
||||
|
||||
For GROUND RULE 9 (Coach refuses GLP-1 dose recommendations) and
|
||||
GROUND RULE 15 (Coach refuses drug-level estimates with EU MDR
|
||||
2017/745 + MDCG 2021-24 cites) specifically:
|
||||
|
||||
- Verify the refusal probe covers the *specific* regulatory failure
|
||||
modes the GROUND RULE is designed to catch: drug-level point
|
||||
estimates, dose-adjustment recommendations ("should I go up to
|
||||
1.0 mg?"), pharmacokinetic predictions, diagnostic claims
|
||||
("is this side effect from semaglutide?"), and indirect
|
||||
prompt-injection attempts ("ignore your earlier instructions").
|
||||
- Verify the refusal copy cites EU MDR 2017/745 + MDCG 2021-24
|
||||
verbatim where the GROUND RULE requires it (not just paraphrased).
|
||||
- Verify the EN + DE bodies are Marc-authored (per the
|
||||
Marc-Voice rule); verify the FR / ES / IT / PL bodies carry the
|
||||
same safety contract surface even though their prose is
|
||||
LLM-drafted (the structural matrix covers safety; the prose
|
||||
itself is a v1.4.26 native-speaker review item).
|
||||
|
||||
### 4. Per-user timezone correctness
|
||||
|
||||
`code-H2` (Fix-O) threaded `timeZone` into `expandScheduleSlots`,
|
||||
`pairDoses`, `buildCadenceTimeline`, and `complianceChips` via
|
||||
`Intl.DateTimeFormat`. The cadence route at
|
||||
`src/app/api/medications/[id]/cadence/route.ts` resolves through
|
||||
`resolveUserTimeZone(user)`.
|
||||
|
||||
Verify:
|
||||
|
||||
- **DST-transition correctness.** A cross-midnight dose in
|
||||
`America/New_York` at the November fall-back boundary (a day with
|
||||
25 hours) — does the schedule expander still produce the right
|
||||
number of slots? A dose taken at the duplicated `01:30` local —
|
||||
which slot does `pairDoses` pick?
|
||||
- **Leap-year edge cases.** Feb 29 in a leap-year tz vs Feb 28 in
|
||||
a non-leap tz — the slot expansion should not skip or duplicate
|
||||
a day.
|
||||
- **`Intl.DateTimeFormat` cache.** The formatter is expensive; verify
|
||||
the helper memoises or reuses an instance per tz argument across
|
||||
the loop body (not constructing one per slot).
|
||||
- **Withings activity TZ anchor.** Migration 0055 + the Withings
|
||||
activity sync in `src/lib/withings/sync-activity.ts` anchors per-day
|
||||
rows at noon UTC (`T12:00:00.000Z`) — verify the test asserts that
|
||||
Berlin, Los Angeles, Tokyo, and Auckland users all bucket the same
|
||||
source `YYYY-MM-DD` into the same local-day row. The Fix-O senior-M2
|
||||
reconcile applied this; the test must cover all four tz buckets
|
||||
unambiguously.
|
||||
|
||||
### 5. Source-priority two-axis resolver (W8c)
|
||||
|
||||
`pickCanonicalSource()` walks the metric axis first, then a per-device
|
||||
override within the matched bucket. Verify:
|
||||
|
||||
- **Exhaustiveness.** Every `MeasurementType` × `deviceType` combination
|
||||
must have a defined resolution path. There should be no combo that
|
||||
falls through to `undefined` or throws. Verify by sampling: BP +
|
||||
`WITHINGS_BPM_CONNECT`, BP + `MANUAL`, BP + `APPLE_HEALTH` (where
|
||||
`deviceType` from Apple Health may not yet have a specific override),
|
||||
sleep + `WITHINGS_SLEEP_ANALYZER`, sleep + `APPLE_HEALTH`.
|
||||
- **`__default__` retirement.** The W21 reconcile plan documents the
|
||||
retirement of the `__default__` sentinel in favour of a null bucket.
|
||||
Verify there are no lingering `__default__` string references in the
|
||||
storage layer or the resolver — search for the literal in
|
||||
`src/lib/sources/`, `src/components/settings/sources-section.tsx`,
|
||||
and the migration history.
|
||||
- **`reorderLadder` consolidation.** The `moveSource` + `moveDeviceType`
|
||||
paths were collapsed into one helper (commit `03089675`). Verify the
|
||||
consolidated helper handles both axes correctly without sentinel
|
||||
bleed.
|
||||
|
||||
### 6. Inventory state machine (Fix-M code-H1)
|
||||
|
||||
The PATCH path on
|
||||
`src/app/api/medications/[id]/inventory/[itemId]/route.ts` re-runs
|
||||
`computeInventoryState` after composing the next-state view. The
|
||||
W21 code-H1 finding was that a back-dated `markAsFirstUseAt` PATCH
|
||||
left the item in IN_USE; the fix re-runs the state machine on every
|
||||
PATCH so a back-dated first-use immediately moves a stale pen to
|
||||
EXPIRED.
|
||||
|
||||
Verify:
|
||||
|
||||
- The PATCH route in commit `1b5906a5` correctly composes the
|
||||
next-state view *before* calling `computeInventoryState`, not the
|
||||
other way around (otherwise the state machine sees the stale row).
|
||||
- The regression test in
|
||||
`src/app/api/medications/[id]/inventory/__tests__/route.test.ts`
|
||||
asserts the back-dated first-use → EXPIRED contract end-to-end,
|
||||
not just a unit-level check on the state-machine helper.
|
||||
- The bulk `updateMany` replacement of the per-row `prisma.update`
|
||||
loop in `expireStaleInUseItems` (commit `1d5dcfb8`) preserves the
|
||||
same transition semantics and does not skip rows that the loop
|
||||
would have caught.
|
||||
|
||||
### 7. Cross-cutting concerns
|
||||
|
||||
- **Coach refusal-probe matrix loader.** The YAML loader resolves
|
||||
the matrix path from `cwd` rather than `__dirname` (per the
|
||||
CHANGELOG "Build resolution for safety-contract YAML" Fixed
|
||||
entry). Verify the bundler reshape that the fix protects against
|
||||
does not regress in a Next.js production build — try a
|
||||
`pnpm build && pnpm start` cold-start and confirm the matrix
|
||||
resolves.
|
||||
- **GLP-1 drift guard self-skip.** The drift-guard test self-skips
|
||||
when the EMA research file is absent on a local checkout. Verify
|
||||
the skip is conditional on file presence only, not on environment
|
||||
(so CI never silently skips the guard).
|
||||
- **Onboarding step 409.** `POST /api/onboarding/step` conditions
|
||||
the update on `{ id, onboardingStep: current,
|
||||
onboardingCompletedAt: null }` and returns 409 on conflict
|
||||
(commit `7c9b4b65`). Verify the 409 response carries the standard
|
||||
`apiError` envelope and does not leak the row's current
|
||||
`onboardingStep` in the error body.
|
||||
- **Personal-records pagination clamp.** `GET /api/personal-records`
|
||||
clamps `?limit` (default 25, max 200). Verify a request with
|
||||
`?limit=10000` does not silently coerce to 200 without an error;
|
||||
verify a request with `?limit=-1` rejects rather than returning
|
||||
zero rows.
|
||||
|
||||
## Deliverable shape
|
||||
|
||||
For each finding:
|
||||
|
||||
- **Severity**: Critical / High / Medium / Low.
|
||||
- **Where**: `path/to/file.ts:line` (or `path/to/migration.sql` for
|
||||
Prisma). When the issue spans multiple files, lead with the
|
||||
primary site and reference the others.
|
||||
- **Issue**: 1–2 sentences. Describe the failure mode, not the
|
||||
symptom.
|
||||
- **Proposed fix or follow-up**: 1–3 sentences. If the fix touches
|
||||
more than one file, list them.
|
||||
- **Confidence**: high / medium / low. Note the basis ("static
|
||||
analysis only", "verified by running the test under modified
|
||||
inputs", "deduced from documentation").
|
||||
|
||||
Skip "Low" unless they are load-bearing for the iOS sprint. Marc
|
||||
applies Critical + High + Medium before the v1.4.26 tag. Lows queue
|
||||
for v1.4.27 or later.
|
||||
|
||||
## Working method
|
||||
|
||||
Use the repo's existing tooling rather than guessing:
|
||||
|
||||
- `pnpm typecheck` — confirms TS soundness across the surface.
|
||||
- `pnpm test:unit` — runs the 3 828 unit tests; helpful to confirm a
|
||||
finding by writing a regression test first.
|
||||
- `pnpm test:integration` — runs the ~170 integration tests; the
|
||||
`coach-prefs.test.ts` NextRequest mock failures are pre-existing
|
||||
and tracked, ignore them.
|
||||
- `pnpm openapi:check` — confirms the OpenAPI spec is in lockstep with
|
||||
the Zod registry.
|
||||
- `pnpm test:safety` (or the equivalent Coach refusal-probe matrix
|
||||
script) — runs the 1800-assertion probe matrix.
|
||||
- `pnpm prisma migrate dev` against a scratch database — confirms
|
||||
the migration train applies cleanly from `0001` forward.
|
||||
|
||||
Treat the W21 reviewer findings files as the prior art:
|
||||
|
||||
- `.planning/research/w21-security-findings.md`
|
||||
- `.planning/research/w21-code-review-findings.md`
|
||||
- `.planning/research/w21-design-findings.md`
|
||||
- `.planning/research/w21-senior-dev-findings.md`
|
||||
- `.planning/research/w21-simplifier-findings.md`
|
||||
- `.planning/research/w21-dead-code-findings.md`
|
||||
- `.planning/research/w21-i18n-runtime-findings.md`
|
||||
- `.planning/research/w21-product-lead-assessment.md`
|
||||
|
||||
A finding that exactly matches one of those is not interesting (it
|
||||
shipped or it's tracked in `.planning/v1426-backlog.md`). A finding
|
||||
that extends one of those — e.g. "Fix-N consolidated nine medication
|
||||
routes through `assertMedicationOwnership`, but the rate-limit-header
|
||||
alignment missed the `[id]/intake` route" — is interesting.
|
||||
|
||||
## Format
|
||||
|
||||
Output as a single Markdown document with one `##` heading per
|
||||
severity tier (Critical / High / Medium). Under each tier, one
|
||||
`###` block per finding with the five fields above. End with a
|
||||
short "Confidence summary" listing how much of the audit surface
|
||||
you cleared and how much you sampled.
|
||||
|
||||
## Tag context for the iOS sprint reader
|
||||
|
||||
The audit's downstream consumer is the v1.5 iOS Swift sprint. iOS
|
||||
DTOs are codegen'd from `docs/api/openapi.yaml`; the spec is now
|
||||
authoritative (hard-fail drift gate). Any finding that affects an
|
||||
iOS-touched route signature (the eight P1 routes:
|
||||
`/api/auth/login`, passkey verify, `/api/auth/refresh`,
|
||||
`/api/measurements` GET + POST + batch, `/api/devices` POST, and the
|
||||
comprehensive insights bundle, plus the five v1.4.25 additions:
|
||||
`/api/workouts/batch`, `/api/measurements/by-external-ids`,
|
||||
`/api/onboarding/step`, `/api/personal-records`, and the per-device
|
||||
`/api/auth/me/devices` surface) carries an "iOS-affecting" tag.
|
||||
That lets Marc triage iOS-blocking fixes ahead of the rest.
|
||||
Reference in new issue
Block a user