diff --git a/.planning/STATE.md b/.planning/STATE.md deleted file mode 100644 index 9e55ab863..000000000 --- a/.planning/STATE.md +++ /dev/null @@ -1,62 +0,0 @@ -# Project State - -## Project Reference - -See: .planning/PROJECT.md (updated 2026-06-18) - -**Core value:** The user understands their own health at a glance, coherent across every module. -**Current focus:** Milestone v1.18.6 — Phase 1 (Module Consistency + Disclaimer) ready to plan - -## Current Position - -Phase: 1 of 5 (Module Consistency + Disclaimer) -Plan: — (not yet planned) -Status: Ready to plan -Last activity: 2026-06-18 — ROADMAP.md created; 5 phases defined, 29/29 v1 requirements mapped - -Progress: [░░░░░░░░░░] 0% - -## Performance Metrics - -**Velocity:** -- Total plans completed: 0 -- Average duration: — -- Total execution time: — - -**By Phase:** - -| Phase | Plans | Total | Avg/Plan | -|-------|-------|-------|----------| -| - | - | - | - | - -*Updated after each plan completion* - -## Accumulated Context - -### Roadmap Evolution - -- Phase 1 added: v1.34.1 dashboard insights records admin and integration reliability - -### Decisions - -- **Disclaimer**: One-time onboarding acknowledgment only; remove all per-page banners; text reachable in Settings (legal). -- **BP guideline**: ESH-2023 default, no per-user toggle; ESC/AHA surfaces as context only. -- **Single-setting card**: Always wrap in a standard card, even one setting. -- **Proactive nudge**: Option A — create conversation + write nudge as initial message BEFORE notification dispatch. -- **Deep knowledge articles**: Deferred to docs.healthlog.dev (hybrid); app keeps concise cited tooltips. - -### Pending Todos - -1 pending — dependency advisory remediation is scheduled for v1.34.2. - -### Blockers/Concerns - -- Phase 5 (SHIP) is strictly sequential; depends on all four parallel phases merging cleanly with green CI. -- SLP-01 (sleep stage breakdown) has a deferred dependency: real hypnogram ingestion is v2 (SLP-FUT). Decision = remove the misleading element rather than add ingestion in this release. -- SHIP-02 requires OpenAPI regen; any schema touch in Phases 1–4 must be flagged to the Phase 5 executor. - -## Session Continuity - -Last session: 2026-06-18 -Stopped at: Roadmap created; all 29 v1 requirements mapped; ready to plan Phase 1. -Resume file: None diff --git a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-01-SUMMARY.md b/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-01-SUMMARY.md deleted file mode 100644 index ee64b085b..000000000 --- a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-01-SUMMARY.md +++ /dev/null @@ -1,126 +0,0 @@ ---- -phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability -plan: "01" -subsystem: testing -tags: [mcp, oauth, bearer-scopes, postgres, concurrency, security] - -requires: [] -provides: - - Explicit health:read and wildcard MCP positive contracts - - Final-route denial contracts for unrelated, expired, and revoked bearers - - Transactional refresh rotation, rollback, and CAS-loss contracts - - Deterministic real-Postgres concurrent refresh replay regression -affects: [01-05-mcp-security-enforcement, SEC-01, SEC-04, SEC-08] - -tech-stack: - added: [] - patterns: - - Spy on MCP server construction and reader invocation at the route boundary - - Hold a PostgreSQL row lock until both refresh contenders reach the database barrier - -key-files: - created: - - tests/integration/mcp-oauth-refresh-race.test.ts - modified: - - src/lib/mcp/__tests__/auth.test.ts - - src/lib/mcp/__tests__/scopes.test.ts - - src/app/mcp/__tests__/route.test.ts - - tests/integration/bearer-scope-enforcement.test.ts - - src/app/api/mcp/oauth/__tests__/token.test.ts - - src/lib/mcp/oauth/__tests__/connections.test.ts - -key-decisions: - - "Accept either 401 or 403 for insufficient MCP read scope while requiring denial before rate limiting, module lookup, server construction, or data access." - - "Use a real PostgreSQL row-lock barrier so both refresh exchanges contend deterministically without mocking or sequentializing the race." - -patterns-established: - - "MCP read authorization tests pair health:read and wildcard positives with unrelated-scope negatives at helper, route, and database-reader boundaries." - - "Refresh race tests resolve every returned successor through normal bearer/token routes and separately verify cross-connection isolation." - -requirements-completed: [SEC-01, SEC-04, SEC-08] - -duration: 14min -completed: 2026-07-29 ---- - -# Phase 01 Plan 01: MCP Authorization and Refresh Replay Red Contracts Summary - -**Least-privilege MCP read and atomic refresh-family security contracts, including a deterministic real-Postgres replay race** - -## Performance - -- **Duration:** 14 min -- **Started:** 2026-07-29T11:31:22Z -- **Completed:** 2026-07-29T11:44:47Z -- **Tasks:** 3 -- **Files modified:** 8 - -## Accomplishments - -- Pinned `health:read` and wildcard access as positive MCP capability and reader paths while proving write-only, ingest, notification, FHIR, unrelated, expired, and revoked credentials cannot pass. -- Required insufficient-scope denial before rate limiting, module resolution, MCP server construction, or the real lab reader. -- Specified one transaction-client boundary for connection rotation and successor insertion, including CAS-loss family revocation and insertion-failure rollback. -- Reproduced the concurrent refresh vulnerability against real PostgreSQL while preserving a passing sequential rotation and an unrelated connection. - -## Task Commits - -1. **Task 1: Pin MCP read authorization at the final route and reader boundary** - `6c0f47aca` (test) -2. **Task 2: Specify sequential rotation and replay-family semantics** - `38501f657` (test) -3. **Task 3: Add the real-Postgres concurrent refresh replay regression** - `390c718b7` (test) - -Supporting Plan 01-01 commits: - -- `51f5f4395` - restored the explicit transaction-client contract after a concurrent HEAD advance -- `0c29e6e6b` - renamed bearer fixture helpers to keep the owned suite lint-clean - -## Files Created/Modified - -- `src/lib/mcp/__tests__/auth.test.ts` - Explicit read capability matrix on resolved MCP contexts. -- `src/lib/mcp/__tests__/scopes.test.ts` - Canonical read-scope predicate positives and negatives. -- `src/app/mcp/__tests__/route.test.ts` - Registration and reader spies at the final `/mcp` boundary. -- `tests/integration/bearer-scope-enforcement.test.ts` - Real bearer, module, route, and lab-reader enforcement. -- `src/app/api/mcp/oauth/__tests__/token.test.ts` - Transactional successor issuance and rollback contract. -- `src/lib/mcp/oauth/__tests__/connections.test.ts` - Transaction-client and CAS-loser family-revocation contract. -- `tests/integration/mcp-oauth-refresh-race.test.ts` - Real-Postgres sequential and concurrent refresh-family regression. - -## Verification - -- MCP scope unit/route target: **expected RED**, 17 failures and 29 passes. Failures are limited to implicit read capability and insufficient-scope requests reaching HTTP 200. -- Bearer integration target: **expected RED**, 5 failures and 17 passes. Failures are limited to same-user unrelated scopes reaching the real MCP lab reader; `health:read`, wildcard, expired/revoked denial, REST ownership, and existing bearer positives pass. -- OAuth token/connection target: **expected RED**, 4 failures and 19 passes. Failures are limited to ignored transaction clients, non-transactional successor issuance, missing rollback, and missing CAS-loss family revocation. -- PostgreSQL refresh-race target: **expected RED**, 1 failed and 1 passed test. The race reports four expected soft failures: live access successor, usable refresh successor, null `revokedAt`, and one live linked access row. Sequential rotation and unrelated-connection isolation pass. -- ESLint passed for all seven owned test files. -- Release branch, binary scanner patch digest, and all three immutable working-file hashes matched `01-EXECUTION-BASELINE.md` before and after execution. - -## Decisions Made - -- Kept the insufficient-scope wire status flexible between 401 and 403 because both are denial semantics allowed by the plan; the strict invariant is that no downstream capability or reader is reached. -- Used PostgreSQL `pg_stat_activity` only to observe two lock waiters; the test does not alter production synchronization and releases contenders via a row lock held by a separate real transaction. - -## Deviations from Plan - -None - plan executed as test-only RED contracts without production changes. - -## Issues Encountered - -- The plan's `pnpm test -- ` form passes `--` through to Vitest 4 and can select the whole unit suite. Focused evidence was therefore collected with the equivalent direct `pnpm exec vitest run ` commands. -- A concurrent Plan 01-02 documentation commit advanced `HEAD` between staging and an amend attempt. Shared history was not rewritten; `51f5f4395` restored the exact Plan 01-01 file content without changing the other agent's summary. - -## User Setup Required - -None - the existing Testcontainers PostgreSQL harness provides the integration environment. - -## Next Phase Readiness - -- Plan 01-05 can implement explicit MCP read scope enforcement against the helper, route, and real-reader failures. -- Plan 01-05 can move row locking, JTI validation, family/access revocation, and successor insertion into one transaction until the unit and real-Postgres race contracts turn green. - -## Self-Check: PASSED - -- All seven owned test files and this summary exist. -- All five Plan 01-01 commits are present in repository history. -- Scanner baseline hashes remain unchanged. - ---- -*Phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability* -*Completed: 2026-07-29* diff --git a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-02-SUMMARY.md b/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-02-SUMMARY.md deleted file mode 100644 index 05ef423c1..000000000 --- a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-02-SUMMARY.md +++ /dev/null @@ -1,130 +0,0 @@ ---- -phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability -plan: "02" -subsystem: testing -tags: [pwa, cache-storage, ssrf, ipv6, undici, vitest] - -requires: [] -provides: - - RED CacheStorage contracts preventing authenticated root HTML from entering the PWA cache - - RED session-end cleanup contracts covering current and legacy static/page/data caches - - RED literal and DNS-pinned verdict matrices for standard IPv4-embedded IPv6 forms -affects: [SEC-02, SEC-03, SEC-08, service-worker, safe-fetch] - -tech-stack: - added: [] - patterns: - - VM service-worker install fixtures with a signed-in health-data sentinel - - Table-driven transition-prefix cases shared conceptually across literal and connect-time boundaries - - Real Undici dispatcher failures distinguished by nested ENOTFOUND codes - -key-files: - created: [] - modified: - - src/__tests__/service-worker.test.ts - - src/__tests__/sw-security-hardening.test.ts - - src/lib/pwa/__tests__/query-persister.test.ts - - src/lib/validations/__tests__/notifications.test.ts - - src/lib/__tests__/safe-fetch-dispatcher.test.ts - - src/lib/__tests__/safe-fetch-pinned.test.ts - -key-decisions: - - "Exercise authenticated install at the CacheStorage boundary with a same-origin credential request and a unique health-data sentinel." - - "Cover only standard mapped, compatible, 6to4, NAT64-WKP, and RFC8215 local-use encodings; do not infer arbitrary operator NAT64 prefixes." - - "Treat ENOTFOUND from the pinned lookup as the pre-connect refusal signal and retain a real dual-stack/redirect transport control." - -patterns-established: - - "Security RED tests pair attacker-negative cases with explicit legitimate-positive controls." - - "Dispatcher regressions mock DNS answers, not HTTP response bodies." - -requirements-completed: [SEC-02, SEC-03, SEC-08] - -duration: 10min -completed: 2026-07-29 ---- - -# Phase 1 Plan 02: PWA Cache and Embedded-IPv4 SSRF RED Contracts Summary - -**Final-boundary RED contracts now expose authenticated PWA cache leakage and standard IPv4-embedded IPv6 SSRF bypasses while preserving immutable assets, public address families, dual-stack fallback, and redirect rechecks.** - -## Performance - -- **Duration:** 10 min -- **Started:** 2026-07-29T11:31:40Z -- **Completed:** 2026-07-29T11:41:40Z -- **Tasks:** 3 -- **Files modified:** 6 - -## Accomplishments - -- Added a service-worker VM install fixture whose signed-in `/` response contains a unique health sentinel; current code demonstrably stores and serves it offline. -- Added session-end cleanup coverage for current and legacy `healthlog-static-*`, `healthlog-pages-*`, and `healthlog-data-*` caches while preserving unrelated CacheStorage entries. -- Added matching literal URL/raw-IP and DNS-pinned matrices for mapped, compatible, 6to4, NAT64-WKP, and RFC8215 local-use forms, plus real transport controls for public dual-stack fallback and redirect destination rechecks. - -## Task Commits - -Each task was committed atomically: - -1. **Task 1: Pin authenticated install, public offline shell, and legacy cache cleanup** - `3a813b51b` (test) -2. **Task 2: Add the canonical IPv6 transition-form classifier matrix** - `fb533147d` (test) -3. **Task 3: Pin the same verdict at the DNS-pinned dispatcher** - `e25080b21` (test) - -## Files Created/Modified - -- `src/__tests__/service-worker.test.ts` - Signed-in install fixture, cache-content sentinel assertion, and offline-shell boundary. -- `src/__tests__/sw-security-hardening.test.ts` - Source-level invariant that the immutable precache list excludes `/`. -- `src/lib/pwa/__tests__/query-persister.test.ts` - Current/legacy HealthLog cache-family cleanup and unrelated-cache preservation. -- `src/lib/validations/__tests__/notifications.test.ts` - Canonical transition-form literal URL/raw-IP matrix with public and syntax controls. -- `src/lib/__tests__/safe-fetch-dispatcher.test.ts` - Resolver-answer refusal matrix requiring pre-connect `ENOTFOUND`. -- `src/lib/__tests__/safe-fetch-pinned.test.ts` - Real dual-stack fallback and redirect-destination DNS recheck controls. - -## Decisions Made - -- Kept all work test-only; no production, dependency, lockfile, workspace, schema, or migration changes were made. -- Used the browser-equivalent same-origin credential mode in the service-worker `Cache.addAll` fixture so the root response models an install occurring while signed in. -- Used RFC-defined transition prefixes only, including the RFC 6052 bit placement for the RFC8215 `/48` local-use prefix. - -## Verification - -Focused six-file gate: - -- **Result:** expected RED, exit 1. -- **Tests:** 119 total; 21 intended failures and 98 preserved passes. -- **PWA RED reason:** `/` remains in `PRECACHE_URLS`, so signed-in root HTML enters `healthlog-static-*`, is returned offline instead of the inline 503 shell, and session cleanup omits static caches. -- **Classifier RED reason:** compatible, 6to4, NAT64-WKP, and RFC8215 private embeddings are currently accepted at both URL and raw-IP boundaries; malformed IPv6-shaped raw inputs are also accepted. -- **Dispatcher RED reason:** the same eight embedded-private DNS answers reach socket handling instead of being refused by the pinned lookup with `ENOTFOUND`. - -Positive-only gate: - -- **Result:** 11 passed, 74 skipped. -- Public immutable-asset support, the data-free fallback source contract, public IPv4/native IPv6/transition forms, real dual-stack fallback, and redirect DNS rechecking remain pinned. - -Formatting: - -- Prettier check passed for all six owned test files. - -## Deviations from Plan - -None - plan executed exactly as written. - -## Issues Encountered - -The plan's `pnpm test -- ` spelling caused the repository test script to begin unrelated suites as well as the requested files. Verification was therefore isolated with `pnpm exec vitest run `; the exact owned-file failures and positive controls are reported above. - -## User Setup Required - -None - no external service configuration required. - -## Next Phase Readiness - -- SEC-02 implementation can remove `/` from install precaching and broaden session-end cleanup across every HealthLog cache version. -- SEC-03 implementation can centralize validated IPv6 parsing and standard embedded-IPv4 extraction in `isPublicIp`; the pinned dispatcher will inherit the corrected verdict. -- These suites are intentionally RED until the corresponding production-fix plans run. - -## Self-Check: PASSED - -All six owned test files and this summary exist. Task commits `3a813b51b`, `fb533147d`, and `e25080b21` are present in repository history. - ---- -*Phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability* -*Completed: 2026-07-29* diff --git a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-03-SUMMARY.md b/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-03-SUMMARY.md deleted file mode 100644 index 0f76be473..000000000 --- a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-03-SUMMARY.md +++ /dev/null @@ -1,122 +0,0 @@ ---- -phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability -plan: "03" -subsystem: auth-testing -tags: [vitest, testcontainers, postgres, webauthn, credential-revocation] - -requires: - - phase: 01-security-research - provides: Cookie-only enrollment and atomic recovery threat model -provides: - - RED final-boundary contracts for fresh existing-factor passkey enrollment - - RED user/purpose/session/expiry/single-use challenge-binding matrix - - RED real-PostgreSQL password-reset revocation and rollback contract - - Green Admin API reset parity coverage for the complete credential family -affects: [01-08, 01-09, passkey-enrollment, password-recovery] - -tech-stack: - added: [] - patterns: - - Real PostgreSQL persistence assertions around mocked WebAuthn cryptography - - Test-only PostgreSQL trigger injection for transaction rollback proof - -key-files: - created: - - src/components/settings/security-section/__tests__/passkey-enrollment-reauth.test.tsx - - tests/integration/reset-password-revocation.test.ts - modified: - - tests/integration/passkey-register.test.ts - - src/app/api/auth/passkeys/__tests__/route.test.ts - - tests/integration/admin-reset-password.test.ts - -key-decisions: - - "The cookie reauthentication proof is presented before register-options; an ambient cookie or Bearer elevation is never enrollment authority." - - "Rollback is forced with a test-only PostgreSQL DELETE trigger so atomicity is proven without a schema migration or production test hook." - -patterns-established: - - "Enrollment RED matrix: positive existing factors and attacker-negative binding cases reach the final route and database boundaries." - - "Recovery RED matrix: execute the production CLI as a child process and inspect every credential-family table." - -requirements-completed: [SEC-05, SEC-07, SEC-08] - -duration: 11min -completed: 2026-07-29 ---- - -# Phase 01 Plan 03: Credential Enrollment and Recovery RED Contracts Summary - -**Cookie-only passkey challenge-binding and atomic PostgreSQL reset/revocation behavior are specified as durable RED contracts before production remediation.** - -## Performance - -- **Duration:** 11 min -- **Started:** 2026-07-29T11:31:33Z -- **Completed:** 2026-07-29T11:42:31Z -- **Tasks:** 3 -- **Files modified:** 5 - -## Accomplishments - -- Added real-PostgreSQL passkey enrollment coverage for password, primary passkey, TOTP, and MFA WebAuthn proof, plus ambient cookie, Bearer, stale, foreign-user/session, wrong-purpose, expired, and replay attacks. -- Pinned the Settings flow to finish reauthentication before requesting registration options, preserving one cookie-bound options → WebAuthn → verify ceremony. -- Added a production-CLI subprocess suite proving complete credential-family revocation, unrelated-admin isolation, safe input failures, and rollback through an injected database failure. -- Extended Admin API parity coverage to trusted devices and step-up elevations while confirming the administrator's own session and API token survive. - -## Task Commits - -Each task was committed atomically: - -1. **Task 1: Add two-leg cookie-only, existing-factor passkey regressions** - `9f6cd86c0` (test) -2. **Task 2: Pin the passkey enrollment reauthentication UX** - `3f4afb26a` (test) -3. **Task 3: Add atomic password-reset revocation and rollback coverage** - `abea48026` (test) - -## Files Created/Modified - -- `tests/integration/passkey-register.test.ts` - Final route/database enrollment proof and challenge-binding matrix. -- `src/app/api/auth/passkeys/__tests__/route.test.ts` - Static route guard contracts requiring cookie-only handling on both registration legs. -- `src/components/settings/security-section/__tests__/passkey-enrollment-reauth.test.tsx` - Factor availability and proof-before-challenge UI ordering contract. -- `tests/integration/reset-password-revocation.test.ts` - Real CLI/PostgreSQL reset, revocation, isolation, validation, and rollback coverage. -- `tests/integration/admin-reset-password.test.ts` - Complete credential-family parity assertions for the existing Admin API path. - -## Verification - -The Wave 0 plan intentionally requires failing contracts against current production code. - -- Passkey PostgreSQL suite: **12 failed, 2 passed**. Expected RED causes are generic `registration` challenge purpose, ambient/wildcard authorization, and missing stale/session/user/purpose/expiry/replay checks. -- Passkey route unit suite: **2 failed, 3 passed**. Expected RED cause is both registration routes still using generic `requireAuth` without same-session proof handling. -- Passkey UI suite: **2 failed, 1 passed**. Expected RED cause is the missing factor chooser and missing proof-before-options step; the existing options → registration → verify ordering passes. -- Reset/Admin PostgreSQL suites: **2 failed, 6 passed**. The Admin parity suite is green; CLI RED causes are surviving target credentials and no transaction path reaching the injected rollback failure. -- Focused ESLint, Prettier, and `git diff --check` passed for all owned test files. - -## Decisions Made - -- Used the existing step-up proof vocabulary (`password`, `passkey`, `totp`, `webauthn`) for the enrollment contract, but explicitly prohibited the Bearer elevation transport. -- Mocked only WebAuthn cryptography; route policy, AuthChallenge persistence, Passkey persistence, CLI execution, and credential revocation use actual application/database boundaries. -- Forced rollback through PostgreSQL itself so the future CLI must encounter a real statement failure after opening its transaction. - -## Deviations from Plan - -None - plan executed exactly as written. - -## Issues Encountered - -- In this shell, forwarding paths through `pnpm test:integration -- ...` caused Vitest to select the full integration suite. Focused verification used the equivalent direct command, `pnpm exec vitest run --config vitest.integration.config.mts `, while retaining the plan's expected non-zero RED assertion. - -## User Setup Required - -None - no external service configuration required. - -## Next Phase Readiness - -- Plan 01-08 can implement the cookie proof, bound challenge redemption, UI reauthentication, and transactional CLI directly against these RED contracts. -- The package, lock, workspace, schema, migrations, and production implementation files remain untouched. - -## Self-Check: PASSED - -- All five owned test files and this summary exist. -- Task commits `9f6cd86c0`, `3f4afb26a`, and `abea48026` are present. -- Summary formatting and diff whitespace checks pass. - ---- -*Phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability* -*Completed: 2026-07-29* diff --git a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-04-SUMMARY.md b/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-04-SUMMARY.md deleted file mode 100644 index c2a4a011a..000000000 --- a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-04-SUMMARY.md +++ /dev/null @@ -1,125 +0,0 @@ ---- -phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability -plan: "04" -subsystem: testing -tags: [nightscout, ssrf, exact-origin, operator-policy, vitest] - -requires: [] -provides: - - Strict server-only NIGHTSCOUT_PRIVATE_ORIGINS parser and verdict contracts - - Final-boundary tests denying user-controlled private-host authority - - Redacted operator-action UI contract for legacy private connections -affects: [01-07-nightscout-security-implementation, 01-09-security-gate] - -tech-stack: - added: [] - patterns: - - Exact canonical scheme/host/port membership for private egress - - Stable private_origin_not_approved reason across client, sync, route, and UI - -key-files: - created: - - src/lib/validations/__tests__/nightscout.test.ts - modified: - - src/lib/nightscout/__tests__/client.test.ts - - src/lib/nightscout/__tests__/sync.test.ts - - src/app/api/nightscout/connect/__tests__/route.test.ts - - src/app/api/nightscout/sync/__tests__/route.test.ts - - src/components/settings/integrations/__tests__/nightscout-card.test.tsx - -key-decisions: - - "Private Nightscout authority is an exact canonical operator-owned origin, never the stored user boolean." - - "Policy denials use private_origin_not_approved and never expose the raw origin, token, resolver error, or allowlist." - - "The Wave 0 plan remains test-only; production enforcement belongs to Plan 01-07." - -patterns-established: - - "Two-sided SSRF contracts: public and exact-approved private positives accompany every attacker-negative boundary." - - "Denied targets stop before the network call; connect-time drift is translated to one redacted reason." - -requirements-completed: [SEC-06, SEC-08] - -duration: 8 min -completed: 2026-07-29 ---- - -# Phase 1 Plan 04: Nightscout Operator-Origin Red Contract Summary - -**A 100-test Nightscout security contract preserves public and exact operator-approved private access while making the user-controlled SSRF bypass and unsafe legacy UI explicitly RED.** - -## Performance - -- **Duration:** 8 min -- **Started:** 2026-07-29T11:31:43Z -- **Completed:** 2026-07-29T11:40:22Z -- **Tasks:** 3 -- **Files modified:** 6 - -## Accomplishments - -- Declared strict comma-separated `NIGHTSCOUT_PRIVATE_ORIGINS` parsing, canonicalization, and malformed-entry rejection. -- Pinned connect, client, sync, and manual-sync behavior for public positives, exact private positives, user-boolean attacks, port mismatch, DNS/connect drift, and legacy-row rechecks. -- Specified an accessible breaking-change state that removes the private-host toggle, redacts unsafe details, requests operator action, and preserves healthy connected actions. - -## Task Commits - -Each task was committed atomically: - -1. **Task 1: Specify exact operator-origin configuration** - `668f85a58` (test) -2. **Task 2: Pin connect-time and sync-time egress policy** - `14f451093` (test) -3. **Task 3: Pin the user-facing breaking-change state** - `5569b8259` (test) - -## Files Created/Modified - -- `src/lib/validations/__tests__/nightscout.test.ts` - Exact origin parser and public/private policy verdict matrix. -- `src/lib/nightscout/__tests__/client.test.ts` - Final fetch denial, exact-private positive, port mismatch, and redacted DNS/connect-drift contracts. -- `src/lib/nightscout/__tests__/sync.test.ts` - Legacy boolean removal and stable persistent policy-failure recording. -- `src/app/api/nightscout/connect/__tests__/route.test.ts` - Persistence-boundary user attack denial and exact operator-private positive. -- `src/app/api/nightscout/sync/__tests__/route.test.ts` - Stable redacted operator-action response for legacy rows. -- `src/components/settings/integrations/__tests__/nightscout-card.test.tsx` - Toggle removal, safe operator guidance, and healthy public/private actions. - -## Verification - -- Combined six-file Vitest run: **100 discovered, 80 passed, 20 expected RED failures**. -- Validation suite: RED because `parseNightscoutPrivateOrigins` and `evaluateNightscoutOrigin` do not exist yet. -- Client/sync/routes: RED because the stored boolean still grants private access, configured exact origins are not authoritative, port mismatches reach the probe, and policy failures are not yet stable/redacted end to end. -- Settings card: RED because the user toggle still renders and legacy policy failures still expose raw details and unsafe actions. -- Focused Prettier check: passed. -- Focused ESLint over all six owned test files: passed. - -## Decisions Made - -- The parser throws on any malformed operator trust entry rather than silently broadening or partially accepting the allowlist. -- Default ports are canonicalized through URL-origin semantics; non-default ports remain part of exact membership. -- Compatibility fields may remain readable, but tests forbid forwarding `allowPrivateHost` as egress authority. - -## Deviations from Plan - -None - plan executed exactly as written. - -## Issues Encountered - -- Parallel Vitest processes from other disjoint Wave 0 executors contended for workers. Focused verification was rerun serially with `--no-file-parallelism`; contract results were stable. -- The pre-existing package, lockfile, and workspace scanner diff was preserved and not staged. - -## Deferred Issues - -- The accepted Low Nightscout token-warning log finding remains unresolved by design. No production logging code or claim of remediation was added. - -## User Setup Required - -None - this Wave 0 plan adds tests only. Operator configuration is implemented and documented by later plans. - -## Next Phase Readiness - -- Plan 01-07 can implement the exact-origin policy against explicit positive and attacker-negative contracts. -- The focused security gate can use the same six suites after Plan 01-07 turns all 20 policy assertions green. - -## Self-Check: PASSED - -- All six owned test files and this summary exist. -- Task commits `668f85a58`, `14f451093`, and `5569b8259` are present in repository history. - ---- - -_Phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability_ -_Completed: 2026-07-29_ diff --git a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-05-SUMMARY.md b/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-05-SUMMARY.md deleted file mode 100644 index f374cd3d6..000000000 --- a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-05-SUMMARY.md +++ /dev/null @@ -1,127 +0,0 @@ ---- -phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability -plan: "05" -subsystem: auth -tags: [mcp, oauth, bearer-scopes, postgres, prisma, concurrency] - -requires: - - phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability - provides: Plan 01-01 MCP authorization and refresh-race RED contracts -provides: - - Explicit health:read or wildcard authorization before MCP registration - - PostgreSQL row-locked refresh-family rotation - - Transactional JTI advancement, access revocation, and successor insertion - - Post-commit refresh artifact signing -affects: [SEC-01, SEC-04, SEC-08, MCP OAuth] - -tech-stack: - added: [] - patterns: - - Canonical MCP read-scope predicate enforced before downstream route gates - - Prisma interactive transaction with PostgreSQL SELECT FOR UPDATE - - Transaction-client-capable access-token issuance - -key-files: - created: [] - modified: - - src/lib/mcp/auth.ts - - src/lib/mcp/scopes.ts - - src/app/mcp/route.ts - - src/lib/mcp/oauth/connections.ts - - src/app/api/mcp/oauth/token/route.ts - - src/lib/auth/issue-token.ts - -key-decisions: - - "Return an incremental-consent 401 for a valid bearer lacking health:read, before rate limiting, module lookup, or server construction." - - "Serialize refresh decisions with SELECT FOR UPDATE and commit replay-family revocation in the same transaction that handles successful successor insertion." - - "Sign stateless refresh artifacts only after the database transaction commits." - -patterns-established: - - "Write and unrelated bearer scopes never imply MCP read authority." - - "Database helpers accept the active Prisma transaction client instead of opening nested transactions." - -requirements-completed: [SEC-01, SEC-04, SEC-08] - -duration: 5min -completed: 2026-07-29 ---- - -# Phase 01 Plan 05: MCP Authorization and Refresh Replay Enforcement Summary - -**Explicit MCP health-read authorization and row-locked, rollback-safe OAuth refresh-family rotation** - -## Performance - -- **Duration:** 5 min -- **Started:** 2026-07-29T11:46:55Z -- **Completed:** 2026-07-29T11:51:26Z -- **Tasks:** 3 -- **Files modified:** 7 - -## Accomplishments - -- Restricted MCP health capabilities to `health:read` and wildcard tokens, rejecting write-only and unrelated valid bearers before any downstream route work. -- Serialized each refresh family with a PostgreSQL connection-row lock and revoked the family on stale JTI or guarded-update loss. -- Moved JTI advancement, linked-access revocation, and successor access insertion into one Prisma interactive transaction. -- Deferred successor refresh signing until after commit, so rolled-back credentials can never be returned. -- Turned the real-Postgres concurrency regression green while preserving sequential rotation and unrelated-connection usability. - -## Task Commits - -1. **Task 1: Enforce explicit MCP health-read scope at the final boundary** - `20a6731c8` (fix) -2. **Task 2: Make refresh rotation and successor issuance one transaction** - `db08f6c17` (fix) -3. **Task 3: Prove real-Postgres family revocation under concurrency** - verification-only; covered by `db08f6c17` - -## Files Created/Modified - -- `src/lib/mcp/scopes.ts` - Canonical explicit read-scope predicate. -- `src/lib/mcp/auth.ts` - Permission-derived `canRead` context. -- `src/app/mcp/route.ts` - Final pre-registration insufficient-scope denial. -- `src/lib/mcp/oauth/connections.ts` - Transaction-client rotation, row lock, and complete CAS/replay revocation. -- `src/app/api/mcp/oauth/token/route.ts` - One refresh transaction and post-commit artifact finalization. -- `src/lib/auth/issue-token.ts` - Optional transaction-client token insertion seam. - -## Verification - -- MCP auth/scope/route unit bundle: **46/46 passed**. -- Real bearer-to-reader integration: **22/22 passed**. -- OAuth token/connection unit bundle: **23/23 passed**. -- Real PostgreSQL refresh-race plus bearer integration bundle: **24/24 passed**. -- Broader owned-seam unit regression, including native refresh and manual MCP token routes: **90/90 passed**. -- ESLint passed across all six production files. -- Full typecheck was attempted and reached one unrelated concurrent Wave-1 passkey test error at `tests/integration/passkey-register.test.ts:86`; it reported no Plan 01-05 owned-file errors. -- Release branch, scanner binary patch digest, and all immutable package/workspace hashes matched the execution baseline after implementation. - -## Decisions Made - -- Kept bearer validation centralized and used `canRead` as defense in depth at the route boundary, allowing the response to advertise `health:read` for incremental consent. -- Retained the guarded JTI update after row locking. The lock provides serialization; the guard remains a fail-closed invariant and any zero-count result revokes the family. -- No deadlock or serialization retry was added because the deterministic real-Postgres race passed repeatedly with row locking alone. - -## Deviations from Plan - -None - the plan was implemented without schema, migration, package, or test changes. - -## Issues Encountered - -- Full-tree typecheck was temporarily blocked by an unrelated concurrently edited passkey integration fixture. Focused tests and ESLint for every owned file passed. - -## User Setup Required - -None. - -## Next Phase Readiness - -- SEC-01 and SEC-04 are closed against helper, final-route, rollback, and real-concurrency contracts. -- The merged security gate can include the Plan 01-01 suites without expected failures. - -## Self-Check: PASSED - -- All six owned production files and this summary exist. -- Both Plan 01-05 implementation commits exist. -- All focused MCP unit and PostgreSQL integration suites are green. -- Scanner baseline files remain byte-for-byte unchanged. - ---- -*Phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability* -*Completed: 2026-07-29* diff --git a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-06-SUMMARY.md b/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-06-SUMMARY.md deleted file mode 100644 index a1549f249..000000000 --- a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-06-SUMMARY.md +++ /dev/null @@ -1,110 +0,0 @@ ---- -phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability -plan: "06" -subsystem: pwa -tags: [service-worker, cache-storage, indexeddb, privacy, offline] - -requires: - - phase: 01-02 - provides: RED authenticated-install and session-cache isolation contracts -provides: - - Public-asset-only service-worker install with no authenticated root precache - - Same-version root eviction and data-free offline fallback - - Current and legacy HealthLog static/page/data cache cleanup at session end - - App-scoped activation cleanup that preserves unrelated CacheStorage namespaces -affects: [SEC-02, SEC-08, pwa, logout, offline] - -tech-stack: - added: [] - patterns: - - Explicit owned-cache regex shared by service-worker and session cleanup boundaries - - Same-version legacy-entry removal during service-worker install - -key-files: - created: [] - modified: - - public/sw.js - - src/lib/pwa/query-persister.ts - -key-decisions: - - "Precache only immutable public logos/favicon and explicitly delete a root entry left in the same release-scoped cache." - - "Scope activation and logout deletion to healthlog-{static,pages,data}-* instead of deleting unrelated CacheStorage namespaces." - -patterns-established: - - "PWA cache ownership uses the same anchored HealthLog family matcher at worker activation and client session end." - - "Session-end cleanup clears IndexedDB query persistence before CacheStorage families on a best-effort basis." - -requirements-completed: [SEC-02, SEC-08] - -duration: 3min -completed: 2026-07-29 ---- - -# Phase 1 Plan 06: PWA Account-Data Cache Isolation Summary - -**Public-only PWA installation, data-free root fallback, and complete app-scoped logout eviction prevent authenticated Dashboard HTML from surviving a session.** - -## Performance - -- **Duration:** 3 min -- **Started:** 2026-07-29T11:44:13Z -- **Completed:** 2026-07-29T11:47:13Z -- **Tasks:** 2 -- **Files modified:** 2 - -## Accomplishments - -- Removed `/` from install precaching and evicted same-version root entries left by older worker source, preventing signed-in Dashboard HTML from entering the static cache. -- Preserved the inline locale-neutral, data-free offline document and immutable public logo/favicon support. -- Expanded logout cleanup across every current and legacy HealthLog static/page/data cache while keeping unrelated cache namespaces intact. -- Narrowed service-worker activation cleanup to owned HealthLog cache families rather than deleting every non-current cache on the origin. - -## Task Commits - -Each task was committed atomically: - -1. **Task 1: Replace authenticated root precache with a data-free offline shell** - `885156965` (fix) -2. **Task 2: Purge all current and legacy HealthLog caches on logout** - `338dbe6c1` (fix) - -## Files Created/Modified - -- `public/sw.js` - Public-only precache list, same-version root eviction, and app-scoped stale-cache activation cleanup. -- `src/lib/pwa/query-persister.ts` - Session-end deletion of all owned static/page/data cache versions alongside the IndexedDB query snapshot. - -## Decisions Made - -- Kept normal online navigation and the existing `Cache-Control: no-store` privacy gate intact; the reported leak was the credential-bearing install fetch of `/`. -- Used the anchored `^healthlog-(?:static|pages|data)-` ownership contract in both runtimes, covering documented historical names without an indiscriminate browser-wide purge. -- Preserved all existing public/offline behavior and added no dependency, schema, migration, package, lockfile, or workspace changes. - -## Verification - -- Plan 01-06 focused gate: **42/42 passed** across service-worker VM, source hardening, and query-persister tests. -- Positive controls: **52/52 passed** when including logout/session-expiry hook integration suites. -- Prettier passed for both owned production files. -- The former Plan 01-02 RED failures are green: no signed-in root request/storage/replay, static caches are included in current/legacy logout cleanup, and unrelated sentinel caches survive. - -## Deviations from Plan - -None - plan executed exactly as written. - -## Issues Encountered - -None. - -## User Setup Required - -None - no external service configuration required. - -## Next Phase Readiness - -- SEC-02 is closed at install, offline navigation, activation, logout, and persisted-query boundaries. -- The merged security gate can use the Plan 01-02 tests as final-boundary positive/negative evidence. - -## Self-Check: PASSED - -Both owned production files and this summary exist. Task commits `885156965` and `338dbe6c1` are present in repository history. - ---- -*Phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability* -*Completed: 2026-07-29* diff --git a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-07-SUMMARY.md b/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-07-SUMMARY.md deleted file mode 100644 index aa3552abf..000000000 --- a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-07-SUMMARY.md +++ /dev/null @@ -1,139 +0,0 @@ ---- -phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability -plan: "07" -subsystem: security -tags: [ssrf, ipv6, dns-pinning, nightscout, nextjs] - -requires: - - phase: 01-02 - provides: SSRF classifier and connect-time pinned dispatcher regression matrix - - phase: 01-04 - provides: Nightscout route, client, sync, persistence, and UI security contracts -provides: - - Byte-normalized IPv6 transition classification for embedded special-use IPv4 - - Exact server-owned Nightscout private-origin authorization - - Stable redacted Nightscout policy failures from client through UI -affects: [outbound-http, integrations, notifications, nightscout] - -tech-stack: - added: [] - patterns: - - Byte-level IP classification shared by literal validation and DNS lookup policy - - Exact canonical origin grants sourced only from server environment - -key-files: - created: [] - modified: - - src/lib/validations/notifications.ts - - src/lib/safe-fetch-dispatcher.ts - - src/lib/validations/nightscout.ts - - src/app/api/nightscout/connect/route.ts - - src/app/api/nightscout/sync/route.ts - - src/lib/nightscout/client.ts - - src/lib/nightscout/credentials.ts - - src/lib/nightscout/sync.ts - - src/components/settings/integrations/nightscout-card.tsx - -key-decisions: - - "Treat mapped, compatible, 6to4, NAT64 WKP, and RFC8215 local-use IPv6 embeddings according to their extracted IPv4 bytes." - - "Authorize private Nightscout egress only through exact canonical NIGHTSCOUT_PRIVATE_ORIGINS membership; retain the stored boolean as non-authoritative compatibility metadata." - - "Represent denied private origins with the stable private_origin_not_approved code and redact all URL/token detail at API, persistence, and UI boundaries." - -patterns-established: - - "Operator-owned egress policy: request fields and stored user preferences never grant private-network authority." - - "Policy failure propagation: stable reason codes cross internal boundaries while user-facing copy remains redacted." - -requirements-completed: [SEC-03, SEC-06, SEC-08] - -duration: 14min -completed: 2026-07-29 ---- - -# Phase 1 Plan 07: IPv6 SSRF and Nightscout Origin Policy Summary - -**Byte-normalized IPv6 SSRF defenses and exact operator-owned Nightscout origins enforced across connect, sync, persistence, and UI** - -## Performance - -- **Duration:** 14 min -- **Started:** 2026-07-29T11:44:25Z -- **Completed:** 2026-07-29T11:58:32Z -- **Tasks:** 3 -- **Files modified:** 9 - -## Accomplishments - -- Classified private and special-use IPv4 embedded in mapped, compatible, 6to4, NAT64 WKP, and RFC8215 local-use IPv6 forms for both literal validation and resolved DNS answers. -- Replaced Nightscout's user-controlled private-host opt-in with exact normalized origins from the server-owned `NIGHTSCOUT_PRIVATE_ORIGINS` setting, re-evaluated on every fetch. -- Propagated a stable, non-secret `private_origin_not_approved` failure through connect, sync, persistence, and a redacted operator-action UI state while preserving public and approved-private success paths. -- Passed the combined Plan 02 and Plan 04 security gate: 9 test files and 177 tests, plus the repository TypeScript check. - -## Task Commits - -Each task was committed atomically: - -1. **Task 1: Reject private embedded IPv4 across IPv6 transition formats** - `a17786960` (fix) -2. **Task 2: Enforce exact operator-owned Nightscout origins end to end** - `f95da50ba` (fix) -3. **Task 3: Present the server-authoritative Nightscout policy safely** - `27a3e0843` (fix) - -## Files Created/Modified - -- `src/lib/validations/notifications.ts` - Normalizes IPv6 bytes and rejects special-use embedded IPv4 destinations. -- `src/lib/safe-fetch-dispatcher.ts` - Applies public or exact-operator lookup policy at socket resolution and keeps dispatcher caches isolated. -- `src/lib/validations/nightscout.ts` - Parses and evaluates exact canonical server-owned private origins. -- `src/app/api/nightscout/connect/route.ts` - Ignores user authority flags, enforces server policy, and persists only validated connection metadata. -- `src/app/api/nightscout/sync/route.ts` - Maps the stable private-origin policy failure to an actionable 422 response. -- `src/lib/nightscout/client.ts` - Re-evaluates origin policy for every request and prevents denied requests before network access. -- `src/lib/nightscout/credentials.ts` - Marks the stored private-host boolean as compatibility/display metadata. -- `src/lib/nightscout/sync.ts` - Removes stored-boolean authority and records stable redacted persistent policy failures. -- `src/components/settings/integrations/nightscout-card.tsx` - Removes the bypass control and presents a safe operator-action state. - -## Decisions Made - -- Used byte-level IP classification rather than hostname text so literal and DNS-resolved transition forms share one verdict. -- Exact operator membership wins for an approved origin; same-host port/scheme variants and hostname suffixes are explicitly denied before the public fallback. -- Kept the legacy request/schema and stored boolean readable for compatibility, but the route and client ignore it as network authority. -- Kept redirects manual and public resolution pinned; operator-approved private origins are re-authorized by exact origin on every request. - -## Deviations from Plan - -### Auto-fixed Issues - -**1. [Rule 3 - Blocking] Added the final Nightscout sync API policy boundary** - -- **Found during:** Task 2 (Enforce exact operator-owned Nightscout origins end to end) -- **Issue:** The Plan 04 contract requires `private_origin_not_approved` to remain stable through the manual sync API, but `src/app/api/nightscout/sync/route.ts` was not listed in Plan 07's files. -- **Fix:** With explicit orchestrator authorization, added one narrow error arm mapping only that policy reason to HTTP 422 plus the stable error code; all other upstream failures retain their existing 502 response. -- **Files modified:** `src/app/api/nightscout/sync/route.ts` -- **Verification:** The focused sync route suite and combined 177-test security gate pass. -- **Committed in:** `f95da50ba` - ---- - -**Total deviations:** 1 auto-fixed (1 blocking) -**Impact on plan:** The scoped boundary change was necessary to satisfy the existing end-to-end stable-error contract and introduced no unrelated cleanup. - -## Issues Encountered - -- Undici rejects port 9 before DNS lookup, so the Plan 02 transition harness was independently corrected by its owning plan to use a high non-blocked port. Plan 07 then passed the corrected DNS/pinning suite without changing that test. -- A hostname under an approved private DNS name could otherwise reach the generic public-host fallback. Exact-origin evaluation now denies same-host and suffix variants before that fallback. - -## User Setup Required - -Public Nightscout origins require no setup. Operators who intentionally connect to a private Nightscout instance must set `NIGHTSCOUT_PRIVATE_ORIGINS` to a comma-separated list of exact `http(s)://host:port` origins. - -## Next Phase Readiness - -- Generic outbound validation and Nightscout share regression coverage for literal, DNS, redirect, rebinding, exact-origin, persistence, and UI boundaries. -- No known blockers remain for dependent integration-security work. - -## Self-Check: PASSED - -- All nine modified production files exist. -- Task commits `a17786960`, `f95da50ba`, and `27a3e0843` exist on the current branch. -- Combined Plan 02 + Plan 04 security tests pass: 177/177. -- `pnpm typecheck` passes. - ---- -*Phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability* -*Completed: 2026-07-29* diff --git a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-08-SUMMARY.md b/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-08-SUMMARY.md deleted file mode 100644 index cd77a8ecf..000000000 --- a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-08-SUMMARY.md +++ /dev/null @@ -1,141 +0,0 @@ ---- -phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability -plan: "08" -subsystem: auth -tags: [passkeys, webauthn, reauthentication, postgres, credential-revocation] - -requires: - - phase: 01-03 - provides: Passkey enrollment and password-reset security contracts -provides: - - Cookie-only passkey enrollment with fresh existing-factor proof - - HMAC session-bound, purpose-bound, expiring, single-use registration challenges - - Atomic operator password reset with complete credential-family revocation -affects: [passkey-management, account-recovery, session-security] - -tech-stack: - added: [] - patterns: - - Atomic PostgreSQL DELETE RETURNING challenge claims - - Cookie-session-bound WebAuthn registration ceremonies - - SELECT FOR UPDATE credential-family recovery transactions - -key-files: - created: [] - modified: - - src/lib/auth/passkey.ts - - src/app/api/auth/passkey/register-options/route.ts - - src/app/api/auth/passkey/register-verify/route.ts - - src/components/settings/security-section/passkey-list-section.tsx - - scripts/reset-password.mjs - - tests/integration/passkey-register.test.ts - -key-decisions: - - "Bind registration challenge purpose to an HMAC of the current cookie session rather than persist or expose the raw session identifier." - - "Claim a valid registration challenge with guarded DELETE RETURNING before WebAuthn verification and credential insertion so concurrent replay has one winner." - - "Perform password rotation and every credential-family revocation under one locked PostgreSQL transaction." - -patterns-established: - - "Enrollment proof bootstrap: passkey and MFA-WebAuthn assertion options may be issued before registration, but registration options are created only after assertion verification." - - "Recovery atomicity: resolve and lock exactly one user, mutate all credential families, then commit or roll back the complete operation." - -requirements-completed: [SEC-05, SEC-07, SEC-08] - -duration: 12min -completed: 2026-07-29 ---- - -# Phase 01 Plan 08: Passkey Enrollment and Atomic Recovery Summary - -**Fresh-factor-gated passkey enrollment with session-bound one-time challenges, plus transactional password recovery that revokes every alternate access path** - -## Performance - -- **Duration:** 12 min -- **Started:** 2026-07-29T11:44:43Z -- **Completed:** 2026-07-29T11:56:57Z -- **Tasks:** 3 -- **Files modified:** 6 - -## Accomplishments - -- Restricted both passkey registration legs to cookie sessions and required password, TOTP, primary-passkey, or MFA-WebAuthn proof before registration options exist. -- Bound enrollment challenges to user, purpose, cookie session, expiry, and one atomic redemption without a schema change. -- Added an accessible settings reauthentication dialog that completes the selected existing-factor proof before invoking passkey registration. -- Changed the standalone reset CLI to rotate the password and revoke sessions, API tokens, refresh tokens, trusted devices, and step-up elevations in one rollback-safe transaction. - -## Task Commits - -Each task was committed atomically: - -1. **Task 1: Bind passkey enrollment to fresh same-session proof** - `89c83510d` (feat) -2. **Task 2: Require reauthentication in the passkey settings flow** - `7874eea04` (feat) -3. **Task 3: Make password reset atomically revoke every access path** - `9c661dcc5` (fix) - -Additional blocking verification correction: - -- `b7b18a6da` - Align the Plan 01-03 integration fixture with NextRequest's framework-specific init type. - -## Files Created/Modified - -- `src/lib/auth/passkey.ts` - Creates HMAC session-bound registration challenges and atomically claims them. -- `src/app/api/auth/passkey/register-options/route.ts` - Requires a cookie session and verifies an existing account factor before enrollment. -- `src/app/api/auth/passkey/register-verify/route.ts` - Rechecks the live session and consumes the bound challenge before credential persistence. -- `src/components/settings/security-section/passkey-list-section.tsx` - Presents password, TOTP, passkey, and security-key reauthentication before add-passkey. -- `scripts/reset-password.mjs` - Locks the target account and atomically rotates password plus all access credentials. -- `tests/integration/passkey-register.test.ts` - Uses a NextRequest-compatible test helper without changing security assertions. - -## Decisions Made - -- The registration challenge type carries `passkey-registration:v1:`; raw session identifiers never enter challenge metadata. -- Foreign user/session challenges are refused without consumption, while an eligible challenge is claimed with `DELETE ... RETURNING` so replay and races lose. -- Password proof authorizes only the enrollment ceremony and does not upgrade the session's MFA freshness; strong existing factors may refresh that stamp. -- The operator reset retains plain `pg.Client` compatibility with the production standalone image and scopes every statement to the locked target user. - -## Deviations from Plan - -### Auto-fixed Issues - -**1. [Rule 3 - Blocking] Corrected the passkey integration request-helper type** - -- **Found during:** Final repository typecheck -- **Issue:** The Plan 01-03 fixture annotated browser `RequestInit`, whose nullable `signal` is incompatible with Next 16's `NextRequest` constructor type. -- **Fix:** Constructed the request options inline so TypeScript infers the narrower framework-compatible shape; no assertion or runtime behavior changed. -- **Files modified:** `tests/integration/passkey-register.test.ts` -- **Verification:** `pnpm typecheck`, focused ESLint, and the full passkey integration suite pass. -- **Committed in:** `b7b18a6da` - ---- - -**Total deviations:** 1 auto-fixed (1 blocking) -**Impact on plan:** The correction was required for the mandated repository typecheck and did not widen implementation scope or weaken the security contract. - -## Issues Encountered - -- The repository-wide typecheck initially exposed the fixture mismatch above; it was resolved and rerun successfully. - -## User Setup Required - -None - no external service configuration required. - -## Verification - -- `pnpm typecheck` - passed. -- Focused ESLint across all TypeScript/TSX implementation and fixture files - passed. -- `node --check scripts/reset-password.mjs` - passed. -- Focused unit/static suite - 17 tests passed. -- Real-Postgres passkey, operator reset, and admin reset suites - 22 tests passed. - -## Next Phase Readiness - -- SEC-05, SEC-07, and SEC-08 are closed without a schema migration or first-passkey exemption. -- Passkey management and recovery flows are ready for phase-level integration verification. - -## Self-Check: PASSED - -- All six modified implementation/fixture files and this summary exist. -- Task commits `89c83510d`, `7874eea04`, `9c661dcc5`, and verification correction `b7b18a6da` are present in git history. - ---- -*Phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability* -*Completed: 2026-07-29* diff --git a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-09-SUMMARY.md b/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-09-SUMMARY.md deleted file mode 100644 index d39bf426b..000000000 --- a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-09-SUMMARY.md +++ /dev/null @@ -1,165 +0,0 @@ ---- -phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability -plan: "09" -subsystem: security -tags: [vitest, postgresql, mcp, nightscout, passkeys, recovery] - -requires: - - phase: 01-05 - provides: explicit MCP read authorization and refresh-family serialization - - phase: 01-06 - provides: public-only PWA caching and logout cleanup - - phase: 01-07 - provides: embedded-private IP rejection and operator-owned Nightscout origins - - phase: 01-08 - provides: passkey reauthentication and atomic operator reset revocation -provides: - - Green final-boundary security evidence for SEC-01 through SEC-08 - - Operator upgrade guides for MCP scopes, Nightscout egress, and account recovery - - Immutable scanner baseline and post-security scope freeze -affects: [plans-10-24, release-gate, self-hosting, security-operations] - -tech-stack: - added: [] - patterns: - - Direct Vitest file selection without a standalone argument separator - - Exact operator-owned origin allowlists with pinned outbound resolution - -key-files: - created: - - docs/integrations/nightscout.md - - docs/ops/security-recovery.md - - .planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-SECURITY-FOCUSED-GATE.md - modified: - - docs/self-hosting/mcp.md - -key-decisions: - - "Use direct Vitest file arguments as the authoritative focused gate because a standalone -- selects the full configured catalog under Vitest 4.1.5." - - "Preserve the user-owned Codex Security package/workspace diff byte-for-byte and defer accepted Low findings, dependency reconciliation, and the final standard scan." - -patterns-established: - - "Security gates record legitimate-positive and attacker-negative evidence at final route, cache, network, and database boundaries." - - "Operator documentation treats private egress and credential recovery as server-owned security policy, never user-controlled bypasses." - -requirements-completed: [SEC-01, SEC-02, SEC-03, SEC-04, SEC-05, SEC-06, SEC-07, SEC-08] - -duration: 11min -completed: 2026-07-29 ---- - -# Phase 01 Plan 09: Focused Security Gate Summary - -**Zero-skip security boundary evidence across 305 unit/component and 46 real-PostgreSQL tests, with explicit operator contracts and a byte-exact scanner scope freeze** - -## Performance - -- **Duration:** 11 min -- **Started:** 2026-07-29T12:01:29Z -- **Completed:** 2026-07-29T12:12:00Z -- **Tasks:** 3 -- **Files modified:** 5 - -## Accomplishments - -- Closed SEC-01 through SEC-08 with 20/20 unit-component files and 5/5 - PostgreSQL files passing: 351 tests, zero failures, and zero skips. -- Passed repository typecheck and the revised plan's exact 19-path ESLint - allowlist with zero errors; four pre-existing unused-parameter warnings in - the pinned-fetch test fixture were recorded without out-of-scope edits. -- Documented MCP `health:read` isolation, the breaking exact-origin - `NIGHTSCOUT_PRIVATE_ORIGINS` policy with DNS pinning, and rollback-safe - all-access-path operator recovery. -- Recomputed the immutable branch, binary-diff digest, and three scanner - working-file hashes and preserved each value exactly. - -## Task Commits - -Each task was committed atomically: - -1. **Task 1: Run the focused final-boundary security gate** - - `240918358`, corrected by `56c20f26d` (test/docs) -2. **Task 2: Document the changed security contracts for operators** - - `9ea0cb867` (docs) -3. **Task 3: Freeze security scope before product work** - - `fb95e3a13` (docs) - -## Files Created/Modified - -- `01-SECURITY-FOCUSED-GATE.md` - Commands, counts, SEC-01–08 boundary - matrix, corrected-command audit trail, hashes, and scope freeze. -- `docs/self-hosting/mcp.md` - Explicit MCP read-scope and upgrade contract. -- `docs/integrations/nightscout.md` - Exact private-origin policy, pinned - resolution, safe examples, and upgrade checklist. -- `docs/ops/security-recovery.md` - Credential-family revocation and - transaction rollback behavior. -- `01-09-SUMMARY.md` - Plan execution record. - -## Decisions Made - -- The corrected direct Vitest commands in the revised Plan 01-09 are the - authoritative focused gate. The original package-script forwarding failure - remains in the gate report as an audit trail. -- No source correction was made for the unrelated reverse-i18n orphan keys; - locale reconciliation remains owned by Plan 23. -- No security-remediation migration, dependency upgrade, Deep scan, or Low - finding change was permitted at this gate. - -## Deviations from Plan - -### Auto-fixed Issues - -**1. [Rule 3 - Blocking] Corrected focused Vitest command selection** - -- **Found during:** Task 1 -- **Issue:** The original `pnpm test -- ` and - `pnpm test:integration -- ` forms selected the full configured - catalogs under Vitest 4.1.5. The broad unit run produced 18,905 passes, one - unrelated reverse-i18n failure, and 12 unrelated skips. -- **Fix:** The planner revised Plan 01-09 to direct - `pnpm exec vitest run` commands, including the explicit integration config. - The already-run direct commands exactly match the revision. -- **Files modified:** `01-09-PLAN.md` by the orchestrator; no package script or - security source was changed by this executor. -- **Verification:** 305/305 focused unit-component tests and 46/46 focused - PostgreSQL tests passed with zero skips. -- **Committed in:** `56c20f26d` records the corrected final verdict. - ---- - -**Total deviations:** 1 auto-fixed blocking command-selection issue -**Impact on plan:** The correction narrowed execution to the intended security -files without changing product behavior or release scope. - -## Issues Encountered - -- The unintended broad unit run exposed two locale keys without call sites. - This is unrelated to Plans 01-05 through 01-08 and remains assigned to the - planned locale reconciliation. -- Exact ESLint reports four warnings, but zero errors, in the existing - `safe-fetch-pinned` test fixture. - -## User Setup Required - -- Operators with private Nightscout instances must configure exact - `NIGHTSCOUT_PRIVATE_ORIGINS` entries before upgrading. -- Existing MCP connectors must use `health:read`; unrelated scopes no longer - imply MCP read access. - -## Next Phase Readiness - -- The focused security gate is green, so product contract work may begin. -- Plan 21 alone may later add the nullable - `0287_google_health_sync_progress` migration for GH-07. -- Plan 24 must still run the final merged standard working-tree scan and dated - dependency reconciliation before any release authorization. - -## Self-Check: PASSED - -- All five owned documentation, gate, and summary files exist. -- Task commits `240918358`, `56c20f26d`, `9ea0cb867`, and `fb95e3a13` - exist in repository history. -- Documentation and gate content checks pass with no whitespace errors. - ---- -*Phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability* -*Completed: 2026-07-29* diff --git a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-10-SUMMARY.md b/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-10-SUMMARY.md deleted file mode 100644 index 73b0ec3f8..000000000 --- a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-10-SUMMARY.md +++ /dev/null @@ -1,145 +0,0 @@ ---- -phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability -plan: "10" -subsystem: testing -tags: [vitest, playwright, react, responsive-layout, ai-safety] - -requires: - - phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability - provides: v1.34.1 product intent, UI standards, and test infrastructure -provides: - - RED component contracts for Dashboard, documents, Health Score, assessments, records, Settings, and Admin - - RED responsive geometry contracts across desktop, short desktop, tablet, and phone viewports - - Deterministic release evidence capture points for five named UI surfaces -affects: [01-14, 01-15, 01-20, dashboard, insights, records, navigation] - -tech-stack: - added: [] - patterns: - - test-only RED presentation contracts with positive preservation assertions - - route-envelope to hook to card assessment-state coverage - - deterministic Playwright geometry and evidence capture - -key-files: - created: - - src/components/records/__tests__/allergy-manager.test.tsx - - src/components/records/__tests__/family-history-manager.test.tsx - - e2e/v1341-navigation-and-insights-mobile.spec.ts - - e2e/v1341-ui-evidence.spec.ts - modified: - - src/components/daily/__tests__/today-hero.test.tsx - - src/components/documents/__tests__/document-summary-block.test.tsx - - src/components/insights/__tests__/health-score-card.test.tsx - - src/hooks/__tests__/use-insight-status.test.ts - - src/components/insights/__tests__/insight-status-card.test.tsx - - src/app/api/insights/biomarker-assessment/__tests__/route.test.ts - - src/app/insights/__tests__/assessment-spine-order.test.ts - - src/components/records/__tests__/health-profile-facts-manager.test.tsx - - src/components/settings/__tests__/settings-shell.test.tsx - - src/components/admin/__tests__/admin-shell.test.tsx - -key-decisions: - - "Treat a safe terminal assessment fallback as displayable text even when the envelope has hasProvider:false; a null-text envelope remains the provider-setup branch." - - "Expose poll exhaustion explicitly and clear the preparing presentation instead of merely stopping the timer." - - "Measure shell alignment, pinning, scroll ownership, and mobile Insights spacing in the browser rather than relying only on utility-class assertions." - -patterns-established: - - "RED presentation tests pair removed-copy assertions with positive checks for data, provenance, history, and destructive controls that must remain." - - "Evidence screenshots disable animation, wait for fonts and a visible sentinel, use fixed viewports, and attach the exact captured file to Playwright output." - -requirements-completed: [DASH-01, DOC-01, INS-01, INS-02, INS-03, INS-04, INS-05, INS-06, INS-07, INS-08, INS-09, REC-01, REC-02, REC-03, REC-04, REC-05, NAV-01, NAV-02, NAV-03, NAV-04, NAV-05, AI-01, AI-02, AI-03, AI-04, AI-05] - -duration: 12min -completed: 2026-07-29 ---- - -# Phase 1 Plan 10: UI Presentation and Navigation RED Contracts Summary - -**Vitest and Playwright contracts now isolate the v1.34.1 redundant-copy, assessment-state, record-action, shell-alignment, and mobile Insights spacing defects while preserving safety and data-provenance behavior.** - -## Performance - -- **Duration:** 12 min -- **Started:** 2026-07-29T12:11:14Z -- **Completed:** 2026-07-29T12:23:12Z -- **Tasks:** 3 -- **Files modified:** 15 - -## Accomplishments - -- Added Dashboard, document-summary, and Health Score RED contracts that reject duplicate or redundant presentation while retaining loaded narrative, score calculation/version, pillar details, and provenance. -- Added assessment route-envelope, bounded-polling, safe-fallback, readable-prose, and stable-state contracts plus record-manager Save/removal/history and empty/populated CTA grammar. -- Added Settings/Admin grouping and pinning contracts, phone Insights gap/focus/touch geometry, and deterministic screenshots for Dashboard, Insights, Anamnesis, Settings, and Admin. - -## Task Commits - -Each task was committed atomically: - -1. **Task 1: Pin honest Dashboard, document, and Health Score presentation** - `8e944dbcd` (test) -2. **Task 2: Pin assessment presentation/state honesty and record-management grammar** - `18d0facdf` (test) -3. **Task 3: Define grouped, pinned, responsive navigation geometry** - `65ca1aae4` (test) - -## Files Created/Modified - -- `src/components/daily/__tests__/today-hero.test.tsx` - useful loaded narrative and non-invented transient score prose. -- `src/components/documents/__tests__/document-summary-block.test.tsx` - stored summaries without regenerate/replacement controls. -- `src/components/insights/__tests__/health-score-card.test.tsx` - one headline score with calculation and provenance preserved. -- `src/hooks/__tests__/use-insight-status.test.ts` - bounded polling must terminate honestly. -- `src/components/insights/__tests__/insight-status-card.test.tsx` - distinct stable states and visible screened fallback. -- `src/app/api/insights/biomarker-assessment/__tests__/route.test.ts` - success, preparing, screened, and negative-cache envelopes. -- `src/app/insights/__tests__/assessment-spine-order.test.ts` - shared compact rhythm and readable assessment prose. -- `src/components/records/__tests__/health-profile-facts-manager.test.tsx` - one Save grammar with removal/history preserved. -- `src/components/records/__tests__/allergy-manager.test.tsx` - exact empty and populated Allergy actions. -- `src/components/records/__tests__/family-history-manager.test.tsx` - exact empty and populated Family history actions. -- `src/components/settings/__tests__/settings-shell.test.tsx` - Settings groups and heading-aligned sticky sidebar. -- `src/components/admin/__tests__/admin-shell.test.tsx` - Admin operational groups, separators, role gate, and sidebar alignment. -- `e2e/v1341-navigation-and-insights-mobile.spec.ts` - desktop/tablet pinning and phone Insights geometry. -- `e2e/v1341-ui-evidence.spec.ts` - five deterministic named screenshot capture points. - -## Decisions Made - -- The no-provider state is identified by `hasProvider:false` plus no text; safe terminal fallback text must not be discarded solely because its envelope also carries `hasProvider:false`. -- Poll exhaustion is a user-visible terminal condition, so tests require an explicit exhausted signal and a cleared `preparing` presentation. -- The shell contract measures the initial sidebar/heading top, the sidebar after main-content scrolling, document scroll ownership, and width stability. -- Screenshot evidence uses only the seeded E2E account and deterministic route fixtures; no live or personal health data is captured. - -## Deviations from Plan - -None - plan executed exactly as written. - -## Issues Encountered - -- The direct Playwright command did not load `.env`, so global setup initially stopped on a missing `DATABASE_URL`. Re-running through Node with `dotenv/config` loaded the standard project environment and exercised the suite. -- The standalone server emitted an encryption-key readiness warning, but the authenticated E2E seed and test run completed. The expected RED browser results were 14 navigation/spacing failures, one evidence test pass, and one project skip. - -## Verification - -- Combined Vitest contract run: **113 tests, 99 passed, 14 intended RED failures**. -- Playwright contract run on `chromium-desktop` and `chromium-mobile`: **14 intended RED failures, 1 evidence test passed, 1 skipped**. -- Browser measurements reproduced the current defects precisely: Settings/Admin sidebars start **164px** below their headings and the phone Insights row starts **24px** below the main viewport. -- Evidence test generated: - - `test-results/v1341/dashboard-desktop.png` - - `test-results/v1341/insights-mobile.png` - - `test-results/v1341/anamnesis-desktop.png` - - `test-results/v1341/settings-short-desktop.png` - - `test-results/v1341/admin-tablet.png` -- ESLint and Prettier passed for all Task 3 files. - -## User Setup Required - -None - no external service configuration required. - -## Next Phase Readiness - -- Production plans can implement directly against 14 isolated Vitest failures and the measured browser geometry failures. -- Plan 20 owns the shared `InsightStatusCard` and assessment-state seam; the tests intentionally leave production untouched. - -## Self-Check: PASSED - -- All 14 planned test files exist. -- Task commits `8e944dbcd`, `18d0facdf`, and `65ca1aae4` exist. -- All work remains test-only; no production or locale files were modified. - ---- -*Phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability* -*Completed: 2026-07-29* diff --git a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-11-SUMMARY.md b/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-11-SUMMARY.md deleted file mode 100644 index ecfd8a658..000000000 --- a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-11-SUMMARY.md +++ /dev/null @@ -1,160 +0,0 @@ ---- -phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability -plan: "11" -subsystem: testing -tags: [medications, reminders, recurrence, postgres, vitest, regression] - -requires: - - phase: 01-09 - provides: medication correctness audit and regression-test seams -provides: - - Independent RED contract for reminder occurrence identity across local midnight - - Canonical rolling-weekly overdue-band and resolved-state contracts - - Real-Postgres parity contract for list, Dashboard, GLP-1, take-all, and intake writes -affects: [medication-reminders, rolling-recurrence, medication-consumers, issue-664] - -tech-stack: - added: [] - patterns: - - deterministic Date clocks across UTC, Berlin DST, and Auckland boundaries - - real PostgreSQL persistence tests paired with focused unit contracts - - two-sided RED regressions with explicit passing safety controls - -key-files: - created: - - tests/integration/medication-reminder-cross-midnight.test.ts - - tests/integration/medication-rolling-overdue-consumers.test.ts - modified: - - src/lib/jobs/__tests__/medication-reminder-check.test.ts - - src/lib/jobs/__tests__/medication-reminder-dedup.test.ts - - src/lib/medications/scheduling/__tests__/recurrence.test.ts - - src/lib/medications/scheduling/__tests__/next-due.test.ts - - src/app/api/medications/[id]/intake/__tests__/route.test.ts - -key-decisions: - - "Kept issue #664 reminder identity and the rolling Mounjaro overdue defect as separate RED contracts." - - "Compared canonical occurrence instants at server read and real persistence boundaries instead of adding component fallbacks." - - "Required passing negative controls for exact taken suppression, next-dose isolation, retries, concurrency, tombstones, and sibling medications." - -patterns-established: - - "Occurrence contract: assertions use the exact scheduledFor instant, not a local date or wall-clock approximation." - - "RED contract: the intended defect stays failing while adjacent safety behavior must already pass." - -requirements-completed: [REM-01, REM-02, REM-03, REM-04, MED-01, MED-02, MED-03, MED-04, MED-05, MED-06] - -duration: 14min -completed: 2026-07-29 ---- - -# Phase 01 Plan 11: Medication Occurrence Identity Regressions Summary - -**Deterministic unit and real-Postgres RED contracts isolate cross-midnight reminder identity from rolling-weekly overdue projection while preserving exact-suppression and no-duplicate safety controls.** - -## Performance - -- **Duration:** 14 min -- **Started:** 2026-07-29T12:11:52Z -- **Completed:** 2026-07-29T12:25:20Z -- **Tasks:** 3 -- **Files modified:** 8 - -## Accomplishments - -- Reproduced issue #664 across UTC, ordinary Berlin time, a Berlin DST boundary, and positive-offset Auckland, including a real reminder worker and push-attempt ledger. -- Pinned the Mounjaro weekly timeline through its canonical overdue band, exact taken/skipped/auto-missed resolution, sibling schedules, and schedule-era isolation. -- Proved the current cross-consumer failure using real PostgreSQL records: list, Dashboard, GLP-1 card derivation, and take-all all lose the same prior-day occurrence. -- Protected both safety directions with passing controls for an exact taken occurrence, the next distinct dose, concurrent/retried writes, tombstoned rows, and sibling medications. - -## Task Commits - -Each task was committed atomically: - -1. **Task 1: Reproduce issue #664 with exact cross-midnight occurrence identity** - `81893ac97` (test) -2. **Task 2: Specify the canonical rolling overdue band** - `d5f473d67` (test) -3. **Task 3: Prove one authoritative result across medication consumers** - `25612294d` (test) - -Supporting fixture corrections: - -- `958b0e10d` - keep the reminder fixture type-safe -- `34372b57b` - use a canonical medication compliance status - -## Files Created/Modified - -- `src/lib/jobs/__tests__/medication-reminder-check.test.ts` - Cross-midnight worker dispatch and intake-query identity cases across zones and DST. -- `src/lib/jobs/__tests__/medication-reminder-dedup.test.ts` - Retry/dedup contract keyed to the scheduled occurrence's local date. -- `tests/integration/medication-reminder-cross-midnight.test.ts` - Real-Postgres reminder worker, intake, notification dispatch, and push-ledger regression. -- `src/lib/medications/scheduling/__tests__/recurrence.test.ts` - Prior-day rolling occurrence lookup through ordinary and DST weeks. -- `src/lib/medications/scheduling/__tests__/next-due.test.ts` - Rolling overdue band, exact resolution, sibling schedule, and era isolation. -- `tests/integration/medication-rolling-overdue-consumers.test.ts` - Real list, Dashboard, GLP-1, take-all, concurrency, retry, and unrelated-row contracts. -- `src/app/api/medications/[id]/intake/__tests__/route.test.ts` - Exact live-slot query scope and no-false-prompt positive control. - -## Verification Results - -The plan intentionally requires non-zero RED-suite exits before implementation begins. - -- **Focused unit suites:** 131 tests; 118 passed and 13 expected RED assertions failed. - - Reminder worker: 5 RED failures (four timezone/DST dispatch cases and the prior-day intake-query lower bound). - - Reminder dedup: 1 RED failure (untaken overnight occurrence is not dispatched). - - Rolling recurrence: 2 RED failures (ordinary Berlin and spring-DST prior-day occurrences return `null`). - - Rolling display due: 5 RED failures (unresolved, skipped, auto-missed, sibling schedule, and archived-era identity). - - Intake route: all 22 tests passed, including the new exact-slot isolation control. -- **Real-Postgres suites:** 8 tests; 4 passed and 4 expected RED assertions failed. - - Cross-midnight reminder: untreated retry is RED; exact taken suppression plus next distinct dose passes. - - Rolling consumers: unresolved, skipped, and auto-missed projection are RED; exact taken advancement, concurrent/retry convergence, and unrelated-row isolation pass. -- **ESLint:** Passed for all seven test files. -- **TypeScript:** Plan-owned files pass type checking. The repository-wide command remains non-zero only because concurrently added Google Health test files reference production modules that have not landed yet. - -## Decisions Made - -- The reminder bug and rolling-overdue bug share the idea of canonical occurrence identity but retain independent tests, so fixing one cannot falsely close the other. -- Consumer parity is asserted at exact ISO `scheduledFor` instants from production builders and derivations; no UI-only fallback is accepted. -- Resolved-state coverage advances to the next distinct occurrence rather than merely asserting disappearance, preventing a false fix that hides every future dose. - -## Deviations from Plan - -### Auto-fixed Issues - -**1. [Rule 3 - Blocking] Kept the reminder schedule fixture assignable** -- **Found during:** Task 1 -- **Issue:** The fixture inferred `timesOfDay` as an empty tuple-like array and `rrule` as only `null`, so direct replacement failed repository type checking. -- **Fix:** Mutated the existing schedule with `Object.assign` without changing the RED behavior. -- **Files modified:** `src/lib/jobs/__tests__/medication-reminder-dedup.test.ts` -- **Verification:** Focused ESLint passed and the suite executes with the intended assertion failure. -- **Committed in:** `958b0e10d` - -**2. [Rule 3 - Blocking] Corrected the resolved compliance fixture value** -- **Found during:** Task 3 -- **Issue:** The initial no-longer-overdue fixture used `on_track`, which is not a production `DoseStatus`. -- **Fix:** Used the canonical `upcoming` status so take-all suppression is type-correct. -- **Files modified:** `tests/integration/medication-rolling-overdue-consumers.test.ts` -- **Verification:** Repository type checking reports no Plan 01-11 errors. -- **Committed in:** `34372b57b` - ---- - -**Total deviations:** 2 auto-fixed (2 blocking fixture corrections) -**Impact on plan:** Both corrections only made the planned tests executable and type-correct; test semantics and production scope were unchanged. - -## Issues Encountered - -- Repository-wide `pnpm typecheck` is currently blocked by concurrent Plan 01-13 Google Health RED tests whose referenced `route` and `sync-progress` production modules do not yet exist. No Plan 01-11 file remains in the error output. -- The integration harness reapplies all migrations for each invocation; tests completed normally against isolated PostgreSQL containers. - -## User Setup Required - -None - no external service configuration required. - -## Next Phase Readiness - -- Implementation can address reminder-day lookup independently from rolling recurrence projection, using exact persisted occurrence identity in both paths. -- The passing controls are ready to reject duplicate intake rows, broad suppression, false already-taken prompts, and future-dose disappearance. -- No claim is made that issue #664 is resolved; these tests intentionally document the current failures. - -## Self-Check: PASSED - -- All 8 claimed files exist. -- All 5 task/supporting commits exist. - ---- -*Phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability* -*Completed: 2026-07-29* diff --git a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-12-SUMMARY.md b/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-12-SUMMARY.md deleted file mode 100644 index 45d1dffe2..000000000 --- a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-12-SUMMARY.md +++ /dev/null @@ -1,122 +0,0 @@ ---- -phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability -plan: "12" -subsystem: testing -tags: [apple-health, ecg, zip, encryption, postgres, whoop, tdd] - -requires: - - phase: 01-09 - provides: Focused security gate before product and reliability contracts -provides: - - RED streaming archive and incremental HKElectrocardiogram CSV parser contracts - - RED real-Postgres encrypted ECG persistence and fail-soft worker contracts - - Green source-agnostic HealthKit and direct WHOOP API regressions -affects: [01-19, apple-health-import, ecg-recordings, whoop] - -tech-stack: - added: [] - patterns: - - Real ZIP fixtures enter through the archive path rather than trusted extracted files - - Real-Postgres failure injection uses a temporary database trigger to verify transactional fail-soft behavior - -key-files: - created: - - src/lib/measurements/__tests__/apple-health-ecg-import.test.ts - - src/lib/jobs/__tests__/apple-health-import-ecg.test.ts - - tests/integration/apple-health-import-ecg.test.ts - modified: - - src/lib/sources/__tests__/source-priority-whoop-applehealth.test.ts - - src/lib/whoop/__tests__/sync-user-whoop.test.ts - -key-decisions: - - "Plan 19 must expose streamAppleHealthEcgMembers and parseAppleHealthEcgCsv so archive bytes are bounded before persistence." - - "Stable ECG identity is content-bound and tenant-scoped: filenames do not identify recordings, while identical normalized content does." - - "HealthKit rows written by WHOOP remain APPLE_HEALTH imports, and no direct WHOOP ECG capability is implied." - -patterns-established: - - "Security RED contracts: malicious ZIP metadata and actual decompressed bytes are tested independently." - - "Privacy RED contracts: assertions cover database ciphertext, job results, failure messages, logs, and staged-file cleanup." - -requirements-completed: [HK-01, HK-02, HK-03, HK-04, HK-05, HK-06, HK-07] - -duration: 9min -completed: 2026-07-29 ---- - -# Phase 1 Plan 12: Apple ECG and WHOOP RED Contract Summary - -**Streaming ZIP, ECG normalization, encrypted persistence, fail-soft XML survival, and source-agnostic WHOOP behavior pinned before Plan 19 implementation** - -## Performance - -- **Duration:** 9 min -- **Started:** 2026-07-29T12:11:46Z -- **Completed:** 2026-07-29T12:21:02Z -- **Tasks:** 1 -- **Files modified:** 5 - -## Accomplishments - -- Added real ZIP fixtures for recognized ECG members plus traversal, duplicate names, encryption, unsupported compression, claimed-ratio bombs, actual-byte overruns, aggregate bytes, member counts, and sample limits. -- Pinned incremental CSV normalization, known device-classification passthrough, unknown-classification null behavior, malformed value/date rejection, and non-diagnostic/no-plaintext output. -- Added real-Postgres worker contracts for encryption/decryption, content-bound idempotency, distinct recordings, tenant isolation, parser revision reprocessing, malformed/unsafe auxiliary fail-soft behavior, mid-write rollback, and valid XML survival. -- Preserved 20 green unit regressions covering source priority, WHOOP-written supported HealthKit records as `APPLE_HEALTH`, direct WHOOP API orchestration, and the explicit absence of a WHOOP ECG claim. - -## Task Commits - -Each task was committed atomically: - -1. **Task 1: Pin safe bounded Apple Health ECG auxiliary import** - `6cc3de824` (test, RED) - -## Files Created/Modified - -- `src/lib/measurements/__tests__/apple-health-ecg-import.test.ts` - Streaming archive and incremental ECG CSV parser attack/positive matrix. -- `src/lib/jobs/__tests__/apple-health-import-ecg.test.ts` - Parser revision, fail-soft worker wiring, bounded progress, privacy, and no-diagnosis contracts. -- `tests/integration/apple-health-import-ecg.test.ts` - End-to-end worker contracts against migrated PostgreSQL and the existing encrypted `EcgRecording` model. -- `src/lib/sources/__tests__/source-priority-whoop-applehealth.test.ts` - Source-agnostic WHOOP-written HealthKit regressions and no ECG inference. -- `src/lib/whoop/__tests__/sync-user-whoop.test.ts` - Direct WHOOP resource orchestration regression and explicit no-ECG claim. - -## Decisions Made - -- The archive contract accepts an archive path and yields only stream handles with normalized member names; it never returns a filesystem path for an auxiliary ECG. -- Limits are injectable in tests so every byte/count/compression boundary is exercised with small fixtures, while production can use safe real-world defaults. -- The CSV contract converts waveform values to integer microvolts and carries only the recording timestamp, sampling frequency, samples, lead, average heart rate, and mapped device verdict. -- Only `Sinus Rhythm`, `Atrial Fibrillation`, and `Inconclusive` map to existing device-verdict enums; localized or unknown classifications persist as null. -- The real worker, not a persistence mock, owns the integration contract. A temporary PostgreSQL trigger forces a write failure and is dropped in `finally`. - -## Deviations from Plan - -None - plan executed exactly as written. - -## Issues Encountered - -- Initial positive fixtures assumed the canonical type name was `HEART_RATE`; the established mapping correctly emits `PULSE`, so the source-agnostic and XML-survival assertions were corrected before commit. -- Direct WHOOP resource mocks receive a second options object; the regression was aligned to the real call shape and is green. -- Repository-wide `pnpm typecheck` remains RED only in concurrently added Google Health contract files (`sync-progress.test.ts` and `google-health-sync-progress.test.ts`). After correcting Plan 01-12's regular-expression target compatibility, no type errors point to the five Apple ECG/WHOOP files. - -## Verification Evidence - -- Unit gate: **expected RED**, 27 failed ECG archive/parser/worker implementation contracts; 20 source/WHOOP regressions passed. -- Real-Postgres gate: **expected RED**, 7 failed ECG persistence/result contracts after a successful container boot and all 280 migrations. -- Focused ESLint: passed for all five owned files. -- Prettier and `git diff --check`: passed for all five owned files. -- Gitleaks pre-commit hook: passed. - -## User Setup Required - -None - this plan adds test contracts only. - -## Next Phase Readiness - -- Plan 19 has executable API, security-limit, normalization, persistence, privacy, progress, and compatibility contracts. -- The RED failures are attributable to the missing Apple ECG modules/worker branch and existing parser revision 2, not fixture or infrastructure failures. - -## Self-Check: PASSED - -- All five owned test files exist. -- Task commit `6cc3de824` exists on the current branch. -- No production, schema, migration, package, lockfile, locale, Google, or unrelated job test was changed by Plan 01-12. - ---- -*Phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability* -*Completed: 2026-07-29* diff --git a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-13-SUMMARY.md b/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-13-SUMMARY.md deleted file mode 100644 index 45eeb0703..000000000 --- a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-13-SUMMARY.md +++ /dev/null @@ -1,132 +0,0 @@ ---- -phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability -plan: "13" -subsystem: testing -tags: [google-health, vitest, postgres, prisma, cache-invalidation, privacy] - -# Dependency graph -requires: - - phase: 01-09 - provides: immutable baseline and reporter-evidence boundary -provides: - - RED contracts for workout-first Google Health sync fairness under the observed 786-page heart-rate fixture - - Bounded per-resource outcomes and race-safe current-run progress contracts - - Authenticated-subject-only status, timeout polling, and post-commit cache visibility contracts -affects: [01-21, google-health-sync, integration-reliability] - -# Tech tracking -tech-stack: - added: [] - patterns: - - fixed-cardinality redacted per-resource outcome DTOs - - owner-and-runId guarded current-run progress - - real-PostgreSQL cache visibility verification - -key-files: - created: - - src/lib/google-health/__tests__/sync-fairness.test.ts - - src/lib/google-health/__tests__/sync-outcomes.test.ts - - src/lib/google-health/__tests__/sync-progress.test.ts - - tests/integration/google-health-sync-progress.test.ts - - src/app/api/google-health/sync/status/__tests__/route.test.ts - - src/components/settings/integrations/__tests__/google-health-card.test.tsx - - tests/integration/google-health-workout-terminal.test.ts - modified: - - src/app/api/google-health/sync/__tests__/route.test.ts - -key-decisions: - - "Model only the observed 786 heart-rate pages and 323.849-second trace; do not infer a provider fault or universal page ceiling." - - "Keep progress as one nullable owner-scoped current-run envelope with unpredictable runId and fixed bounded resource fields." - - "Require workout cache visibility and client invalidation only from evidence-backed committed workout writes." - -patterns-established: - - "RED contract isolation: future modules and routes fail specifically as missing while existing route behavior remains green." - - "Privacy boundary: progress and resource outcomes exclude tokens, URLs, raw errors, health values, and per-sample facts." - -requirements-completed: [GH-01, GH-02, GH-03, GH-04, GH-05, GH-06, GH-07, SYNC-01, SYNC-03] - -# Metrics -duration: 10min -completed: 2026-07-29 ---- - -# Phase 01 Plan 13: Google Health Reliability RED Contracts Summary - -**Workout-first fairness, durable owner-scoped progress, honest terminal recovery, and post-commit cache visibility are pinned as privacy-bounded RED contracts without inventing a reporter cause** - -## Performance - -- **Duration:** 10 min -- **Started:** 2026-07-29T12:13:41Z -- **Completed:** 2026-07-29T12:23:02Z -- **Tasks:** 2 -- **Files modified:** 9 - -## Accomplishments - -- Reproduced starvation with 786 successful dense-heart-rate pages and proved the current ordering reaches workout only after that history. -- Defined the exact nine-field resource outcome DTO plus complete, partial, empty, truncated, collection, token, upsert, and rollup-failure states. -- Pinned owner/run-ID concurrency, newer-run precedence, stale interruption, single-current-run retention, fixed cardinality, and privacy at unit and real-PostgreSQL boundaries. -- Pinned authenticated-subject-only status reads, POST timeout-to-terminal polling, server post-commit visibility, and exact React Query invalidation roots. - -## Task Commits - -Each task was committed atomically: - -1. **Task 1: Pin fair ordering and a race-safe bounded current-run ledger** - `d8e52b831` (test) -2. **Task 2: Pin status recovery and post-commit cache visibility** - `6f8462948` (test) - -## Files Created/Modified - -- `src/lib/google-health/__tests__/sync-fairness.test.ts` - 786-page workout-first scheduling contract. -- `src/lib/google-health/__tests__/sync-outcomes.test.ts` - Exact bounded and redacted resource outcome contracts. -- `src/lib/google-health/__tests__/sync-progress.test.ts` - Current-run ownership, concurrency, staleness, and privacy contracts. -- `tests/integration/google-health-sync-progress.test.ts` - Real-PostgreSQL persistence and race contracts. -- `src/app/api/google-health/sync/__tests__/route.test.ts` - Rich terminal POST response and privacy contracts. -- `src/app/api/google-health/sync/status/__tests__/route.test.ts` - Authenticated-subject-only status route negative cases. -- `src/components/settings/integrations/__tests__/google-health-card.test.tsx` - Timeout polling and exact client invalidation contracts. -- `tests/integration/google-health-workout-terminal.test.ts` - Real-PostgreSQL insert/update cache visibility contracts. -- `.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-13-SUMMARY.md` - Execution evidence and handoff. - -## Verification Evidence - -- Task 1 unit group: expected RED, 3 files failed and 6/6 tests failed specifically on workout ordering, absent resource outcomes, and the missing progress module. -- Task 1 real-PostgreSQL group: expected RED, 4/4 tests failed specifically because the planned progress module is absent. -- Task 2 unit/route/card group: expected RED, 6 failed and 5 passed. Failures identify the dropped POST resource verdict, missing status route, absent timeout polling, and absent exact invalidation roots. Existing legacy POST outcomes, secret omission, and the card's owner/query-key negative guard remain green. -- Task 2 real-PostgreSQL group: expected RED, 2/2 tests failed because both inserted and updated committed workouts remain behind the primed server workout cache. -- Prettier and ESLint passed on all eight owned test files. - -## Decisions Made - -- The fixture records the reporter evidence exactly: 786 heart-rate pages and 323,849 ms. It deliberately sets `truncated: false`; no page cap or provider failure is inferred. -- The storage seam is named `startGoogleHealthSyncProgress`, `updateGoogleHealthSyncProgress`, and `readGoogleHealthSyncProgress`, with user ID plus run ID guarding delayed writers. -- Cache correctness is proven against real PostgreSQL by priming the public cached workout read before insert and update, then requiring the post-sync read to expose committed state. -- GH-01 and GH-06 remain reporter-evidence gated; these RED contracts do not claim the live incident is resolved. - -## Deviations from Plan - -None - plan executed exactly as written. - -## Issues Encountered - -None. All failures are the intentional implementation gaps required by this RED-only plan. - -## User Setup Required - -None - no external service configuration required. - -## Next Phase Readiness - -- Plan 01-21 can implement the named progress seam, status route, fair resource ordering, terminal DTO propagation, and post-commit invalidation without interpreting the reporter trace. -- GH-01/GH-06 stay open until reporter evidence confirms the production symptom. - -## Self-Check: PASSED - -- All eight owned test files and this summary exist. -- Task commits `d8e52b831` and `6f8462948` exist. -- Branch remains `release/v1.34.1`. -- Scanner-owned package, lock, workspace, and binary-diff hashes exactly match the immutable baseline. - ---- -*Phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability* -*Completed: 2026-07-29* diff --git a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-14-SUMMARY.md b/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-14-SUMMARY.md deleted file mode 100644 index 4aa4ff26b..000000000 --- a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-14-SUMMARY.md +++ /dev/null @@ -1,113 +0,0 @@ ---- -phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability -plan: "14" -subsystem: testing -tags: [jobs, cohorts, google-health, withings, telemetry, privacy, tdd] - -requires: - - phase: 01-09 - provides: Focused security gate before reliability contract work -provides: - - RED Google Health and Withings per-user verdict aggregation contracts - - RED bounded privacy-safe job outcome serialization contracts -affects: [01-22, job-outcomes, integration-cohorts, worker-telemetry] - -tech-stack: - added: [] - patterns: - - Provider-neutral per-user verdict folding into fixed aggregate counters - - Explicit serialization boundary between internal job causes and emitted job facts - -key-files: - created: - - src/lib/jobs/reminder/__tests__/integration-cohort-outcomes.test.ts - - src/lib/jobs/__tests__/job-outcome.test.ts - modified: [] - -key-decisions: - - "users_synced counts only complete users; partial, failed, parked, and skipped users retain separate counters." - - "A complete zero-write run is clean_zero, while complete or partial raw writes are useful work." - - "Job output serialization uses a fixed fact-key allowlist, stable short codes, safe non-negative integer counts, and a 2 KiB ceiling." - -patterns-established: - - "Cohort privacy: only fixed aggregate counts and provider/outcome codes leave the fold; per-user objects never do." - - "Retry truth: a retryable cohort-wide failure remains ok:false, while isolated retryable users are counted without retrying the whole cohort." - -requirements-completed: [SYNC-02, JOB-01] - -duration: 4min -completed: 2026-07-29 ---- - -# Phase 1 Plan 14: Truthful Cohort and Job Outcome RED Contract Summary - -**Google/Withings verdict folding and bounded job serialization pinned against false synced counts and private telemetry** - -## Performance - -- **Duration:** 4 min -- **Started:** 2026-07-29T12:22:40Z -- **Completed:** 2026-07-29T12:26:31Z -- **Tasks:** 1 -- **Files modified:** 2 - -## Accomplishments - -- Added table-driven Google Health and Withings contracts for complete, partial, failed, parked, skipped, useful-write, and clean-zero outcomes. -- Required failed, parked, skipped, and partial users to remain outside `users_synced`, with the five exclusive status counters summing to total. -- Pinned rollup/downstream partial failure and retryable-user counts without turning isolated user failure into a cohort-wide queue retry. -- Added fixed-key, scalar-type, count-range, reason-code, fact-count, payload-byte, and privacy-negative serialization tests. -- Directly captured the current Google resolved-failure discard and Withings unconditional `usersSynced++` behavior as RED handler-wiring regressions. - -## Task Commits - -Each task was committed atomically: - -1. **Task 1: Define truthful cohort and high-volume job scalars** - `690c4aeb5` (test, RED) - -## Files Created/Modified - -- `src/lib/jobs/reminder/__tests__/integration-cohort-outcomes.test.ts` - Provider handler wiring, mixed verdict fold, clean-zero, downstream/retry, high-volume boundedness, and privacy contracts. -- `src/lib/jobs/__tests__/job-outcome.test.ts` - Generic allowlist, scalar validation, stable failure-code, cause-redaction, and serialized-size contracts. - -## Decisions Made - -- Plan 22 will expose `foldIntegrationCohortOutcomes` from the shared poll-cohort module so Google and Withings cannot drift into separate definitions. -- Complete users with zero imported rows count as synced and clean-zero; partial writes count as useful but not synced. -- The fold emits no per-user reason codes or identifiers. It keeps only fixed counts, a fixed provider code, and a useful/clean-zero outcome code. -- Raw `cause` may remain available internally for retry mechanics, but `serializeJobOutcome` must omit it and emit only a stable `reason_code`. -- Unsafe fact keys, free-text string facts, negative/non-finite/unsafe counts, nested data, and oversized payloads fail closed. - -## Deviations from Plan - -None - plan executed exactly as written. - -## Issues Encountered - -- Repository-wide `pnpm typecheck` remains RED only in concurrently authored Google Health contract files for missing Plan 21 implementation and mock tuple typing. No error references either Plan 01-14 file. - -## Verification Evidence - -- Focused Vitest gate: **expected RED**, 46/46 contracts fail because the verdict fold, handler wiring, allowlist, and serializer do not exist yet. -- Focused ESLint: passed for both owned files. -- Prettier and `git diff --check`: passed for both owned files. -- Gitleaks pre-commit hook: passed. - -## User Setup Required - -None - this plan adds test contracts only. - -## Next Phase Readiness - -- Plan 22 has a fixed input/output contract for provider-neutral cohort aggregation. -- The failures are implementation-specific: missing exports and the explicitly detected false-synced handler patterns, not syntax or infrastructure errors. - -## Self-Check: PASSED - -- Both owned test files exist. -- Task commit `690c4aeb5` exists on the current branch. -- No production, schema, migration, package, scanner, or unrelated test files were changed by Plan 01-14. - ---- -*Phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability* -*Completed: 2026-07-29* diff --git a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-15-SUMMARY.md b/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-15-SUMMARY.md deleted file mode 100644 index e0d1f12a3..000000000 --- a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-15-SUMMARY.md +++ /dev/null @@ -1,139 +0,0 @@ ---- -phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability -plan: "15" -subsystem: testing -tags: [performance-baseline, bundle-budget, audit-ledger, privacy] - -requires: - - phase: 01-09 - provides: performance, correctness, and read-only live audit source reports -provides: - - bounded deterministic fixtures for Google, reminder, and Health Score workload shapes - - commit-pinned release bundle and watched-route budget evidence - - privacy-safe normalized finding ledger with an executable schema checker -affects: [01-16, 01-17, 01-18, 01-20, 01-21, 01-22, 01-24, 01-25] - -tech-stack: - added: [] - patterns: - - deterministic local fixture with explicit workload and deadline bounds - - machine-checked Markdown finding records with privacy-dangerous pattern rejection - -key-files: - created: - - scripts/v1341-performance-baseline.mjs - - scripts/v1341-audit-ledger-check.mjs - - .planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-PERFORMANCE-BASELINE.md - - .planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-AUDIT-RECONCILIATION.md - - .planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-15-SUMMARY.md - modified: [] - -key-decisions: - - "Treat fixture timing as local code-shape evidence only; provider, database, cache, rendering, and production behavior remain unmeasured." - - "Keep confirmed, suspected, blocked, and cleared audit states distinct; an adjacent confirmed fix cannot resolve the externally unobserved Google report." - - "Reject credentials, identifiers, network authorities, local user paths, and explicit health values from the normalized audit ledger." - -patterns-established: - - "Bounded benchmark: require --fixture, reject production/live target arguments, cap duration, samples, scale, concurrency, and emitted data." - - "Audit ledger: sequential unique IDs plus source, status, severity, category, evidence, path, refutation, impact, requirement, verification, confidence, and unresolved fields." - -requirements-completed: [PERF-01, AUD-01, AUD-02, AUD-03, AUD-04, AUD-05] - -duration: 15min -completed: 2026-07-29 ---- - -# Phase 01 Plan 15: Performance Baseline and Audit Reconciliation Summary - -**Commit-pinned local workload and bundle measurements paired with a 35-finding privacy-safe audit ledger and executable completeness checks** - -## Performance - -- **Duration:** 15 min -- **Started:** 2026-07-29T12:28:51Z -- **Completed:** 2026-07-29T12:43:28Z -- **Tasks:** 3 -- **Files modified:** 5 - -## Accomplishments - -- Benchmarked the observed 786-page Google shape, a 2,000-medication reminder cohort with cleanup backlog, and dense/sparse Health Score processing with fixed concurrency, samples, scale, output, and a 30-second deadline. -- Built and analyzed one isolated source snapshot: 3,133 KB gzip total client chunks passed the 3,140 KB gate; all four watched routes passed with 25–29 KB headroom; Recharts remained one fingerprinted chunk. -- Reconciled 35 unique research, correctness, read-only live, and performance findings while preserving confirmed, suspected, blocked, and cleared states. -- Added a ledger checker that enforces required evidence fields, source/category coverage, sequential unique IDs, status consistency, and privacy-safe content. - -## Task Commits - -Each task was committed atomically: - -1. **Task 1: Add bounded deterministic performance fixtures** - `c0321d2f2` (test) -2. **Task 2: Record release bundle and route-budget evidence** - `eccacd91e` (test) -3. **Task 3: Normalize and validate the audit finding ledger** - `4ba4ee38c` (test) - -## Files Created/Modified - -- `scripts/v1341-performance-baseline.mjs` - Refuses live/production modes and emits bounded cold/warm fixture evidence without sensitive data. -- `scripts/v1341-audit-ledger-check.mjs` - Validates finding structure, semantic consistency, source/category coverage, and privacy constraints. -- `01-PERFORMANCE-BASELINE.md` - Records environment, fixture scale, cold/median/p95 timings, heap deltas, bundle budgets, and explicit evidence limits. -- `01-AUDIT-RECONCILIATION.md` - Maps 35 deduplicated findings to requirements and smallest planned verification. -- `01-15-SUMMARY.md` - Captures execution evidence, decisions, deviations, and readiness. - -## Decisions Made - -- Local fixture timings are baselines for regression comparison, not production capacity or latency claims. -- The Health Score fixture measures dense/sparse construction, canonical selection, and a stable composite proxy; exact production score parity remains a database-backed verification requirement. -- The existing bundle checker does not emit Settings, Admin, or Documents rows while a watched-route budget is active. Their eager-import risk remains labelled as unmeasured rather than assigned invented figures. -- The reporter-specific Google sync failure remains externally blocked; confirmed cache, pagination, and outcome defects are independently actionable but cannot substantiate that resolution claim. - -## Deviations from Plan - -### Auto-fixed Issues - -**1. [Rule 3 - Blocking] Raised only the verification build heap** - -- **Found during:** Task 2 (release build and bundle evidence) -- **Issue:** The default build compiled successfully but its TypeScript worker exhausted Node's 4 GiB heap. -- **Fix:** Re-ran the build with an 8 GiB process heap in an isolated source snapshot; no repository or runtime limit changed. -- **Files modified:** None -- **Verification:** Production build completed and `node scripts/check-bundle-budget.mjs --check` passed. -- **Committed in:** `eccacd91e` - -**2. [Rule 1 - Bug] Kept regenerated Markdown free of trailing whitespace** - -- **Found during:** Task 2 report verification -- **Issue:** The generator used Markdown hard-break whitespace, causing `git diff --check` warnings. -- **Fix:** Emitted blank-separated metadata fields and regenerated the checked report. -- **Files modified:** `scripts/v1341-performance-baseline.mjs`, `01-PERFORMANCE-BASELINE.md` -- **Verification:** `git diff --check` returned clean. -- **Committed in:** `eccacd91e` - ---- - -**Total deviations:** 2 auto-fixed (1 blocking issue, 1 generated-report bug) - -**Impact on plan:** Both changes were verification-only and preserved the prohibition on production load, optimization, dependency changes, and product-source edits. - -## Issues Encountered - -- A first isolated snapshot used an external `node_modules` symlink that Turbopack correctly rejected; an APFS-cloned dependency directory kept resolution inside the snapshot. -- Generated Prisma artifacts are intentionally untracked and had to be supplied as build prerequisites. They were not treated as product-source changes. - -## User Setup Required - -None - no external service configuration or manual action is required. - -## Next Phase Readiness - -- Plans 16–22 can use stable finding IDs, requirement mappings, and planned verification fields to distinguish implementation work from hypotheses. -- Plan 24 should rerun the bundle gate after the final merge, add direct Settings/Admin/Documents route evidence if needed, and reconcile each unresolved finding without weakening privacy or screening. -- Aggregate bundle headroom is only 7 KB, so the final merged build must remeasure rather than relying on this intermediate snapshot. - -## Self-Check: PASSED - -- All five owned files exist. -- Task commits `c0321d2f2`, `eccacd91e`, and `4ba4ee38c` exist on the branch. -- Fresh bounded fixture execution, ledger schema/privacy check, bundle budget gate, and `git diff --check` all pass. - ---- -*Phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability* -*Completed: 2026-07-29* diff --git a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-16-SUMMARY.md b/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-16-SUMMARY.md deleted file mode 100644 index b076cb6a8..000000000 --- a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-16-SUMMARY.md +++ /dev/null @@ -1,137 +0,0 @@ ---- -phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability -plan: "16" -subsystem: ui -tags: [react, vitest, dashboard, insights, records] - -requires: - - phase: 01-10 - provides: RED presentation and records interaction contracts -provides: - - Non-redundant dashboard, document-summary, and Health Score presentation - - Foreground treatment and compact spacing for generated assessment prose - - Consistent Save and first-entry CTA grammar across Records editors -affects: [01-20, 01-23, dashboard, insights, records] - -tech-stack: - added: [] - patterns: - - Displayed metric values are not repeated in adjacent generated prose - - Empty collections expose one first-entry CTA while populated collections expose the standard Add action - -key-files: - created: [] - modified: - - src/components/daily/today-hero.tsx - - src/components/documents/document-summary-block.tsx - - src/components/insights/health-score-card.tsx - - src/app/insights/medications/page.tsx - - src/components/insights/mood/mood-better-days.tsx - - src/components/records/health-profile-facts-manager.tsx - - src/components/records/allergy-manager.tsx - - src/components/records/family-history-manager.tsx - -key-decisions: - - "The Today Hero suppresses deterministic score prose while the score is provisional and removes only the sentence that repeats a displayed rounded score." - - "Health Score retains method version and source provenance while removing repeated method and composition prose." - - "Records collection Add actions are derived from row presence, leaving a single first-entry CTA in empty states." - -patterns-established: - - "Assessment hierarchy: generated assessment prose uses foreground text and compact vertical rhythm." - - "Action grammar: editable lifestyle facts consistently use Save; destructive and history controls remain unchanged." - -requirements-completed: [DASH-01, DOC-01, INS-01, INS-02, INS-03, INS-04, INS-05, INS-06, INS-07, REC-01, REC-02, REC-03, REC-04, REC-05] - -duration: 5min -completed: 2026-07-29 ---- - -# Phase 1 Plan 16: Dashboard, Insight, and Records Presentation Cleanup Summary - -**Non-redundant dashboard and insight prose with clearer assessment hierarchy and consistent Records action grammar** - -## Performance - -- **Duration:** 5 min -- **Started:** 2026-07-29T12:25:30Z -- **Completed:** 2026-07-29T12:30:21Z -- **Tasks:** 3 -- **Files modified:** 8 - -## Accomplishments - -- Removed repeated score prose from the Today Hero without fabricating a score while the ring remains provisional. -- Simplified stored document summaries and Health Score presentation while retaining regeneration for absent summaries and preserving score provenance. -- Normalized generated Health Score, medication, and mood assessment prose to foreground text with a tighter vertical rhythm. -- Unified lifestyle fact editing on Save and removed duplicate Add actions from empty allergy and family-history states. -- Kept removal, history, localization, API, and score-engine behavior unchanged. - -## Task Commits - -Each task was committed atomically: - -1. **Task 1: Remove redundant dashboard, document, and Health Score presentation** - `d67b89913` (fix) -2. **Task 2: Normalize assessment foreground and spacing** - `c4ec2d552` (fix) -3. **Task 3: Unify Records Save and CTA grammar** - `431120d55` (fix) - -## Files Created/Modified - -- `src/components/daily/today-hero.tsx` - Removes the sentence that repeats the displayed score and avoids deterministic score prose during provisional scoring. -- `src/components/documents/document-summary-block.tsx` - Removes regeneration from the stored-summary branch while preserving absent-state generation. -- `src/components/insights/health-score-card.tsx` - Removes redundant notice, method, and composition prose and tightens the assessment stack. -- `src/app/insights/medications/page.tsx` - Promotes generated assessment prose to foreground text with compact spacing. -- `src/components/insights/mood/mood-better-days.tsx` - Promotes generated mood assessment prose to foreground text. -- `src/components/records/health-profile-facts-manager.tsx` - Uses Save consistently for lifestyle fact edits. -- `src/components/records/allergy-manager.tsx` - Shows the normal Add action only after the collection has rows. -- `src/components/records/family-history-manager.tsx` - Shows the normal Add action only after the collection has rows. - -## Decisions Made - -- Preserve any non-score sentences in the Today Hero generated lead; suppress the lead only when removing the repeated score sentence leaves no content. -- Retain the pure algorithm-notice dismissal helper because its existing contract remains useful, even though the notice is no longer rendered. -- Preserve method version, band selection, delta, pillar, and source provenance while removing only redundant Health Score explanatory prose. -- Base collection CTA visibility on row presence so empty states have exactly one entry point. - -## Deviations from Plan - -### Auto-fixed Issues - -**1. [Rule 3 - Blocking] Added Mood Better Days to foreground normalization** - -- **Found during:** Task 2 -- **Issue:** The focused assessment-spine contract intentionally covers Mood Better Days, but the plan ownership list omitted its component and no other phase plan owned the change. -- **Fix:** Changed only the generated assessment description from muted foreground to foreground text. -- **Files modified:** `src/components/insights/mood/mood-better-days.tsx` -- **Commit:** `c4ec2d552` - -## Issues Encountered - -None. - -## Verification Evidence - -- Full focused Vitest gate: **8 files passed, 65 tests passed**. -- Task 1 Vitest gate: **4 files passed, 40 tests passed**. -- Task 2 Vitest gate: **2 files passed, 20 tests passed**. -- Task 3 Vitest gate: **3 files passed, 12 tests passed**. -- Focused ESLint, Prettier, and `git diff --check`: passed for every owned production file. -- Protected-scope comparison confirmed no Plan 16 changes under `src/app/api`, `messages/en.json`, `messages/de.json`, `src/lib/analytics/score`, or `src/components/insights/insight-status-card.tsx`. - -## User Setup Required - -None. - -## Next Phase Readiness - -- The dashboard, document-summary, Health Score, medication, mood, and Records interaction contracts from Plan 10 are green. -- Downstream dashboard and insight integration work can rely on a single presentation hierarchy without changing API or score-engine behavior. - -## Self-Check: PASSED - -- All eight owned production files and this summary exist. -- Task commits `d67b89913`, `c4ec2d552`, and `431120d55` exist on the current branch. -- Protected API, locale, score-engine, and shared insight-status targets remain unchanged by Plan 01-16. - ---- -*Phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability* -*Completed: 2026-07-29* diff --git a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-17-SUMMARY.md b/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-17-SUMMARY.md deleted file mode 100644 index bcf99daf4..000000000 --- a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-17-SUMMARY.md +++ /dev/null @@ -1,131 +0,0 @@ ---- -phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability -plan: "17" -subsystem: ui -tags: [navigation, responsive-layout, accessibility, playwright, tailwind] - -# Dependency graph -requires: - - phase: 01-10 - provides: responsive shell component and Playwright geometry contracts -provides: - - fail-closed grouped Admin navigation with empty-group omission - - heading-aligned sticky Settings and Admin desktop sidebars - - gap-free mobile Insights pill mount with preserved focus and touch geometry -affects: [navigation, settings, admin, insights, responsive-shells] - -# Tech tracking -tech-stack: - added: [] - patterns: - - filter authorized navigation items before deriving group boundaries - - one main vertical scroll owner with bounded sticky sidebar content - - route-scoped inherited-spacing removal without negative margins - -key-files: - created: [] - modified: - - src/components/admin/admin-shell.tsx - - src/components/settings/settings-shell.tsx - - src/components/insights/insights-layout-shell.tsx - -key-decisions: - - "Reuse existing Settings group translations for Admin headings rather than add locale keys." - - "Keep AuthShell as the only page scroll owner and cap sidebar height against the existing 10.5rem shell budget." - - "Remove only the inherited mobile Insights top padding through a route-scoped parent selector; retain horizontal and safe-area-aware bottom padding." - -patterns-established: - - "Grouped navigation: render headings and separators from the already-filtered visible destination list." - - "Responsive heading semantics: expose one visible level-one heading per breakpoint without duplicate native h1 geometry." - -requirements-completed: [INS-09, NAV-01, NAV-02, NAV-03, NAV-04, NAV-05] - -# Metrics -duration: 13min -completed: 2026-07-29 ---- - -# Phase 01 Plan 17: Stable Grouped Navigation Summary - -**Fail-closed grouped Admin navigation, heading-aligned pinned desktop sidebars, and a gap-free accessible mobile Insights pill row verified across both Chromium projects** - -## Performance - -- **Duration:** 13 min -- **Started:** 2026-07-29T12:25:41Z -- **Completed:** 2026-07-29T12:38:43Z -- **Tasks:** 3 -- **Files modified:** 4 - -## Accomplishments - -- Clustered every existing Admin destination into five meaningful Settings-style groups only after the confirmed-Admin gate, with no empty headings and quiet mobile separators. -- Aligned Settings and Admin sidebars with their visible page headings and kept them pinned across desktop, short desktop, and tablet without width movement. -- Preserved AuthShell as the page scroll owner while bounding only long sidebar content on short viewports. -- Removed the duplicated 24px mobile Insights pre-pill spacer without negative margins, preserving safe bounds, focus visibility, and 44px targets. -- Regenerated and attached all five deterministic v1.34.1 UI evidence images. - -## Task Commits - -Each task was committed atomically: - -1. **Task 1: Group Admin navigation after fail-closed gating** - `7396119ec` (feat) -2. **Task 2: Align and pin desktop sidebars with one scroll owner** - `23c738efa` (fix) -3. **Task 3: Remove only the unexplained mobile Insights gap** - `b8260474e` (fix) - -## Files Created/Modified - -- `src/components/admin/admin-shell.tsx` - Adds gated semantic groups and heading-row sticky sidebar geometry. -- `src/components/settings/settings-shell.tsx` - Aligns the sticky sidebar with the heading and shares the shell height budget. -- `src/components/insights/insights-layout-shell.tsx` - Removes only the inherited mobile pre-pill top spacer. -- `.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-17-SUMMARY.md` - Execution evidence and handoff. - -## Verification Evidence - -- `TZ=UTC pnpm exec vitest run src/components/settings/__tests__/settings-shell.test.tsx src/components/admin/__tests__/admin-shell.test.tsx`: 31/31 passed. -- ESLint passed on all three owned production components. -- Explicit desktop/mobile Plan 10 Playwright command: 15 passed, 1 expected skip because deterministic evidence capture runs once on `chromium-desktop`. -- Browser checks passed for Settings and Admin at 1440×900, 1280×600, and 900×700 in both configured projects. -- Mobile Insights passed gap, horizontal bounds, overflow, visible focus, and minimum 44px target assertions. -- Evidence produced exactly: - - `test-results/v1341/dashboard-desktop.png` - - `test-results/v1341/insights-mobile.png` - - `test-results/v1341/anamnesis-desktop.png` - - `test-results/v1341/settings-short-desktop.png` - - `test-results/v1341/admin-tablet.png` - -## Decisions Made - -- Admin keeps the original confirmed-Admin fail-closed boundary. Grouping consumes only that authorized visible list and introduces no new authorization or module predicate. -- Admin group labels reuse existing translated Settings grammar (`System`, `Connectivity`, `AI`, `Account`, and `Your data`), avoiding locale churn. -- Sidebar `max-height` uses the same 10.5rem viewport budget as the shared sub-shell floor. This leaves enough containing-block travel for sticky positioning while constraining a long list only when needed. -- The generic AuthShell `pt-6` is suppressed only when the mobile Insights shell is the direct route child. Horizontal padding and safe-area-aware bottom clearance remain owned by AuthShell. - -## Deviations from Plan - -None - plan executed exactly as written. - -## Issues Encountered - -- The repository's direct Playwright command did not load `.env`, and concurrent work repeatedly held the shared Next production-build lock. Verification was run against an isolated current-source Next dev server on port 3017 with the existing seeded database and plain-HTTP cookie override. The server was stopped after the full green run. -- The geometry test's native `h1` selector initially selected a hidden duplicate mobile heading at desktop. Both shells now retain an accessible level-one mobile heading while exposing only the visible desktop heading as the native `h1`, avoiding duplicate native heading geometry and duplicate Settings IDs. - -## User Setup Required - -None - no external service configuration required. - -## Next Phase Readiness - -- Grouping, scroll ownership, sticky geometry, safe mobile spacing, focus, and touch-target behavior are green and ready for release verification. -- No authorization, locale, package, schema, migration, or unrelated application files changed. - -## Self-Check: PASSED - -- All three owned production files and this summary exist. -- Task commits `7396119ec`, `23c738efa`, and `b8260474e` exist. -- Branch remains `release/v1.34.1`. -- Scanner-owned package, lock, workspace, and binary-diff hashes exactly match the immutable baseline. - ---- -*Phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability* -*Completed: 2026-07-29* diff --git a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-18-SUMMARY.md b/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-18-SUMMARY.md deleted file mode 100644 index 3d4bfc711..000000000 --- a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-18-SUMMARY.md +++ /dev/null @@ -1,130 +0,0 @@ ---- -phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability -plan: "18" -subsystem: medication-scheduling -tags: [medications, recurrence, reminders, occurrence-identity, postgres] - -requires: - - phase: 01-11 - provides: RED reminder and rolling-overdue occurrence contracts -provides: - - Cross-midnight reminder lookup and dedup keyed to the canonical scheduled occurrence - - Rolling prior-occurrence projection bounded by the existing actionable band - - Schedule/era-aware exact resolution with retry-safe shared consumer state -affects: [issue-664, medication-reminders, rolling-recurrence, medication-list, dashboard] - -tech-stack: - added: [] - patterns: - - canonical occurrence identity carries schedule ID plus exact scheduled instant - - display-band projection is separate from retrospective compliance thresholds - - server list state prevents client clock-window rebinding - -key-files: - created: [] - modified: - - src/lib/jobs/reminder/medication-reminder-check.ts - - src/lib/medications/scheduling/recurrence.ts - - src/lib/medications/scheduling/next-due.ts - - src/lib/medications/scheduling/band-minter.ts - - src/lib/medications/list-read.ts - -key-decisions: - - "Reminder suppression and dedup derive from the selected occurrence local date, never the worker tick date." - - "Open rolling occurrences opt into display-band minting without changing the half-cycle compliance denominator." - - "Resolved rolling skips and auto-misses advance on the existing cadence grid without becoming intake anchors." - - "Existing canonical intake upsert/retry behavior was retained because its real-Postgres controls already passed." - -patterns-established: - - "Occurrence identity: scheduleId + scheduled instant, with action time used for era isolation." - - "Consumer authority: prior rolling due state is projected once by the server and not reconstructed from today's wall clock." - -requirements-completed: [REM-01, REM-02, REM-03, REM-04, REM-05, MED-01, MED-02, MED-03, MED-04, MED-05, MED-06] - -duration: 10min -completed: 2026-07-29 ---- - -# Phase 01 Plan 18: Canonical Medication Occurrence Identity Summary - -**Canonical schedule-and-instant identity now survives reminder midnight boundaries and keeps unresolved rolling doses visible through their existing actionable band across every medication consumer.** - -## Performance - -- **Duration:** 10 min -- **Started:** 2026-07-29T12:27:25Z -- **Completed:** 2026-07-29T12:36:57Z -- **Tasks:** 3 -- **Files modified:** 6 - -## Accomplishments - -- Fixed issue #664’s worker-day mismatch by selecting canonical recurrence occurrences, widening the action lookup only to eligible cross-midnight windows, and deriving dedup/notification metadata from the occurrence itself. -- Removed the rolling post-midnight gap by retaining the prior occurrence identity and opting display-due banding into the open rolling slot without changing weekly clinical windows or compliance’s half-cycle threshold. -- Added schedule and era identity to exact resolved marks, allowing taken/skipped/auto-missed state to suppress one occurrence while sibling schedules, replacement eras, and future grid occurrences remain independent. -- Kept list, Dashboard, GLP-1 card, and take-all on the same persisted occurrence; exact intake, concurrent retries, tombstones, and sibling medications converge without duplicate live rows or false already-taken behavior. - -## Task Commits - -Each task was committed atomically: - -1. **Task 1: Carry scheduled occurrence identity through reminder lookup and dedup** - `c0af97ce4` (fix) -2. **Task 2: Keep unresolved rolling occurrence through its band** - `2de9bccf9` (fix) -3. **Task 3: Use one due result for reads, projection, and intake** - `840e47fda` (fix) - -Supporting cleanup: - -- `0ae9165aa` - repository formatting for the occurrence identity changes - -## Files Created/Modified - -- `src/lib/jobs/reminder/medication-reminder-check.ts` - Selects an active canonical occurrence across midnight and uses its exact instant/date for suppression, dedup, minting, and notification metadata. -- `src/lib/medications/scheduling/recurrence.ts` - Carries schedule identity, retains the immediate rolling occurrence across day changes, and advances resolved rolling grid occurrences. -- `src/lib/medications/scheduling/next-due.ts` - Resolves by schedule/era identity, selects open overdue bands, and rejects closed stale rolling occurrences. -- `src/lib/medications/scheduling/band-minter.ts` - Allows display consumers to include an open rolling occurrence while retrospective compliance retains its existing threshold. -- `src/lib/medications/list-read.ts` - Exposes the due schedule ID and prevents a prior rolling occurrence from being rebound to today’s matching clock window. - -## Decisions Made - -- A prior reminder occurrence is searched only when its own resolved window crosses into the current local day; ordinary stale yesterday slots do not begin reminding again. -- `Occurrence.scheduleId` is the canonical owner. Optional schedule/action metadata on persisted resolution marks preserves backward compatibility while enabling exact sibling and era isolation where attribution provides identity. -- A skipped or auto-missed rolling occurrence advances to the next cadence-grid occurrence but does not alter `lastIntakeAt`; only a real take re-anchors rolling cadence. -- The existing intake upsert already passed real concurrent/retry and unrelated-row tests, so no route or schema change was warranted. - -## Deviations from Plan - -None - plan executed within the listed production boundaries. Files whose existing implementation already satisfied the green contract were verified and left unchanged. - -## Verification - -- **Broad focused unit cluster:** 21 files, 388 tests passed. - - Includes every scheduling unit suite, reminder worker/dedup, intake route, medication-card next-due, and take-all. -- **Real PostgreSQL regression cluster:** 2 files, 8 tests passed. - - Cross-midnight reminder: 2/2. - - Rolling overdue consumers and intake concurrency/isolation: 6/6. -- **ESLint:** Passed for all eight Plan 01-18 production files. -- **Prettier:** Passed for all eight Plan 01-18 production files. -- **TypeScript:** No Plan 01-18 errors. Repository-wide `pnpm typecheck` remains non-zero only because concurrent Google Health RED tests reference `sync-progress` and status-route production modules that have not landed yet. - -## Issues Encountered - -- Repository-wide type checking is temporarily blocked by concurrent Plan 01-13 Google Health work. The remaining diagnostics are confined to its test files; all occurrence-identity production files are clean. - -## User Setup Required - -None - no external service configuration required. - -## Next Phase Readiness - -- REM-01 through REM-04 and MED-01 through MED-06 are technically closed by green unit and real-persistence regressions. -- REM-05 remains the stated release-process constraint: issue #664 should stay open until reporter confirmation. -- No migration, package, client fallback, or clinical band widening was introduced. - -## Self-Check: PASSED - -- All 6 claimed files exist. -- All 4 implementation/supporting commits exist. - ---- -*Phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability* -*Completed: 2026-07-29* diff --git a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-19-SUMMARY.md b/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-19-SUMMARY.md deleted file mode 100644 index c12a55980..000000000 --- a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-19-SUMMARY.md +++ /dev/null @@ -1,147 +0,0 @@ ---- -phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability -plan: "19" -subsystem: apple-health-import -tags: [apple-health, ecg, zip, encryption, postgres, privacy] - -requires: - - phase: 01-12 - provides: Apple Health ECG archive, persistence, privacy, and WHOOP contract tests -provides: - - Path-safe bounded Apple Health ECG ZIP member reader - - Incremental ECG CSV normalization with device-only classification - - Encrypted content-addressed APPLE_HEALTH EcgRecording persistence - - Fail-soft ECG worker branch and privacy-safe result counters - - Exact Apple Health ECG and WHOOP limitation documentation -affects: [apple-health-import, ecg, settings-import, whoop-documentation] - -tech-stack: - added: [] - patterns: - - Central-directory validation followed by actual decompressed-byte enforcement - - Auxiliary health artifacts fail softly beside the canonical XML transaction - - Content-derived identity scoped by the existing tenant-aware database unique key - -key-files: - created: - - src/lib/apple-health/archive-stream.ts - - src/lib/apple-health/ecg-csv.ts - - src/lib/apple-health/ecg-import.ts - modified: - - src/lib/jobs/apple-health-import-worker.ts - - src/lib/measurements/import-apple-health-export.ts - - src/app/api/import/apple-health-export/[jobId]/status/route.ts - - src/components/settings/import-panel/apple-health-import-card.tsx - - docs/integrations/apple-health.md - -key-decisions: - - "Validate the complete ZIP directory before yielding ECG data and enforce both declared and actual decompression bounds." - - "Derive externalRecordingId from normalized ECG content, leaving tenant separation to the existing compound unique key." - - "Preserve only known source-device rhythm classifications; unknown labels remain null and waveforms are never diagnostically interpreted." - - "Complete the canonical export.xml import before the fail-soft auxiliary ECG branch so bad ECGs cannot roll back valid XML data." - -patterns-established: - - "ECG result envelopes contain only discovered/imported/updated/skipped/failed integer counters." - - "Raw ECG samples are encrypted before a database transaction begins and scrubbed after the write attempt." - -requirements-completed: [HK-01, HK-02, HK-03, HK-04, HK-05, HK-06, HK-07] - -duration: 10min -completed: 2026-07-29 ---- - -# Phase 01 Plan 19: Apple Health ECG Import Summary - -**Bounded path-safe ECG archive ingestion with AES-256-GCM persistence, content-stable identity, fail-soft XML coexistence, and privacy-safe progress counters** - -## Performance - -- **Duration:** 10 min -- **Started:** 2026-07-29T12:39:00Z -- **Completed:** 2026-07-29T12:48:47Z -- **Tasks:** 3 -- **Files modified:** 8 - -## Accomplishments - -- Added a no-temp-file ZIP reader that rejects traversal, duplicate names, encryption, unsupported methods, forged metadata, compression bombs, and actual byte overruns. -- Parsed Apple ECG CSVs incrementally into bounded integer-microvolt waveforms while discarding patient/source labels and accepting only known device classifications. -- Persisted APPLE_HEALTH ECGs through the existing encryption codec and `EcgRecording` model with filename-independent idempotency and transactional rollback. -- Kept valid WHOOP-written and other supported `export.xml` records successful when an auxiliary ECG is malformed, unsafe, or cannot be written. -- Exposed only bounded ECG counters to the status route and card, and documented exact limits and WHOOP export constraints. - -## Task Commits - -Each task was committed atomically: - -1. **Task 1: Stream only safe bounded ECG archive members** - `f06137c0d` -2. **Task 2: Encrypt and idempotently persist source-device ECG records** - `a686c7200` -3. **Task 3: Expose bounded progress and document exact support** - `4b64556b3` -4. **Task 2 follow-up: Avoid phantom ECG failure counts** - `addd6b700` - -## Files Created/Modified - -- `src/lib/apple-health/archive-stream.ts` - Validates ZIP structure and streams only bounded ECG CSV data. -- `src/lib/apple-health/ecg-csv.ts` - Incrementally normalizes ECG descriptors and waveform samples. -- `src/lib/apple-health/ecg-import.ts` - Derives stable identity, encrypts samples, and transactionally upserts recordings. -- `src/lib/jobs/apple-health-import-worker.ts` - Runs ECG ingestion as a parser-revision-3 fail-soft auxiliary branch. -- `src/lib/measurements/import-apple-health-export.ts` - Defines the bounded ECG result envelope. -- `src/app/api/import/apple-health-export/[jobId]/status/route.ts` - Sanitizes ECG outcomes to five non-negative counters. -- `src/components/settings/import-panel/apple-health-import-card.tsx` - Renders ECG-only success and partial outcomes. -- `docs/integrations/apple-health.md` - Documents supported ECG behavior, bounds, privacy, idempotency, and WHOOP limitations. - -## Decisions Made - -- Used Node's existing ZIP/zlib capabilities and no new dependency or schema change. -- Kept content identity identical across users while relying on `(userId, source, externalRecordingId)` for tenant-safe deduplication. -- Treated source-device classification as a stored fact only; no waveform analysis, inference, or diagnosis was added. -- Stored ECG counters only in job results; filenames, writer labels, samples, values, and raw per-ECG failures do not enter result or logging output. - -## Deviations from Plan - -### Auto-fixed Issues - -**1. [Rule 1 - Bug] Prevented a phantom failed-ECG count** - -- **Found during:** Final review after Task 3 -- **Issue:** A structural archive error with zero discovered ECG members could increment `failed` to one. -- **Fix:** Archive-level failure accounting now increments only for discovered ECG artifacts not already represented by another terminal counter. -- **Files modified:** `src/lib/jobs/apple-health-import-worker.ts` -- **Verification:** Worker ECG source contract and full Plan 12 unit matrix pass. -- **Committed in:** `addd6b700` - ---- - -**Total deviations:** 1 auto-fixed bug -**Impact on plan:** Corrected bounded-counter accuracy without expanding scope. - -## Verification - -- Plan 12 and regression unit suites: **107 tests passed across 8 files**. -- Real PostgreSQL Apple Health suites: **25 tests passed across 4 files**, including ECG encryption/idempotency/rollback, worker failures, natural keys, and orphan reconciliation. -- Focused ESLint, Prettier, documentation diff, and privacy/source-contract checks passed. -- Repository-wide `pnpm typecheck` is currently blocked only by concurrent Google Health work: a not-yet-created status route and test mock tuple typing in `src/lib/google-health/**`. No Plan 19 file appears in the TypeScript error output. - -## Issues Encountered - -- The Task 3 verification command references `src/components/settings/import-panel/__tests__/apple-health-import-card.test.tsx`, but that file does not exist in the Plan 12 test commit. The available import-panel component suite was run instead and passed. -- Concurrent plans modified package locks, Prisma schema/migrations, and job files during execution. None were staged in Plan 19 commits. - -## User Setup Required - -None - no new dependency, migration, environment variable, or external service configuration is required. - -## Next Phase Readiness - -- Apple Health ECG import is ready for release-level validation. -- Plan 23 remains the sole owner of locale JSON; the card therefore uses the permitted temporary English ECG outcome copy. - -## Self-Check: PASSED - -- All eight implementation/documentation files exist. -- Task commits `f06137c0d`, `a686c7200`, `4b64556b3`, and `addd6b700` exist. - ---- - -*Phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability* -*Completed: 2026-07-29* diff --git a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-20-SUMMARY.md b/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-20-SUMMARY.md deleted file mode 100644 index 6497537f0..000000000 --- a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-20-SUMMARY.md +++ /dev/null @@ -1,137 +0,0 @@ ---- -phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability -plan: "20" -subsystem: insights -tags: [react-query, assessment, safety, cache, polling, accessibility] - -requires: - - phase: 01-10 - provides: RED assessment route, hook, card, and spine contracts - - phase: 01-16 - provides: Compact foreground assessment presentation -provides: - - Discriminated generated, fallback, provider, pending, failure, and exhaustion states - - Fail-closed derived assessment screening with deterministic fallback - - Explicit generated-cache freshness and negative-cache retry policy - - Bounded query-identity polling with timeout, error, and exhaustion transitions - - Shared stable card rendering consumed by metric and slug wrappers -affects: [01-23, assessment-routes, insight-status-workers, metric-pages] - -tech-stack: - added: [] - patterns: - - Provider availability is metadata and never outranks safe assessment text - - Poll attempts are bounded per React Query cache identity - - Provider failures cross the client boundary only as fixed status codes - -key-files: - created: [] - modified: - - src/lib/insights/status-shared.ts - - src/lib/insights/status-cache.ts - - src/lib/insights/derived/derived-assessment-ai.ts - - src/hooks/use-insight-status.ts - - src/components/insights/insight-status-card.tsx - - src/components/insights/metric-status-card.tsx - - src/components/insights/slug-insight-status-card.tsx - -key-decisions: - - "Use kind as the sole assessment branch discriminator; hasProvider remains provenance metadata." - - "Keep legacy route scalars during rollout and normalize them once in the shared hook." - - "Represent generated cache expiry by the user's date key and negative-cache recovery by an explicit retryAt boundary." - - "Convert request aborts, fetch failures, and poll ceilings to fixed terminal states without carrying raw provider errors." - -patterns-established: - - "Safety fallback: screened, malformed, or ungrounded derived prose is never persisted; the deterministic score assessment remains visible." - - "Stable assessment geometry: pending, provider setup, failure, exhaustion, empty, and populated branches share gap-2 / py-3 / md:py-4." - -requirements-completed: [INS-06, INS-07, INS-08, AI-01, AI-02, AI-03, AI-04, AI-05] - -duration: 10min -completed: 2026-07-29 ---- - -# Phase 1 Plan 20: Bounded Fail-Closed Assessment State Summary - -**One explicit assessment state machine now preserves safe fallback text, blocks unsafe generated prose, and terminates every polling path** - -## Performance - -- **Duration:** 10 min -- **Started:** 2026-07-29T12:31:57Z -- **Completed:** 2026-07-29T12:41:35Z -- **Tasks:** 3 -- **Files modified:** 7 - -## Accomplishments - -- Added a compact discriminated contract for generated, screened fallback, no-provider, preparing, revalidating, timeout, error, and exhausted outcomes. -- Routed derived-score provider prose through the shared fail-closed outbound screen before persistence; blocked, malformed, and ungrounded output now resolves to the safe deterministic assessment. -- Made generated-cache day expiry and negative-cache retry timing explicit without adding provider responses, raw errors, or health values to the envelope. -- Bound polling to each locale/metric query identity, aborted requests on timeout or unmount, and replaced capped preparation with an explicit exhausted terminal state. -- Reordered shared card rendering so safe text remains visible even when `hasProvider` is false, while all non-populated states retain compact stable geometry. -- Updated both shared wrappers to pass the same normalized assessment contract. - -## Task Commits - -Each task was committed atomically: - -1. **Task 1: Classify generated, screened, fallback, and failed outcomes** - `efe203212` (fix) -2. **Task 2: Make assessment polling bounded and terminal** - `33a7dafec` (fix) -3. **Task 3: Render the shared status contract with stable geometry** - `0bf3a3573` (fix) - -## Files Created/Modified - -- `src/lib/insights/status-shared.ts` - Defines the discriminated state contract, labels finalized summaries, and persists explicit generated-cache policy. -- `src/lib/insights/status-cache.ts` - Exposes generated freshness and privacy-safe negative-cache retry classification. -- `src/lib/insights/derived/derived-assessment-ai.ts` - Screens provider prose and returns explicit safe outcomes without persisting blocked text. -- `src/hooks/use-insight-status.ts` - Normalizes legacy DTOs, bounds fetches and polling, and exposes timeout/error/exhausted terminal states. -- `src/components/insights/insight-status-card.tsx` - Renders state-first branches with safe fallback precedence and stable accessible geometry. -- `src/components/insights/metric-status-card.tsx` - Passes the normalized contract from generic metric queries. -- `src/components/insights/slug-insight-status-card.tsx` - Passes the normalized contract from bespoke metric queries. - -## Decisions Made - -- Keep the route DTO backward-compatible because API files are outside this plan; the hook enriches it with the shared discriminator. -- Treat a legacy `hasProvider:false` payload with non-empty text as a safe deterministic fallback, not as permission to hide the text behind setup guidance. -- Count attempts from React Query's cache state so locale and metric query keys naturally isolate exhaustion. -- Return fixed timeout/error states from fetch failures and discard exception details at the boundary. -- Preserve stale text during revalidation until it resolves or exhausts; exhaustion becomes terminal rather than retaining preparation flags. - -## Deviations from Plan - -None - plan executed within the seven-file production ownership boundary. - -## Issues Encountered - -- Repository-wide `pnpm typecheck` is blocked by concurrent, unowned Google Health RED-contract files: missing `src/app/api/google-health/sync/status/route.ts`, missing `src/lib/google-health/sync-progress.ts`, and associated mock tuple narrowing errors. No diagnostic references a Plan 01-20 file. - -## Verification Evidence - -- Combined generator/cache/screen/hook/card/route/spine Vitest gate: **9 files passed, 112 tests passed**. -- Task 1 Vitest gate: **3 files passed, 49 tests passed**. -- Task 2 Vitest gate: **1 file passed, 7 tests passed**. -- Task 3 card/hook gate: **3 files passed, 21 tests passed**. -- Biomarker route and assessment-spine reproduction: **2 files passed, 19 tests passed**. -- Focused ESLint, Prettier, and `git diff --check`: passed for all seven owned production files. -- Repository typecheck was attempted and produced only the concurrent Google Health contract diagnostics listed above. -- No locale JSON, API route, feature gate, or health-log implementation was changed. - -## User Setup Required - -None. - -## Next Phase Readiness - -- Plan 10 assessment route-hook-card and spine contracts are green. -- Downstream integration work can consume a stable state contract without weakening safety gates or depending on raw provider failures. - -## Self-Check: PASSED - -- All seven owned production files and this summary exist. -- Task commits `efe203212`, `33a7dafec`, and `0bf3a3573` exist on the current branch. -- Locale catalogs, API routes, and the feature-flag hook remain unchanged by Plan 01-20. - ---- -*Phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability* -*Completed: 2026-07-29* diff --git a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-21-SUMMARY.md b/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-21-SUMMARY.md deleted file mode 100644 index e812a0c29..000000000 --- a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-21-SUMMARY.md +++ /dev/null @@ -1,161 +0,0 @@ ---- -phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability -plan: "21" -subsystem: integrations -tags: [google-health, postgres, prisma, sync-progress, react-query] - -requires: - - phase: 01-13 - provides: Google Health sync correctness baseline and real-account evidence contract - - phase: 01-15 - provides: Centralized cache/query-key invalidation seams -provides: - - Race-safe bounded current-run progress retained on GoogleHealthConnection - - Workout-first serial sync orchestration with exact redacted resource outcomes - - Authenticated subject-only progress status endpoint and timeout recovery UI - - Post-commit server and browser workout cache invalidation -affects: [google-health, integration-status, manual-sync, settings-integrations] - -tech-stack: - added: [] - patterns: - - AsyncLocalStorage-scoped per-resource counters - - Run-ID guarded JSONB current-run progress - - Terminal-result recovery through authenticated status polling - -key-files: - created: - - prisma/migrations/0287_google_health_sync_progress/migration.sql - - src/lib/google-health/sync-progress.ts - - src/app/api/google-health/sync/status/route.ts - modified: - - prisma/schema.prisma - - src/lib/google-health/client.ts - - src/lib/google-health/sync.ts - - src/lib/google-health/sync-core.ts - - src/lib/google-health/sync-workout.ts - - src/app/api/google-health/sync/route.ts - - src/components/settings/integrations/google-health-card.tsx - -key-decisions: - - "Retain only one bounded progress envelope per Google connection; a guarded run ID prevents delayed writers from replacing a newer run." - - "Treat existing pagination ceilings as truncation only when a next-page cursor remains; the observed 786-page trace is not itself a failure or an invented cap." - - "Hold the success watermark for write, rollup, partial, and truncated outcomes." - -patterns-established: - - "Resource outcomes expose exactly resource/pages/fetched/mapped/written/status/durationMs/truncated/reasonCode." - - "Manual-sync timeout recovery reads only the authenticated subject's retained terminal and invalidates consumers only after committed workout work." - -requirements-completed: [GH-02, GH-03, GH-04, GH-05, GH-07, SYNC-01, SYNC-03, PERF-01] - -duration: 18min -completed: 2026-07-29 ---- - -# Phase 01 Plan 21: Google Sync Fairness and Durable Progress Summary - -**Workout-first Google Health sync with run-ID guarded progress, bounded redacted terminal outcomes, and post-commit cache visibility** - -## Performance - -- **Duration:** 18 min -- **Started:** 2026-07-29T12:45:37Z -- **Completed:** 2026-07-29T13:02:48Z -- **Tasks:** 3 -- **Files modified:** 11 - -## Accomplishments - -- Added the sole nullable `sync_progress` JSONB column and a race-safe current-run ledger that replaces older runs, rejects delayed writers, persists stale runs as interrupted, and bounds all retained facts. -- Moved workouts ahead of dense history, instrumented actual pagination/mapping/writes, preserved 786 successful pages as complete evidence, and prevented partial/write/rollup/truncated cycles from advancing the success watermark. -- Made committed workouts immediately visible through server cache invalidation and through browser workout, Dashboard snapshot, analytics, Google Health, and integration-status query invalidation. -- Added an authenticated subject-only status endpoint and card-side terminal polling after an uncertain POST response. - -## Task Commits - -Each task was committed atomically: - -1. **Task 1: Add the bounded race-safe current-run progress store** - `5cfcb69ef` (feat) -2. **Task 2: Orchestrate fair outcomes and invalidate server reads post-commit** - `5ec1a3836` (feat) -3. **Task 3: Return and render an honest manual-sync terminal result** - `ba6d06be3` (feat) -4. **Authorized verification fix: Type guarded progress mock calls** - `5bc213c5b` (test) - -## Files Created/Modified - -- `prisma/schema.prisma` - Maps the nullable Google connection progress JSON. -- `prisma/migrations/0287_google_health_sync_progress/migration.sql` - Adds only `sync_progress JSONB`. -- `src/lib/google-health/sync-progress.ts` - Sanitizes, serializes, guards, reads, and interrupts current-run progress. -- `src/lib/google-health/__tests__/sync-progress.test.ts` - Types the guarded-write mock tuple for full TypeScript verification. -- `src/lib/google-health/client.ts` - Records actual page/fetch facts and existing-cap truncation inside a resource scope. -- `src/lib/google-health/sync.ts` - Runs workout-first resources and returns/persists terminal resource outcomes. -- `src/lib/google-health/sync-core.ts` - Maps fetch/write/rollup failures to stable reason codes and holds failed cycles. -- `src/lib/google-health/sync-workout.ts` - Invalidates server consumers after successful workout persistence. -- `src/app/api/google-health/sync/route.ts` - Returns only sanitized bounded terminal facts. -- `src/app/api/google-health/sync/status/route.ts` - Reads only the authenticated subject's current run. -- `src/components/settings/integrations/google-health-card.tsx` - Recovers uncertain responses and invalidates committed workout consumers. - -## Decisions Made - -- Used a single retained JSONB envelope rather than history or shared audit/status storage, keeping ownership and cardinality explicit. -- Counted truncation only when an existing walker limit leaves a continuation token; no provider defect or new universal ceiling was inferred from Antonios's trace. -- Kept arrival emission separate from cache invalidation and performed both only after the relevant database write resolved. - -## Deviations from Plan - -### Auto-fixed Issues - -**1. [Rule 1 - Bug] Preserved the resolved Google sync verdict in the cohort adapter** - -- **Found during:** Task 2 (fair outcome orchestration) -- **Issue:** The legacy `.then(r => r.imported)` adapter discarded the richer resolved verdict contract and kept the shared Plan 14/22 truthfulness gate red. -- **Fix:** Replaced the chained projection with an explicit awaited result while retaining the legacy numeric cohort helper boundary. -- **Files modified:** `src/lib/google-health/sync.ts` -- **Verification:** Plan 14/22 focused cohort gate passes 52/52. -- **Committed in:** `5ec1a3836` - -**2. [Authorized scope extension - Test typing] Typed guarded-write mock calls** - -- **Found during:** Final verification -- **Issue:** The pre-seeded progress contract passed at runtime but its zero-argument `vi.fn` inference made guarded-write tuple inspection fail full TypeScript checking. -- **Fix:** Declared the mock argument tuple and centralized the runtime assertion that the guarded write exists before inspection. -- **Files modified:** `src/lib/google-health/__tests__/sync-progress.test.ts` -- **Verification:** Focused progress tests pass 4/4 and full `tsc --noEmit` passes. -- **Committed in:** `5bc213c5b` - ---- - -**Total deviations:** 2 (1 auto-fixed bug, 1 explicitly authorized test-only typing correction) -**Impact on plan:** Both fixes preserve behavior and keep production scope inside the declared Google Health files. - -## Issues Encountered - -- The initial workout cache import used a nonexistent module name; it was corrected to the existing centralized `@/lib/cache/invalidate` seam before Task 2 verification and commit. -- The pre-seeded progress contract initially inferred a zero-argument mock tuple; the explicitly authorized test-only typing correction resolved it without changing runtime behavior. - -## Verification - -- Prisma schema validation: passed. -- Focused Plan 21 unit/route/card suite: 35/35 passed. -- Fresh real-PostgreSQL progress and workout-terminal suites: 6/6 passed. -- Shared Plan 14/22 cohort outcome suite: 52/52 passed. -- Full TypeScript check: passed. -- `git diff --check`: passed. - -## User Setup Required - -None - no new external service configuration required. - -## Next Phase Readiness - -- Plans consuming Google manual-sync results can rely on bounded terminal DTOs and subject-only timeout recovery. -- GH-01 and GH-06 remain reporter/live-account evidence gated as required; this plan does not claim them complete. - -## Self-Check: PASSED - -- All 10 declared implementation files and the authorized test-only typing file exist. -- Task commits `5cfcb69ef`, `5ec1a3836`, `ba6d06be3`, and `5bc213c5b` exist. -- All plan-specific unit and real-PostgreSQL verification gates pass. - ---- -*Phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability* -*Completed: 2026-07-29* diff --git a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-22-SUMMARY.md b/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-22-SUMMARY.md deleted file mode 100644 index fe6ed4efc..000000000 --- a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-22-SUMMARY.md +++ /dev/null @@ -1,118 +0,0 @@ ---- -phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability -plan: "22" -subsystem: jobs -tags: [job-outcomes, cohort-folding, google-health, withings, privacy] - -requires: - - phase: 01-14 - provides: RED contracts for truthful cohort outcomes and bounded serialization -provides: - - Privacy-safe bounded JobOutcome serialization - - Provider-neutral complete/partial/failed/parked/skipped cohort folding - - Truthful Google Health and Withings scheduled-job counters -affects: [integration-reliability, job-observability, reminder-workers] - -tech-stack: - added: [] - patterns: - - Fixed allowlist serialization for durable job facts - - Typed per-user verdicts folded into bounded cohort counters - -key-files: - created: [] - modified: - - src/lib/jobs/job-outcome.ts - - src/lib/jobs/reminder/poll-cohort.ts - - src/lib/jobs/reminder/google-health-sync.ts - - src/lib/jobs/reminder/withings-sync.ts - -key-decisions: - - "Only complete users increment users_synced; partial, failed, parked, and skipped users retain distinct counters." - - "Only complete or partial writes count as useful, while a successful complete zero is counted separately." - - "Durable job facts accept only allowlisted bounded scalars and stable codes; raw causes remain process-local." - -patterns-established: - - "Cohort truth: classify every user once, then fold verdicts into a fixed 14-key aggregate." - - "Privacy boundary: reject unknown keys, nested values, unsafe counts, free-form strings, and oversized serialized outcomes." - -requirements-completed: [SYNC-02, JOB-01] - -duration: 15min -completed: 2026-07-29 ---- - -# Phase 01 Plan 22: Truthful Bounded Cohort Outcomes Summary - -**Privacy-safe job serialization and typed Google Health/Withings verdict folding distinguish useful, clean-zero, partial, failed, parked, and skipped work without false synced counts.** - -## Performance - -- **Duration:** 15 min -- **Started:** 2026-07-29T12:40:18Z -- **Completed:** 2026-07-29T12:54:55Z -- **Tasks:** 1 -- **Files modified:** 4 - -## Accomplishments - -- Added a fail-closed serializer with an explicit fact allowlist, stable reason codes, non-negative safe-integer counters, and a 2 KiB ceiling. -- Added a fixed-shape provider-neutral fold in which only complete users count as synced and only complete/partial writes count as useful. -- Wired Google Health and all Withings scheduled handlers to classify parked and post-sync reauth states without changing bounded fan-out, retry isolation, or reminder enqueue behavior. -- Removed per-user identifiers and raw thrown values from cohort warning messages. - -## Task Commits - -Each task was committed atomically: - -1. **Task 1: Aggregate truthful bounded cohort and job outcomes** - `6a3861122` (feat) -2. **Task 1 refactor: Reuse the shared Google cohort concurrency limit** - `c3ffc4333` (refactor) - -## Files Created/Modified - -- `src/lib/jobs/job-outcome.ts` - Validates and serializes bounded privacy-safe job facts. -- `src/lib/jobs/reminder/poll-cohort.ts` - Defines typed user verdicts and the fixed cohort aggregate. -- `src/lib/jobs/reminder/google-health-sync.ts` - Preserves bounded fan-out while folding rich Google sync results. -- `src/lib/jobs/reminder/withings-sync.ts` - Folds fallback, ECG, activity, and sleep results without unconditional synced counts. - -## Decisions Made - -- A user is synced only after a complete result, including a legitimate clean zero. -- A partial result may be useful when it wrote measurements, but it never increments `users_synced`. -- Reauth is checked both before and after provider work so token failures that park during a run do not masquerade as failures or clean zeros. -- Cohort-wide failures still throw for queue retry, while isolated user failures complete the cohort and remain visible in aggregate counters. -- Per-user reason codes are accepted as internal verdict detail but never serialized into the cohort result. - -## Deviations from Plan - -None - the implementation followed the planned bounded outcome and cohort-folding contract. - -## Issues Encountered - -- During isolated Plan 22 execution, the exact focused command initially - reported 51 passing tests and one static failure in the adjacent - `src/lib/google-health/sync.ts` adapter. Plan 21 subsequently replaced that - adapter in commit `5ec1a3836`; the combined Plan 14/22 gate now passes 52/52. -- The concurrent Plan 21 type errors were closed in `5bc213c5b`; the repository - TypeScript check now passes. -- Targeted ESLint, Prettier, and `git diff --check` pass for all four Plan 22 implementation files. - -## User Setup Required - -None - no external service configuration required. - -## Next Phase Readiness - -- The bounded outcome contract is ready for operator-surface persistence and display. -- The adjacent Google core adapter now retains its resolved verdict, and the - merged Plan 21/22 verification is green. - -## Self-Check: PASSED - -- All four declared implementation files exist. -- Task commits `6a3861122` and `c3ffc4333` exist in repository history. -- Scanner-owned package and workspace hashes remain unchanged on `release/v1.34.1`. - ---- -*Phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability* -*Completed: 2026-07-29* diff --git a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-23-SUMMARY.md b/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-23-SUMMARY.md deleted file mode 100644 index f6ff9685c..000000000 --- a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-23-SUMMARY.md +++ /dev/null @@ -1,171 +0,0 @@ ---- -phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability -plan: "23" -subsystem: internationalization -tags: [i18n, icu, passkeys, nightscout, apple-health, regression-tests] - -requires: - - phase: 01-07 - provides: Nightscout operator-owned private-origin policy - - phase: 01-08 - provides: Passkey existing-factor step-up flow - - phase: 01-16 - provides: Final score, documents and records UI cleanup - - phase: 01-17 - provides: Final settings and administration navigation contracts - - phase: 01-19 - provides: Apple Health ECG import outcome - - phase: 01-20 - provides: Assessment terminal-state contracts - - phase: 01-21 - provides: Google Health per-resource terminal outcomes - - phase: 01-22 - provides: Cohort outcome contracts - - phase: 01-26 - provides: Final workout pulse behavior -provides: - - Six-locale parity for final v1.34.1 security, integration, import and terminal-state copy - - Localized passkey step-up, Nightscout operator action and Apple ECG import outcomes - - Explicit regression contract for required, ICU-compatible and intentionally removed locale keys -affects: [settings, security, integrations, imports, insights, records, navigation] - -tech-stack: - added: [] - patterns: - - literal t() call-site inventory for newly introduced release copy - - targeted required/absent locale contracts with ICU placeholder parity - -key-files: - created: - - src/__tests__/v1341-locale-parity.test.ts - modified: - - messages/en.json - - messages/de.json - - messages/es.json - - messages/fr.json - - messages/it.json - - messages/pl.json - - src/components/settings/security-section/passkey-list-section.tsx - - src/components/settings/integrations/nightscout-card.tsx - - src/components/settings/import-panel/apple-health-import-card.tsx - - src/lib/__tests__/i18n-locale-integrity.test.ts - -key-decisions: - - "Only keys proven unreferenced after final implementation inventory were removed." - - "Temporary English literals were wired through explicit literal t() calls so repository reverse and call-site coverage can enforce them." - - "Apple ECG outcome counts use one ICU message with identical placeholders across all six locales." - -patterns-established: - - "Release locale reconciliation: inventory final call sites first, then serialize all catalog edits." - - "Obsolete-key tests pin intentional removals alongside required-key and placeholder parity." - -requirements-completed: [SEC-05, SEC-06, INS-01, INS-02, INS-03, INS-06, INS-07, INS-08, INS-09, HK-07, REC-01, REC-03, REC-04, NAV-01, GH-03, GH-04, GH-07, SYNC-02, JOB-01, AI-04] - -duration: 7min -completed: 2026-07-29 ---- - -# Phase 01 Plan 23: v1.34.1 Locale Reconciliation Summary - -**All final v1.34.1 security, Nightscout, Apple ECG and terminal-state contracts now resolve naturally in English, German, Spanish, French, Italian and Polish, with obsolete UI copy removed and guarded.** - -## Performance - -- **Duration:** 7 min -- **Started:** 2026-07-29T13:06:30+02:00 -- **Completed:** 2026-07-29T13:13:30+02:00 -- **Tasks:** 3 -- **Files modified:** 11 - -## Accomplishments - -- Added natural six-locale copy for passkey step-up verification, Nightscout operator approval and the complete Apple ECG import outcome. -- Replaced the last temporary English literals in those final components without changing behavior or layout. -- Removed only eight proven-obsolete keys: four superseded Health Score explanations, document-summary regeneration, lifestyle correction and the two user-controlled private-Nightscout override strings. -- Added a focused release guard for literal call sites, required keys, intentional absence and ICU placeholder equivalence. -- Reconciled the pre-existing Health Score integrity test with the intentionally retained transparency surface. - -## Task Commits - -Each task was committed atomically: - -1. **Task 1: Inventory final call sites and obsolete copy** - `b6dfe62c9` -2. **Authorized final call-site wiring** - `67eeefd58` -3. **Task 2: Reconcile English, German and Spanish** - `163aa73e0` -4. **Task 3: Complete French, Italian and Polish and all guards** - `b41b32d47` - -## Files Created/Modified - -- `src/__tests__/v1341-locale-parity.test.ts` - Pins literal final call sites, six-locale required/absent contracts and ICU placeholder parity. -- `messages/{en,de,es,fr,it,pl}.json` - Adds final localized contracts and removes the eight obsolete leaves with identical structure. -- `src/components/settings/security-section/passkey-list-section.tsx` - Translates the passkey existing-factor dialog. -- `src/components/settings/integrations/nightscout-card.tsx` - Translates the operator-owned private-origin action state. -- `src/components/settings/import-panel/apple-health-import-card.tsx` - Renders the ECG terminal summary through one ICU message. -- `src/lib/__tests__/i18n-locale-integrity.test.ts` - Pins only the Health Score transparency copy intentionally retained after UI cleanup. - -## Decisions Made - -- Retained all existing generic assessment, Google Health, cohort, records and navigation translations because their final call sites reuse valid established keys. -- Kept the Nightscout English wording already covered by the component regression and translated its meaning naturally in each other locale. -- Used one ECG sentence per locale rather than concatenated fragments so grammar and word order remain natural while all five counters retain exact ICU parity. -- Did not remove similarly named keys in other namespaces, such as Coach retry, recovery-code regeneration or record composition, because those remain referenced. - -## Deviations from Plan - -### Authorized Scope Extensions - -**1. Final call-site localization** - -- **Found during:** Task 1 final inventory -- **Issue:** Three completed implementation owners still contained temporary English literals, so catalog-only work could not satisfy the release contract. -- **Fix:** With explicit parent authorization, changed exactly the passkey, Nightscout and Apple import call-site files to use `t(...)`; behavior and layout were untouched. -- **Verification:** Literal call-site guard, affected component tests, ESLint, Prettier and TypeScript passed. -- **Committed in:** `67eeefd58` - -**2. Stale Health Score locale-integrity expectation** - -- **Found during:** Task 3 full locale integrity gate -- **Issue:** The existing integrity test still required three keys the final UI intentionally removed. -- **Fix:** With explicit parent authorization, updated only that key list to the retained Health Score transparency contracts. -- **Verification:** Full locale integrity and English-leak guards passed. -- **Committed in:** `b41b32d47` - ---- - -**Total deviations:** 2 authorized, narrowly scoped corrections. -**Impact on plan:** Both were necessary to make the planned final call-site inventory and repository gates truthful; neither changes product behavior, schema, packages or security boundaries. - -## Verification - -- **Plan regression cluster:** 9 files, 470 tests passed. -- **Locale integrity and English-leak guards:** 2 files, 30 tests passed. -- **JSON parse:** All six catalogs passed. -- **ESLint:** Passed for all owned and explicitly authorized TypeScript/TSX files. -- **Prettier:** Passed for all six catalogs, both locale tests and all three localized components. -- **TypeScript:** Repository-wide `pnpm exec tsc --noEmit` passed. -- **Diff check:** Passed for all Plan 01-23 files. - -## Issues Encountered - -- The first call-site assertion did not accept a formatted multiline `t(...)` call. The guard was corrected to permit whitespace after `t(` while still requiring a literal key. -- The Nightscout component regression pinned the original English operator wording. The English catalog retained that established wording; translated catalogs remain natural. - -## User Setup Required - -None. - -## Next Phase Readiness - -- The final v1.34.1 locale surface is synchronized across all six supported languages. -- Release-wide quality gates can now run without orphaned Score/UI copy or temporary English-only states from the completed call-site owners. -- No schema, migration, dependency, package or scanner file was changed by this plan. - -## Self-Check: PASSED - -- All 11 claimed source/test/catalog files exist. -- All 4 implementation/test commits exist. -- No owned Plan 01-23 file remains unstaged or uncommitted. - ---- -*Phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability* -*Completed: 2026-07-29* diff --git a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-26-SUMMARY.md b/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-26-SUMMARY.md deleted file mode 100644 index faa621230..000000000 --- a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-26-SUMMARY.md +++ /dev/null @@ -1,141 +0,0 @@ ---- -phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability -plan: "26" -subsystem: workout-canonicalization -tags: [workouts, whoop, apple-health, heart-rate, canonical-projection] - -requires: - - phase: 01-10 - provides: RED product/UI regression contracts - - phase: 01-15 - provides: App01 privacy-safe performance and production evidence -provides: - - Canonical Apple workout identity enriched with missing WHOOP HR aggregates and sanitized device zones - - Shared list/detail canonical projection without cross-user, cross-sport, cross-window or duplicate donor joins - - Honest aggregate pulse visibility independent of continuous workout samples -affects: [workout-list, workout-detail, whoop, apple-health, effort-zones] - -tech-stack: - added: [] - patterns: - - one-to-one nearest-donor matching inside the existing fixed canonical bucket - - allowlisted fill-missing-only enrichment with sanitized WHOOP zone keys - - aggregate pulse and continuous-series availability remain separate capabilities - -key-files: - created: [] - modified: - - src/lib/measurements/pick-canonical-workout-rows.ts - - src/lib/workouts/list-read.ts - - src/app/api/workouts/[id]/route.ts - - src/components/insights/workout-list.tsx - -key-decisions: - - "Canonical id, source, route, samples and existing values remain owned by the winning row." - - "Only missing avgHeartRate, maxHeartRate and validated WHOOP zone durations may cross the twin boundary." - - "A WHOOP aggregate never implies or fabricates a continuous heart-rate curve." - -patterns-established: - - "Donor safety: deterministic one-to-one assignment prevents one WHOOP record enriching multiple same-slot occurrences." - - "Metadata safety: copy only six known non-negative zone-duration keys and reject malformed known values." - -requirements-completed: [WHR-01, WHR-02, WHR-03, WHR-04, WHR-05] - -duration: 7min -completed: 2026-07-29 ---- - -# Phase 01 Plan 26: Workout Pulse Canonical Enrichment Summary - -**Apple remains the canonical workout while its missing pulse aggregates and device zones now surface from the exact matched WHOOP twin, without inventing a continuous curve.** - -## Performance - -- **Duration:** 7 min -- **Started:** 2026-07-29T12:53:55Z -- **Completed:** 2026-07-29T13:00:30Z -- **Tasks:** 3 -- **Files modified:** 9 - -## Accomplishments - -- Extended the active canonical picker with immutable, fill-missing-only WHOOP enrichment while preserving the winner's identity and all existing values. -- Added user identity to bucket isolation when available and deterministic one-to-one donor assignment so a donor cannot enrich two distinct same-slot occurrences. -- Sanitized WHOOP zones down to the six documented non-negative duration keys; arbitrary donor metadata and malformed zone payloads do not cross into the winner. -- Wired list and detail reads through the same enriched envelope. The detail still owns its original route, samples, metadata response and ownership check while its aggregate statistics and zone computation use the enriched projection. -- Added an average-pulse list value independent of the detailed-series glyph and pinned that aggregate HR/zones alone still produce no curve. - -## Task Commits - -Each task was committed atomically: - -1. **Task 1: Enrich only the matched canonical projection** - - `1cb0f7dd6` - RED positive and negative enrichment regressions - - `635e9b64c` - deterministic fill-missing-only projection - - `d2f6bf33e` - narrow WHOOP-zone sanitization and isolation coverage -2. **Task 2: Use the enriched envelope consistently in list and detail** - - `ed63a3bf3` - RED list/detail envelope regressions - - `43cce0801` - shared enriched list/detail reads -3. **Task 3: Render aggregate pulse honestly and pin the no-curve boundary** - - `89fefce7a` - RED aggregate-versus-series regressions - - `f6555d471` - aggregate average pulse in the workout list - -Supporting cleanup: - -- `104a05809` - repository formatting for the workout enrichment changes - -## Files Created/Modified - -- `src/lib/measurements/pick-canonical-workout-rows.ts` - Enriches winners from deterministic WHOOP twins using only validated allowlisted fields. -- `src/lib/measurements/__tests__/pick-canonical-workout-rows.test.ts` - Covers positive enrichment, non-overwrite, no mutation, sport/window/user isolation, malformed metadata and one-to-one donor use. -- `src/lib/workouts/list-read.ts` - Selects metadata for the shared canonical projection and returns enriched HR aggregates. -- `src/app/api/workouts/[id]/route.ts` - Computes detail aggregates and zones from the enriched canonical envelope while keeping the owned row's route/samples. -- `src/app/api/workouts/__tests__/canonical-dedup.test.ts` - Pins one enriched Apple list row for an Apple/WHOOP twin. -- `src/app/api/workouts/__tests__/detail.test.ts` - Pins Apple identity/route/metadata preservation, WHOOP HR/zones and non-overwrite behavior. -- `src/components/insights/workout-list.tsx` - Shows average pulse in bpm independently of continuous-series availability. -- `src/components/insights/__tests__/workout-list.test.tsx` - Separates aggregate visibility from the series glyph. -- `src/lib/workouts/__tests__/hr-series.test.ts` - Proves aggregates and zones cannot synthesize a curve. - -## Decisions Made - -- Kept the existing fixed five-minute/sport canonical boundary unchanged. Enrichment narrows behavior inside an already accepted twin bucket rather than widening dedup. -- Matched multiple winners and WHOOP donors by nearest start time with stable input-index tie-breaks and one-to-one consumption. -- Returned the original detail metadata envelope unchanged; sanitized donor zones feed only the established zone parser. This avoids leaking arbitrary WHOOP metadata into an Apple-owned API row. -- Kept `hasHrSeries` tied only to real `WorkoutSamples`; the new bpm label is the truthful aggregate-only signal. - -## Deviations from Plan - -None - implementation stayed within the nine declared files, plus this required summary. No schema, migration, sync, locale, package or dependency change was made. - -## Verification - -- **Focused regression cluster:** 6 files, 84 tests passed. -- **Picker regressions:** 21/21 passed, including cross-user and one-to-one donor isolation. -- **ESLint:** Passed for all nine owned source/test files. -- **Prettier:** Passed for all nine owned source/test files. -- **Diff check:** Passed. -- **TypeScript:** Repository-wide `pnpm typecheck` remains temporarily non-zero only in concurrent Google Health RED work (`sync/status/route` not yet landed and mock tuple diagnostics). No diagnostic references a Plan 01-26 file. - -## Issues Encountered - -- Concurrent Google Health implementation temporarily blocks the repository-wide typecheck. The failure set is confined to its status-route and sync-progress tests and was not modified here. - -## User Setup Required - -None. - -## Next Phase Readiness - -- WHR-01 through WHR-05 are closed with positive and negative regression coverage. -- The release can surface existing App01 WHOOP aggregate data immediately after deployment; historical data does not require a resync or migration. -- Continuous WHOOP curves remain intentionally unavailable because the public WHOOP API does not expose continuous heart-rate samples. - -## Self-Check: PASSED - -- All 9 claimed source/test files exist. -- All 8 implementation/supporting commits exist. -- No owned file remains unstaged or uncommitted. - ---- -*Phase: 01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability* -*Completed: 2026-07-29* diff --git a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-AUDIT-RECONCILIATION.md b/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-AUDIT-RECONCILIATION.md deleted file mode 100644 index 0a174a8e7..000000000 --- a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-AUDIT-RECONCILIATION.md +++ /dev/null @@ -1,558 +0,0 @@ -# v1.34.1 Audit Reconciliation Ledger - -**Reconciled:** 2026-07-29 - -**Sources:** phase research, static correctness audit, read-only live audit, and -performance/resilience audit. - -This ledger deduplicates overlapping observations. Confirmed findings remain -release work or explicit blockers; suspected findings remain hypotheses; cleared -findings preserve the attempted refutation so they cannot silently return to -scope. Evidence is limited to repository paths and redacted aggregate facts. - -## Status summary - -| Status | Meaning | -| --- | --- | -| confirmed | Evidence proves the behavior; implement or explicitly block release | -| suspected | Credible risk still needs the planned measurement or decision | -| blocked | External evidence is required before a resolution claim | -| cleared | The examined path refuted the proposed adjacent defect | -| refuted | The specific claim was disproved | - -## AUD-001 — Dashboard pending lead duplicates the headline score - -- **Source:** research:confirmed-dashboard; correctness:C8; performance:C7 -- **Status:** confirmed -- **Severity:** medium -- **Category:** presentation-correctness -- **Evidence:** `src/lib/daily/digest.ts` supplies a score-only fallback while `src/components/daily/today-hero.tsx` renders the score ring independently. -- **Affected path:** digest fallback → Dashboard hero lead → score ring -- **Attempted refutation:** Generated or signal-driven prose later replaces the fallback, but the cold state is deterministic and still duplicates the score. -- **Impact:** The initial Dashboard repeats one headline fact and looks unfinished despite complete data. -- **Requirement:** DASH-01 -- **Planned verification:** Plan 16 component contract for one numeric headline representation followed by Plan 24 merged re-audit. -- **Confidence:** high -- **Unresolved:** yes - -## AUD-002 — Stored document summaries expose a redundant regenerate action - -- **Source:** research:confirmed-document-action; correctness:K1 -- **Status:** confirmed -- **Severity:** low -- **Category:** product-grammar -- **Evidence:** The stored-summary branch in `src/components/documents/document-summary-block.tsx` renders the action even though opening and reading require no generation. -- **Affected path:** stored summary state → document detail UI -- **Attempted refutation:** First-generation and retry states still require backend capability; only the ready-state action is redundant. -- **Impact:** Users see an action that the accepted release brief explicitly removes. -- **Requirement:** DOC-01 -- **Planned verification:** Plan 16 ready-state component contract and Plan 24 re-audit. -- **Confidence:** high -- **Unresolved:** yes - -## AUD-003 — Health Score card contains rejected explanatory blocks - -- **Source:** research:confirmed-score-copy; correctness:K2 -- **Status:** confirmed -- **Severity:** low -- **Category:** presentation-density -- **Evidence:** `src/components/insights/health-score-card.tsx` separates method, composition, band, and algorithm copy from canonical report construction. -- **Affected path:** Health Score report → card presentation -- **Attempted refutation:** No competing calculation or API score was found; removing the blocks can remain presentation-only. -- **Impact:** The card is denser than the accepted design and obscures pillar details. -- **Requirement:** INS-01, INS-02, INS-03, INS-04 -- **Planned verification:** Plan 16 API parity plus one-score component contract; locale cleanup in Plan 23. -- **Confidence:** high -- **Unresolved:** yes - -## AUD-004 — Medication assessment prose uses the wrong visual emphasis - -- **Source:** research:confirmed-medication-tone -- **Status:** confirmed -- **Severity:** low -- **Category:** visual-consistency -- **Evidence:** `src/app/insights/medications/page.tsx` uses muted prose where established assessment bodies use foreground text. -- **Affected path:** medication assessment result → Insights card body -- **Attempted refutation:** Shared cards already use the intended token, limiting the defect to the concrete medication drift. -- **Impact:** Medication assessment text appears less important than equivalent assessment prose. -- **Requirement:** INS-06, INS-07 -- **Planned verification:** Plan 16 focused component assertion and Plan 24 screenshot re-audit. -- **Confidence:** high -- **Unresolved:** yes - -## AUD-005 — Empty Allergy and Family History managers duplicate create actions - -- **Source:** research:confirmed-empty-ctas; correctness:K3 -- **Status:** confirmed -- **Severity:** low -- **Category:** product-grammar -- **Evidence:** Both managers render an unconditional top action and an EmptyState action backed by the same create entry point. -- **Affected path:** empty records query → manager header and empty state -- **Attempted refutation:** The controls do not prove duplicate writes, but they are duplicate affordances in the same state. -- **Impact:** Empty screens present two equivalent primary actions. -- **Requirement:** REC-01, REC-03 -- **Planned verification:** Plan 16 empty/populated component contracts with one request per activation. -- **Confidence:** high -- **Unresolved:** yes - -## AUD-006 — Desktop Settings and Admin navigation starts below the heading - -- **Source:** research:confirmed-shell-layout; correctness:K4; performance:C8 -- **Status:** confirmed -- **Severity:** low -- **Category:** navigation-layout -- **Evidence:** Both shells place the heading in grid row one and navigation in row two with a separate sticky offset. -- **Affected path:** desktop shell grid → sticky navigation → section content -- **Attempted refutation:** Sticky positioning eventually pins the rail, but does not satisfy initial heading alignment or eliminate the visible travel. -- **Impact:** Navigation visibly drifts before pinning and Admin lacks the accepted grouping. -- **Requirement:** NAV-01, NAV-02, NAV-03, NAV-04, NAV-05 -- **Planned verification:** Plan 17 responsive shell contracts and Plan 24 browser re-audit. -- **Confidence:** high -- **Unresolved:** yes - -## AUD-007 — Reminder identity changes at the local-day boundary - -- **Source:** research:pitfall-day-key; correctness:C1 -- **Status:** confirmed -- **Severity:** critical -- **Category:** day-boundary -- **Evidence:** `src/lib/jobs/reminder/medication-reminder-check.ts` derives intake bounds, occurrence anchoring, and dedup keys from the worker tick day rather than the scheduled occurrence. -- **Affected path:** schedule recurrence → occurrence instant → intake attribution → reminder dedup -- **Attempted refutation:** Existing recurrence bands and a wider anchor lookup cannot match an identity whose local date changed before attribution. -- **Impact:** A completed late dose can repeat after midnight, while an overly broad repair could suppress the next real dose. -- **Requirement:** REM-01, REM-02, REM-03, REM-04 -- **Planned verification:** Plan 18 fake-time and persisted cross-midnight matrix across normal and daylight-saving transitions. -- **Confidence:** high -- **Unresolved:** yes - -## AUD-008 — Google resource diagnoses collapse before the manual-sync response - -- **Source:** research:confirmed-google-reasons; correctness:C5 -- **Status:** confirmed -- **Severity:** high -- **Category:** outcome-honesty -- **Evidence:** Resource failures are classified internally, but the aggregate route and failed card path retain only a generic failure envelope and stale status. -- **Affected path:** provider resource → sync ledger → route envelope → integration card -- **Attempted refutation:** The detailed ledger can appear after a later refetch, which does not make the immediate failed response actionable. -- **Impact:** Users cannot distinguish safe reason classes and are encouraged to retry blindly. -- **Requirement:** GH-03, GH-04, GH-06 -- **Planned verification:** Plan 21 bounded per-resource DTO tests and failed-card status invalidation. -- **Confidence:** high -- **Unresolved:** yes - -## AUD-009 — Safe deterministic Insights fallback is discarded - -- **Source:** research:confirmed-screened-fallback; correctness:C2; live:C2 -- **Status:** confirmed -- **Severity:** high -- **Category:** persistence -- **Evidence:** A safe fallback is produced, but the negative cache stub omits it and the shared card evaluates provider presence before available text. -- **Affected path:** outbound screen → deterministic fallback → status persistence → reader → card -- **Attempted refutation:** Retry-storm prevention and fail-closed screening are valid, but neither requires dropping already-safe text. -- **Impact:** A safe result becomes whitespace, indefinite preparation, or a false provider-setup prompt. -- **Requirement:** AI-01, AI-02, AI-03, AI-04 -- **Planned verification:** Plan 20 worker-to-reader-to-card matrix for screened, timeout, error, cached, and generated states. -- **Confidence:** high -- **Unresolved:** yes - -## AUD-010 — Reported Google full-sync failure lacks reproducible evidence - -- **Source:** research:open-google-report; live:S1 -- **Status:** blocked -- **Severity:** high -- **Category:** external-evidence -- **Evidence:** The audited instance had no connected Google cohort or retained manual-sync trace capable of reproducing the external report. -- **Affected path:** external reporter environment → manual full sync → user-visible result -- **Attempted refutation:** A clean zero-target local run cannot clear an issue that it cannot exercise. -- **Impact:** The release must not claim the reporter-specific failure resolved from adjacent fixes alone. -- **Requirement:** GH-01, GH-06 -- **Planned verification:** Plan 21 fixture coverage plus a redacted reporter response checkpoint before any specific resolution claim. -- **Confidence:** high -- **Unresolved:** yes - -## AUD-011 — Insights polling stops while the card remains preparing - -- **Source:** correctness:C3 -- **Status:** confirmed -- **Severity:** high -- **Category:** terminal-state -- **Evidence:** `src/hooks/use-insight-status.ts` stops scheduling after its ceiling without transforming the last preparing payload rendered by the card. -- **Affected path:** status GET loop → polling ceiling → shared card state -- **Attempted refutation:** Healthy workers usually replace the payload, but a negative stub can remain preparing with no work in flight. -- **Impact:** Bounded network work becomes an unbounded and misleading UI state. -- **Requirement:** AI-03, AI-04, AI-05 -- **Planned verification:** Plan 20 fake-timer contract requiring an honest terminal state after the final poll. -- **Confidence:** high -- **Unresolved:** yes - -## AUD-012 — Google writes can leave workout and score readers stale - -- **Source:** correctness:C4; performance:C1 -- **Status:** confirmed -- **Severity:** high -- **Category:** cache-freshness -- **Evidence:** Google workout and measurement writers omit the established server invalidators, while manual client invalidation omits the workout query family. -- **Affected path:** Google write → server projections → browser queries → Dashboard and workout views -- **Attempted refutation:** Cache expiry and remounts eventually heal views, but do not provide immediate post-success visibility across processes. -- **Impact:** A successful import can leave new or updated workouts and derived views visibly stale. -- **Requirement:** GH-02, GH-04, SYNC-03 -- **Planned verification:** Plan 21 insert/update cache-prewarm tests plus real-database workout visibility coverage. -- **Confidence:** high -- **Unresolved:** yes - -## AUD-013 — Resolved Google failures are counted as synced users - -- **Source:** correctness:C6; performance:C2 -- **Status:** confirmed -- **Severity:** high -- **Category:** silent-success -- **Evidence:** The default cohort adapter discards the returned failed verdict and increments success for every resolved promise. -- **Affected path:** per-user Google verdict → cohort counters → job outcome -- **Attempted refutation:** Isolating one user from cohort retries is valid, but does not justify classifying a failed verdict as synced. -- **Impact:** Operator counters can look green while user integrations fail or partially fail. -- **Requirement:** SYNC-02, JOB-01 -- **Planned verification:** Plan 22 mixed clean, resolved-failure, and thrown-failure cohort contract. -- **Confidence:** high -- **Unresolved:** yes - -## AUD-014 — Rollup failure does not change Google sync success - -- **Source:** correctness:C7 -- **Status:** confirmed -- **Severity:** medium -- **Category:** outcome-honesty -- **Evidence:** Full and incremental sync paths catch rollup recomputation failure after raw writes without changing the cycle verdict or success watermark. -- **Affected path:** raw measurement write → rollup recomputation → integration ledger → derived readers -- **Attempted refutation:** Raw rows were written, but derived readers consume materialized results, so raw write count alone is not a complete success verdict. -- **Impact:** The card can report success while Insights and derived views remain stale. -- **Requirement:** GH-04, GH-06, SYNC-03 -- **Planned verification:** Plan 21 forced rollup-failure tests requiring partial or failed outcome and no success watermark. -- **Confidence:** high -- **Unresolved:** yes - -## AUD-015 — Stamp-before-send can suppress a failed reminder phase - -- **Source:** correctness:S1 -- **Status:** suspected -- **Severity:** medium -- **Category:** delivery-policy -- **Evidence:** The worker persists its dedup anchor before dispatch and retains the anchor when dispatch fails. -- **Affected path:** reminder candidate → durable dedup anchor → channel dispatch → next tick -- **Attempted refutation:** The policy prevents notification floods and no live evidence ties it to the reported midnight defect. -- **Impact:** A transient dispatch failure can suppress that phase unless the product intentionally accepts at-most-once delivery. -- **Requirement:** OPS-01 -- **Planned verification:** Plan 24 explicit at-most-once versus durable-outbox decision; no point-release behavior change without that decision. -- **Confidence:** medium -- **Unresolved:** yes - -## AUD-016 — Recovery assessment visual mismatch is not statically proven - -- **Source:** correctness:S2 -- **Status:** suspected -- **Severity:** low -- **Category:** visual-consistency -- **Evidence:** Recovery mounts the same shared populated assessment component and foreground body token as established metric cards. -- **Affected path:** Recovery metric state → shared assessment card → themed rendering -- **Attempted refutation:** Shared code does not prove identical browser output across adjacent copy, themes, and responsive layouts. -- **Impact:** A speculative global CSS change could regress already-correct assessment states. -- **Requirement:** INS-06, INS-07 -- **Planned verification:** Plan 24 mobile and desktop screenshot matrix across loading, fallback, generated, light, and dark states. -- **Confidence:** medium -- **Unresolved:** yes - -## AUD-017 — Records preserve explicit revision and delete semantics - -- **Source:** correctness:K3-cleared -- **Status:** cleared -- **Severity:** info -- **Category:** records-semantics -- **Evidence:** Facts expose separate save, correction history, and confirmed removal paths; duplicate empty actions share one create entry point. -- **Affected path:** records editor → revision persistence → history and removal -- **Attempted refutation:** Static tracing found no silent overwrite or delete triggered by the requested label and conditional-rendering changes. -- **Impact:** No adjacent data-correctness defect is established. -- **Requirement:** REC-02, REC-04, REC-05 -- **Planned verification:** Plan 16 preserves revision history and one mutation per action; Plan 24 re-audit. -- **Confidence:** high -- **Unresolved:** no - -## AUD-018 — Navigation authorization currently fails closed - -- **Source:** correctness:K4-cleared -- **Status:** cleared -- **Severity:** info -- **Category:** authorization -- **Evidence:** Admin shell renders neither frame nor content until the role is authorized. -- **Affected path:** session role → Admin shell → navigation and content -- **Attempted refutation:** Static code clears an authorization leak, though it cannot clear the separate sticky-layout report. -- **Impact:** No adjacent role-boundary defect was found. -- **Requirement:** NAV-01, NAV-03 -- **Planned verification:** Plan 17 non-admin and admin browser contracts followed by Plan 24 security re-audit. -- **Confidence:** high -- **Unresolved:** no - -## AUD-019 — Written-plus-failed Google sync already renders partial - -- **Source:** correctness:K5-cleared -- **Status:** cleared -- **Severity:** info -- **Category:** outcome-honesty -- **Evidence:** The shared written-outcome classifier and route preserve a partial aggregate when some rows were written and a leg failed. -- **Affected path:** resource counts → aggregate outcome → manual-sync route -- **Attempted refutation:** Per-resource detail and zero-write diagnosis remain missing, but the existing aggregate does not paint written-plus-failed work as full success. -- **Impact:** The central partial classifier should be extended, not replaced. -- **Requirement:** GH-04 -- **Planned verification:** Plan 21 retains the partial contract while adding bounded per-resource detail. -- **Confidence:** high -- **Unresolved:** no - -## AUD-020 — Settings and Admin eager imports may inflate route bundles - -- **Source:** performance:M1 -- **Status:** suspected -- **Severity:** medium -- **Category:** bundle-size -- **Evidence:** Both section renderers eagerly import broad section maps; the current budget tool does not emit watched rows for these routes. -- **Affected path:** route module graph → client chunks → parse and hydration -- **Attempted refutation:** Older uncompressed figures include a large shared shell and are not current transfer bytes. -- **Impact:** Mobile and cold clients may download and parse unrelated section code. -- **Requirement:** PERF-01 -- **Planned verification:** Plan 24 must add or collect route-specific gzip evidence before any split; current aggregate baseline remains in `01-PERFORMANCE-BASELINE.md`. -- **Confidence:** medium -- **Unresolved:** yes - -## AUD-021 — Page ceilings can return silently truncated Google histories - -- **Source:** performance:C3 -- **Status:** confirmed -- **Severity:** high -- **Category:** pagination-completeness -- **Evidence:** Data-point and rollup walkers can return accumulated rows when their final allowed page still contains a continuation token. -- **Affected path:** provider pagination → accumulated resource → watermark and outcome -- **Attempted refutation:** Page caps prevent infinite walks and ordinary incremental windows are smaller, but neither proves completeness at the cap. -- **Impact:** Historical data can be omitted while the cycle appears complete and advances its watermark. -- **Requirement:** GH-05, GH-07 -- **Planned verification:** Plan 21 remaining-token-at-limit fixture requiring truncation status and no success watermark. -- **Confidence:** high -- **Unresolved:** yes - -## AUD-022 — Google full sync buffers and serializes large workout histories - -- **Source:** performance:C5 -- **Status:** suspected -- **Severity:** medium -- **Category:** sync-scalability -- **Evidence:** Resource orchestration, workout page accumulation, and per-row persistence are serial in the synchronous manual request. -- **Affected path:** manual sync request → provider pages → workout mapping → database writes -- **Attempted refutation:** Serial ordering protects quota and no live timing proves the request exceeds its route budget. -- **Impact:** Large histories may consume the request window before returning a terminal outcome. -- **Requirement:** PERF-01, GH-07 -- **Planned verification:** Plan 21 terminal workout fairness fixture and a database-backed statement-count benchmark before optimization. -- **Confidence:** medium -- **Unresolved:** yes - -## AUD-023 — Reminder tick is a global serial query-amplifying pass - -- **Source:** performance:C6 -- **Status:** suspected -- **Severity:** medium -- **Category:** worker-scalability -- **Evidence:** The worker loads an unpaged medication cohort, processes it serially, and drains cleanup backlog before due-dose evaluation. -- **Affected path:** cleanup queue → medication cohort → per-slot queries → dispatch -- **Attempted refutation:** Local concurrency and dedup prevent overlap and repeats, but do not cap pass duration as cohorts grow. -- **Impact:** Due reminders may arrive late if a pass approaches its fifteen-minute cadence. -- **Requirement:** PERF-01 -- **Planned verification:** Use the bounded cohort baseline plus a future database/network-stub p95 and query-budget test before structural changes. -- **Confidence:** medium -- **Unresolved:** yes - -## AUD-024 — Dense Health Score reads may scale with sample density - -- **Source:** performance:S1 -- **Status:** suspected -- **Severity:** medium -- **Category:** score-scalability -- **Evidence:** The score reader fetches time-bounded but unbounded-row raw datasets to preserve local-day and source canonicalization. -- **Affected path:** raw measurements → canonicalization → Health Score → Dashboard -- **Attempted refutation:** Parallel queries, sparse domains, and caches bound common cases; only a dense cold user presents the risk. -- **Impact:** A cold score rebuild may allocate and process many rows for a power user. -- **Requirement:** PERF-01 -- **Planned verification:** Use the dense local baseline, then require database row counts, heap, cold p95, and exact production score parity before optimization. -- **Confidence:** medium -- **Unresolved:** yes - -## AUD-025 — Timed-out digest prefetch may duplicate bounded assembly work - -- **Source:** performance:S2 -- **Status:** suspected -- **Severity:** low -- **Category:** request-amplification -- **Evidence:** A soft-timeout loser is not cancelled and can continue while the client later requests the digest. -- **Affected path:** Dashboard server prefetch → soft timeout → client digest request -- **Attempted refutation:** Expensive snapshot work is single-flight and the remaining duplicated assembly is bounded. -- **Impact:** A cold navigation may perform small duplicate work without losing content. -- **Requirement:** PERF-01 -- **Planned verification:** Plan 24 trace of one cold timeout navigation; add cancellation only if duplicate builders survive cache coalescing. -- **Confidence:** low -- **Unresolved:** yes - -## AUD-026 — Comprehensive Insights screening follows multi-minute generation - -- **Source:** live:C1 -- **Status:** confirmed -- **Severity:** high -- **Category:** generated-content-latency -- **Evidence:** Redacted aggregate logs show repeated long comprehensive generation attempts ending in fail-closed causal-claim screening. -- **Affected path:** manual generation → provider response → outbound screen → client outcome -- **Attempted refutation:** Screening must remain fail-closed and unrelated request paths did not show a general outage. -- **Impact:** A refresh can occupy the UI for minutes and still produce no fresh provider prose. -- **Requirement:** AI-02, AI-03, AI-04 -- **Planned verification:** Plan 20 safe rewrite or deterministic fallback tests with bounded retries and explicit visible outcome. -- **Confidence:** high -- **Unresolved:** yes - -## AUD-027 — Status generation is degraded but safe fallback selection survives - -- **Source:** live:C2-provider-chain -- **Status:** confirmed -- **Severity:** high -- **Category:** provider-resilience -- **Evidence:** Redacted aggregate logs contain repeated provider-error and screened fallback events while some provider rows later recovered. -- **Affected path:** status pregeneration → provider chain → screen → fallback -- **Attempted refutation:** The provider fleet was not totally unavailable and fallback selection itself was observable. -- **Impact:** Assessments can arrive late or remain deterministic for repeated cycles. -- **Requirement:** AI-02, AI-04 -- **Planned verification:** Plan 20 preserves fail-closed fallback behavior and Plan 24 compares post-change reason counters. -- **Confidence:** high -- **Unresolved:** yes - -## AUD-028 — Parked Withings targets are counted as synced - -- **Source:** live:C3 -- **Status:** confirmed -- **Severity:** medium -- **Category:** silent-success -- **Evidence:** A durable parked integration leg remained stale while hourly cohort aggregates counted every target as synced. -- **Affected path:** integration state → cohort polling → job counters → operator view -- **Attempted refutation:** Zero new rows can be healthy, but a persistent parked state and repeated warnings refute all-targets-healthy. -- **Impact:** Stale data can coexist with green cohort counters and delayed intervention. -- **Requirement:** SYNC-02, JOB-01 -- **Planned verification:** Plan 22 mixed healthy, parked, failed, and zero-new-data cohort contracts. -- **Confidence:** high -- **Unresolved:** yes - -## AUD-029 — Completed job rows contain no bounded outcome facts - -- **Source:** live:C4 -- **Status:** confirmed -- **Severity:** medium -- **Category:** outcome-honesty -- **Evidence:** Sampled completed operational queues had empty durable output, including zero-target and useful-work cases. -- **Affected path:** job handler result → durable queue row → Admin diagnosis -- **Attempted refutation:** Wide events sometimes carry counters and terminal-failure storage works, but completed rows still cannot distinguish work from no-op. -- **Impact:** Green completion cannot prove targets, writes, skips, failures, or fallbacks. -- **Requirement:** JOB-01 -- **Planned verification:** Plan 22 scalar-only durable outcome digest with zero-target, partial, failed, skipped, and successful cases. -- **Confidence:** high -- **Unresolved:** yes - -## AUD-030 — Measurement planner statistics and index footprint need maintenance review - -- **Source:** live:C5 -- **Status:** confirmed -- **Severity:** medium -- **Category:** operations -- **Evidence:** Redacted database aggregates show severely divergent planner estimates and a large index footprint without a current route outage. -- **Affected path:** measurement table statistics → query plans → read and write cost -- **Attempted refutation:** Current representative routes remain healthy and storage has headroom, so no incident or emergency rewrite is proven. -- **Impact:** Stale cardinality estimates can select poor plans as data changes. -- **Requirement:** OPS-01 -- **Planned verification:** Plan 24 maintenance-window decision using representative plans before ordinary analyze or vacuum; no deploy-time storage rewrite. -- **Confidence:** high -- **Unresolved:** yes - -## AUD-031 — Shared-host peaks are not attributable to HealthLog - -- **Source:** live:S2 -- **Status:** suspected -- **Severity:** low -- **Category:** host-pressure -- **Evidence:** Aggregate host peaks coincided with isolated slow health checks, while the application had no restart, memory kill, or sustained common-path slowdown. -- **Affected path:** shared host scheduling → application event loop → request latency -- **Attempted refutation:** Other workloads share the host and current application resource use was materially calmer. -- **Impact:** Isolated slow reads are possible, but no HealthLog regression is established. -- **Requirement:** AUD-03, OPS-01 -- **Planned verification:** Correlate future slow events with per-container metrics and event-loop lag before changing application code. -- **Confidence:** low -- **Unresolved:** yes - -## AUD-032 — Status transport errors can masquerade as no provider - -- **Source:** performance:C4; live:S3 -- **Status:** confirmed -- **Severity:** medium -- **Category:** terminal-state -- **Evidence:** Status hooks stop after transport failure and consumers omit the error state, defaulting absent data to no-provider semantics. -- **Affected path:** status transport → query hook → shared card -- **Attempted refutation:** Successful preparing responses poll correctly; the defect is specific to timeout and route-error transport. -- **Impact:** A failed read can show a false configuration prompt or hide safe fallback text. -- **Requirement:** AI-03, AI-04, AI-05 -- **Planned verification:** Plan 20 aborted and server-error hook/card matrix with bounded retry and honest terminal rendering. -- **Confidence:** high -- **Unresolved:** yes - -## AUD-033 — Runtime availability showed no general outage - -- **Source:** live:cleared-runtime -- **Status:** cleared -- **Severity:** info -- **Category:** runtime-health -- **Evidence:** Read-only health, restart, and resource aggregates showed healthy application and database containers over the inspected window. -- **Affected path:** runtime containers → health endpoint → operator availability -- **Attempted refutation:** The audit checked restart, memory-kill, current resource, and full-window signals rather than one status response alone. -- **Impact:** No general outage explains the targeted integration and Insights symptoms. -- **Requirement:** AUD-03 -- **Planned verification:** Plan 24 repeats health checks after the integrated build and Plan 25 records release evidence. -- **Confidence:** high -- **Unresolved:** no - -## AUD-034 — Migration state showed no schema drift - -- **Source:** live:cleared-migrations -- **Status:** cleared -- **Severity:** info -- **Category:** schema-health -- **Evidence:** Repository and database migration aggregates matched with no unfinished or rolled-back migration. -- **Affected path:** migration files → deployment ledger → runtime schema -- **Attempted refutation:** The audit compared counts and latest names rather than relying on application startup alone. -- **Impact:** Schema drift is not an evidenced cause of the reported symptoms. -- **Requirement:** AUD-03 -- **Planned verification:** Preserve the normal migration gate and repeat it after any Plan 21 schema addition. -- **Confidence:** high -- **Unresolved:** no - -## AUD-035 — No unrelated server-error or queue-lock cluster was found - -- **Source:** live:cleared-errors-and-locks -- **Status:** cleared -- **Severity:** info -- **Category:** runtime-health -- **Evidence:** Redacted status aggregates found no server-error cluster outside screened generation, no exhausted queue retries, and no database lock incident. -- **Affected path:** HTTP actions and background queues → database concurrency → operator signals -- **Attempted refutation:** Queue state and database activity were checked directly in addition to structured logs. -- **Impact:** The audit does not support broad runtime or database remediation. -- **Requirement:** AUD-03, AUD-04 -- **Planned verification:** Plan 24 merged re-audit with durable outcome facts, followed by Plan 25 release gate. -- **Confidence:** high -- **Unresolved:** no - -## Reconciliation conclusions - -- Confirmed findings map to an implementation plan or remain an explicit release - blocker. No confirmed defect is silently downgraded to a hypothesis. -- The reporter-specific Google failure remains externally blocked even though - adjacent cache, pagination, and outcome defects are independently confirmed. -- Performance entries preserve the difference between local measurements and - production behavior. No optimization or dependency change is authorized by - this ledger. -- Cleared findings remain recorded so future re-audits can distinguish a - regression from an already-refuted adjacent theory. diff --git a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-DEPENDENCY-AUDIT.md b/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-DEPENDENCY-AUDIT.md deleted file mode 100644 index ef8018a46..000000000 --- a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-DEPENDENCY-AUDIT.md +++ /dev/null @@ -1,136 +0,0 @@ -# v1.34.1 dated dependency audit - -## Decision - -**PASS for the locked v1.34.1 no-update scope.** The 2026-07-29 audit has no -production-reachable High or unresolved Medium advisory outside the explicitly -accepted dependency deferral. No dependency, lockfile, workspace setting, or -installed package was changed. - -This is not a claim that the dependency graph is advisory-free. The captured -registry response contains 19 advisory entries: 9 High, 9 Moderate, 1 Low. -Fourteen are additional build, lint, test, or development-tool transitives. The -other five are exactly the locked `hono`, `@hono/node-server`, and `valibot` -Moderate deferral. All remain open for the post-v1.34.1 dependency follow-up. - -## Immutable capture - -| Field | Value | -| --- | --- | -| Captured UTC | `2026-07-29T13:15:08Z` | -| Captured local | `2026-07-29T15:15:08+0200` | -| Command | `pnpm audit --json` | -| pnpm | `11.15.1` | -| Registry/database | `https://registry.npmjs.org/`; advisory records link to the GitHub Advisory Database. pnpm JSON exposes no immutable database revision. | -| Exit code | `1` (advisories present) | -| External directory | `/tmp/healthlog-pnpm-audit-20260729.Epa5Zn` | -| Raw JSON | `/tmp/healthlog-pnpm-audit-20260729.Epa5Zn/pnpm-audit.json` | -| Raw JSON SHA-256 | `a49c2ef9ff9d1511822ffe166a0b643dadcdf22d39c349da8a727eb638194d8e` | -| Raw JSON size | `74,810` bytes | -| Dependency metadata | 541 dependencies, 824 dev dependencies, 282 optional dependencies, 1,449 total | - -Raw audit material remains outside the repository and contains no application -secret. The external path is ephemeral and is recorded for this execution -session rather than treated as a durable release artifact. - -## Exact advisory snapshot - -“Roots” are the first dependency after the workspace root across every exact -path in the raw JSON. “Paths” is the number of unique full dependency paths. -The raw capture is authoritative for every unabridged chain. - -| Advisory | Package | Severity | Installed | Registry fixed range | Roots | Paths | -| --- | --- | --- | --- | --- | --- | ---: | -| GHSA-23c5-xmqv-rm74 | `minimatch` | High | `3.1.3` | `>=3.1.4` | `eslint`, `eslint-config-next` | 78 | -| GHSA-25h7-pfq9-p65f | `flatted` | High | `3.3.3` | `>=3.4.0` | `eslint`, `eslint-config-next` | 26 | -| GHSA-rf6f-7fwh-wjgh | `flatted` | High | `3.3.3` | `>=3.4.2` | `eslint`, `eslint-config-next` | 26 | -| GHSA-f886-m6hf-6m8v | `brace-expansion` | Moderate | `1.1.12` | `>=1.1.13` | `eslint`, `eslint-config-next` | 78 | -| GHSA-g7r4-m6w7-qqqr | `esbuild` | Low | `0.27.7` | `>=0.28.1` | `@vitejs/plugin-react`, `@vitest/coverage-v8`, `vitest` | 5 | -| GHSA-h67p-54hq-rp68 | `js-yaml` | Moderate | `4.1.1` | `>=4.1.2` | `eslint`, `eslint-config-next` | 26 | -| GHSA-96hv-2xvq-fx4p | `ws` | High | `7.5.10` | `>=7.5.11` | `@next/bundle-analyzer` | 1 | -| GHSA-f38q-mgvj-vph7 | `protobufjs` | Moderate | `7.6.2` | `>=7.6.3` | `@testcontainers/postgresql`, `testcontainers` | 6 | -| GHSA-3jxr-9vmj-r5cp (ID 1123896) | `brace-expansion` | High | `2.1.1` | `>=2.1.2` | `@testcontainers/postgresql`, `testcontainers` | 2 | -| GHSA-3jxr-9vmj-r5cp (ID 1123897) | `brace-expansion` | High | `1.1.12`, `1.1.14` | `>=1.1.16` | `eslint`, `eslint-config-next` | 82 | -| GHSA-52cp-r559-cp3m | `js-yaml` | High | `4.1.1` | `>=4.3.0` | `eslint`, `eslint-config-next` | 26 | -| GHSA-j3f2-48v5-ccww | `protobufjs` | Moderate | `7.6.2` | `>=7.6.5` | `@testcontainers/postgresql`, `testcontainers` | 6 | -| GHSA-xgm2-5f3f-mvvc | `hono` | Moderate | `4.12.25` | `>=4.12.27` | `@modelcontextprotocol/sdk`, `@prisma/client`, `prisma`, `shadcn` | 8 | -| GHSA-frvp-7c67-39w9 | `@hono/node-server` | Moderate | `1.19.14` | `>=2.0.5` | `@modelcontextprotocol/sdk`, `@prisma/client`, `prisma`, `shadcn` | 4 | -| GHSA-hvrm-45r6-mjfj | `hono` | Moderate | `4.12.25` | `>=4.12.27` | `@modelcontextprotocol/sdk`, `@prisma/client`, `prisma`, `shadcn` | 8 | -| GHSA-w62v-xxxg-mg59 | `hono` | Moderate | `4.12.25` | `>=4.12.27` | `@modelcontextprotocol/sdk`, `@prisma/client`, `prisma`, `shadcn` | 8 | -| GHSA-r28c-9q8g-f849 | `postcss` | High | `8.5.14` | `>=8.5.18` | `@tailwindcss/postcss` | 1 | -| GHSA-5qjj-4xww-7phc | `valibot` | Moderate | `1.2.0` | `>=1.4.2` | `@prisma/client`, `prisma` | 2 | -| GHSA-mh99-v99m-4gvg | `brace-expansion` | High | `1.1.12`, `1.1.14`, `2.1.1`, `2.1.2`, `5.0.7` | `>=5.0.8` | `@testcontainers/postgresql`, `eslint`, `eslint-config-next`, `shadcn`, `testcontainers` | 100 | - -Representative full paths, with all variants retained in the raw JSON: - -- lint: `. > eslint > minimatch` and - `. > eslint-config-next > ... > eslint > ...` -- test: `. > testcontainers > dockerode > protobufjs` and - `. > testcontainers > archiver > ... > brace-expansion` -- build/analyse: `. > @tailwindcss/postcss > postcss` and - `. > @next/bundle-analyzer > ws` -- locked Hono family: `. > @modelcontextprotocol/sdk > hono`, - `. > @modelcontextprotocol/sdk > @hono/node-server`, and the - `prisma > @prisma/dev` / `shadcn > @modelcontextprotocol/sdk` variants -- locked Valibot: `. > prisma > @prisma/dev > valibot` and the same chain - reached through `@prisma/client > prisma` - -## Delta from the older five-Moderate note - -The PRD's five-entry description remains exact for the named deferral: - -1. three `hono` Moderate advisories: - GHSA-xgm2-5f3f-mvvc, GHSA-hvrm-45r6-mjfj, and - GHSA-w62v-xxxg-mg59; -2. one `@hono/node-server` Moderate advisory: - GHSA-frvp-7c67-39w9; -3. one `valibot` Moderate advisory: - GHSA-5qjj-4xww-7phc. - -The dated registry snapshot is broader than that older shorthand by 14 entries: -9 High, 4 Moderate, and 1 Low across lint, build/analyse, Vitest/Vite, and -Testcontainers transitives. This is the expected date-sensitive delta already -anticipated by `01-SECURITY-RESEARCH.md`; it must not be collapsed back to -“five total advisories.” - -## Reachability and release treatment - -- The additional `minimatch`, `flatted`, `brace-expansion`, and `js-yaml` - paths are ESLint/configuration tooling. Their ReDoS, recursion, expansion, - and YAML-complexity attack preconditions do not accept application runtime - input. -- `protobufjs` and its `brace-expansion` paths are under Testcontainers; - `ws` is under the bundle analyser; `esbuild` is under Vite/Vitest tooling. - These are test/development paths, not deployed HealthLog request handlers. -- `postcss` is a build-time Tailwind path. The repository does not compile - attacker-supplied source maps at runtime. -- The accepted Hono advisories concern adapters/utilities not used by the - application imports (`API Gateway v1`, Hono JSX context/`cx()`, and the - Node-server Windows `serve-static` path). HealthLog's MCP code imports the - MCP SDK's server/transport interfaces, not those vulnerable sinks. -- `valibot` is reached through Prisma's development tool graph, not a - HealthLog runtime validation path. -- Availability-only dependency issues and third-party dependency internals are - outside the reportable application-security scope in `SECURITY.md`, but the - advisories remain tracked rather than called fixed. - -Therefore the dated expansion introduces no production-reachable High or -unresolved Medium release blocker. The explicit v1.34.1 decision remains: -accept/document the five named Moderate entries and do not mutate dependency -state in this release. Review all 19 entries together in the post-release -dependency update so an override does not silently fix one path while leaving -sibling paths unresolved. - -## No-change proof - -The three dependency working files still match the execution baseline: - -| File | SHA-256 | -| --- | --- | -| `package.json` | `7a6dbc488beaf210808bf959b84717be462781f2aca74a5fbc157699d7b3668a` | -| `pnpm-lock.yaml` | `d9fa8c99e86eb4977f55395c43ca2c1781de26400870003c783e7112405970d6` | -| `pnpm-workspace.yaml` | `979d04c351ba838e6c8da97aa34545c9a76513710f86d84f6c07d1289d0cd054` | - -Their binary Git patch SHA-256 remains -`b1206decc31f09b621676ac558919caf23a2190869d948fbc585fa3f38170f95`. -No install, update, override change, audit fix, or lockfile normalization ran. diff --git a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-MERGED-REAUDIT.md b/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-MERGED-REAUDIT.md deleted file mode 100644 index ea694ee15..000000000 --- a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-MERGED-REAUDIT.md +++ /dev/null @@ -1,636 +0,0 @@ -# v1.34.1 merged cross-domain re-audit - -**Re-audited:** 2026-07-29 - -**Tree:** merged `release/v1.34.1` working tree - -**Decision:** BLOCK - -The merged implementation was traced from writer through durable state, reader, -API and card/operator boundary. The re-audit also reran 21 focused unit and -component files with 251 passing tests and eight real-PostgreSQL files with 42 -passing tests. - -Two findings block release: - -- AUD-010 remains externally evidence-gated; this tree cannot honestly claim - the reporter-specific Google full-sync failure resolved. -- AUD-029 remains an implementation gap: successful bounded job outcomes are - constructed and validated but discarded before pg-boss persistence. - -The residual set is exact: - -- AUD-029: successful job outcomes are not durably persisted, Medium and - release-blocking; -- Nightscout token warning logs: accepted Low scanner residual, unchanged; -- PWA post-logout offline asset refetch: accepted Low privacy-first behavior; - and -- AUD-030: stale planner statistics, Medium controlled-operations residual. - -AUD-010 is a separate reporter-evidence checkpoint, not an adopted residual. -No new High finding was discovered. - -## AUD-001 — Dashboard pending lead no longer duplicates the headline score - -- **Source:** research:merged-001; correctness:dashboard-lead -- **Status:** cleared -- **Severity:** medium -- **Category:** presentation-correctness -- **Evidence:** `src/lib/daily/digest.ts` and `src/components/daily/today-hero.tsx` now preserve one numeric headline representation, covered by the merged Dashboard contract. -- **Affected path:** digest fallback → Dashboard hero lead → score ring -- **Attempted refutation:** Cold fallback was rechecked because generated prose can replace it later; the deterministic cold state no longer repeats the score. -- **Impact:** The former unfinished-looking duplicate headline is absent. -- **Requirement:** DASH-01 -- **Planned verification:** Re-audit accepted the Plan 16 component contract; the full browser artifact remains part of the release gate. -- **Confidence:** high -- **Unresolved:** no -- **Closure:** Closed in the merged tree. - -## AUD-002 — Stored document summary action is state-correct - -- **Source:** research:merged-002; correctness:document-ready -- **Status:** cleared -- **Severity:** low -- **Category:** product-grammar -- **Evidence:** `src/components/documents/document-summary-block.tsx` limits generation actions to states that require generation or retry. -- **Affected path:** stored summary state → document detail action -- **Attempted refutation:** First-generation and retry states were retained as positive controls; only ready-state regeneration disappeared. -- **Impact:** A readable stored summary no longer presents a redundant primary action. -- **Requirement:** DOC-01 -- **Planned verification:** Plan 16 ready and retry component contracts were reviewed in the merged tree. -- **Confidence:** high -- **Unresolved:** no -- **Closure:** Closed. - -## AUD-003 — Health Score presentation uses the accepted density - -- **Source:** research:merged-003; correctness:score-density -- **Status:** cleared -- **Severity:** low -- **Category:** presentation-density -- **Evidence:** `src/components/insights/health-score-card.tsx` no longer renders the rejected method, composition, band and algorithm blocks as competing card sections. -- **Affected path:** Health Score report → card presentation -- **Attempted refutation:** The retained transparency surface and canonical score construction were checked so presentation cleanup did not remove score provenance. -- **Impact:** Pillar detail is no longer obscured by duplicate explanatory blocks. -- **Requirement:** INS-01, INS-02, INS-03, INS-04 -- **Planned verification:** Merged Plan 16 contracts and Plan 23 locale integrity coverage pass. -- **Confidence:** high -- **Unresolved:** no -- **Closure:** Closed. - -## AUD-004 — Medication assessment prose uses shared emphasis - -- **Source:** research:merged-004; correctness:medication-tone -- **Status:** cleared -- **Severity:** low -- **Category:** visual-consistency -- **Evidence:** The medication assessment renders through the shared foreground assessment presentation rather than a muted one-off body. -- **Affected path:** medication assessment result → Insights card body -- **Attempted refutation:** Adjacent assessment cards were compared to avoid a global token change; the fix remains local to the intended shared contract. -- **Impact:** Medication assessment prose has the same hierarchy as equivalent assessments. -- **Requirement:** INS-06, INS-07 -- **Planned verification:** The shared card regression is included in the 251-test re-audit run. -- **Confidence:** high -- **Unresolved:** no -- **Closure:** Closed. - -## AUD-005 — Empty record managers expose one create action - -- **Source:** research:merged-005; correctness:records-empty -- **Status:** cleared -- **Severity:** low -- **Category:** product-grammar -- **Evidence:** Allergy and Family History managers condition the header action so the empty state owns the single create entry point. -- **Affected path:** empty records query → manager header and empty state -- **Attempted refutation:** Populated states retain their header action and mutation path; only duplicate empty affordances were removed. -- **Impact:** Empty records screens no longer present two equivalent primary actions. -- **Requirement:** REC-01, REC-03 -- **Planned verification:** Plan 16 empty and populated component contracts were reviewed. -- **Confidence:** high -- **Unresolved:** no -- **Closure:** Closed. - -## AUD-006 — Desktop navigation aligns and remains pinned - -- **Source:** research:merged-006; performance:navigation-shell -- **Status:** cleared -- **Severity:** low -- **Category:** navigation-layout -- **Evidence:** Settings and Admin shells place heading, rail and content in the accepted desktop grid while retaining the mobile navigation branch. -- **Affected path:** desktop shell grid → sticky navigation → section content -- **Attempted refutation:** Sticky behavior alone was not accepted; initial heading alignment and scroll pinning are both represented in the responsive contracts. -- **Impact:** The navigation rail no longer visibly travels before pinning. -- **Requirement:** NAV-01, NAV-02, NAV-03, NAV-04, NAV-05 -- **Planned verification:** Plan 17 responsive and authorization contracts pass; deterministic release screenshots remain a separate gate. -- **Confidence:** high -- **Unresolved:** no -- **Closure:** Closed. - -## AUD-007 — Medication occurrence identity survives the local-day boundary - -- **Source:** correctness:merged-007; research:day-key -- **Status:** cleared -- **Severity:** critical -- **Category:** day-boundary -- **Evidence:** `medication-reminder-check.ts`, recurrence projection and list reads carry schedule ID plus exact scheduled instant; dedup and intake lookup use the occurrence local date. -- **Affected path:** recurrence → occurrence instant → intake attribution → reminder dedup → all medication consumers -- **Attempted refutation:** Tests include ordinary midnight, a daylight-saving boundary, positive-offset time, exact taken, skipped, auto-missed, sibling schedule, replacement era and retry controls. -- **Impact:** A late completed dose does not repeat after midnight and the next distinct dose remains actionable. -- **Requirement:** REM-01, REM-02, REM-03, REM-04 -- **Planned verification:** Two real-PostgreSQL medication files passed again in the 42-test boundary run. -- **Confidence:** high -- **Unresolved:** no -- **Closure:** Code path closed; reporter confirmation remains a release-process follow-up rather than a reproduced defect. - -## AUD-008 — Google resource diagnoses reach the terminal user outcome - -- **Source:** correctness:merged-008; research:google-reasons -- **Status:** cleared -- **Severity:** high -- **Category:** outcome-honesty -- **Evidence:** `sync.ts`, the manual-sync route, sync-status route and Google card carry bounded resource status and stable reason codes through complete, partial, truncated, failed and interrupted states. -- **Affected path:** provider resource → progress ledger → route envelope → integration card -- **Attempted refutation:** Generic failure remains the fallback for unknown errors, but known resource reasons are preserved and raw provider errors are excluded. -- **Impact:** Users receive an honest bounded diagnosis instead of a generic successful-looking retry prompt. -- **Requirement:** GH-03, GH-04, GH-06 -- **Planned verification:** Google route, status and card files passed in the 251-test re-audit run. -- **Confidence:** high -- **Unresolved:** no -- **Closure:** Closed for fixture-proven reason classes. - -## AUD-009 — Safe deterministic Insights fallback persists and renders - -- **Source:** correctness:merged-009; live:fallback-selection -- **Status:** cleared -- **Severity:** high -- **Category:** persistence -- **Evidence:** `status-shared.ts`, `status-cache.ts`, the hook and shared card treat safe text as the primary state and provider presence only as provenance metadata. -- **Affected path:** outbound screen → deterministic fallback → cache → reader → card -- **Attempted refutation:** Provider-setup, retry and negative-cache states remain available when no safe text exists; screened prose itself is never persisted. -- **Impact:** A safe assessment no longer becomes whitespace or a false provider-setup prompt. -- **Requirement:** AI-01, AI-02, AI-03, AI-04 -- **Planned verification:** Screen, cache, hook and card regressions passed in the 251-test run. -- **Confidence:** high -- **Unresolved:** no -- **Closure:** Closed. - -## AUD-010 — Reporter-specific Google full-sync failure remains externally gated - -- **Source:** live:merged-010; research:external-google-report -- **Status:** blocked -- **Severity:** high -- **Category:** external-evidence -- **Evidence:** The audited production instance still has no connected Google cohort or retained reporter trace; fixtures prove internal branches but cannot reproduce the external environment. -- **Affected path:** reporter environment → manual full sync → terminal response -- **Attempted refutation:** Workout-first ordering, durable progress, truncation and terminal outcome fixtures all pass, but adjacent fixture success cannot disprove a report the instance cannot exercise. -- **Impact:** The release must not claim the specific full-sync report resolved without redacted reporter evidence. -- **Requirement:** GH-01, GH-06 -- **Planned verification:** Obtain the redacted terminal response, release/build and resource outcome from the reporter, then reproduce that exact class. -- **Confidence:** high -- **Unresolved:** yes -- **Closure:** Release blocker until the external checkpoint is satisfied or explicitly waived by the release owner. - -## AUD-011 — Insights polling ends in an honest terminal state - -- **Source:** correctness:merged-011; research:poll-ceiling -- **Status:** cleared -- **Severity:** high -- **Category:** terminal-state -- **Evidence:** `use-insight-status.ts` bounds attempts per query identity and maps timeout, transport failure and poll exhaustion to explicit terminal states rendered by the shared card. -- **Affected path:** status request loop → polling ceiling → assessment card -- **Attempted refutation:** Revalidation preserves existing safe text, but a preparation-only payload cannot survive the final allowed poll unchanged. -- **Impact:** Bounded network work no longer leaves an unbounded preparing message. -- **Requirement:** AI-03, AI-04, AI-05 -- **Planned verification:** Hook and card timer/state tests passed in the 251-test run. -- **Confidence:** high -- **Unresolved:** no -- **Closure:** Closed. - -## AUD-012 — Google writes invalidate server and browser consumers - -- **Source:** correctness:merged-012; performance:cache-prewarm -- **Status:** cleared -- **Severity:** high -- **Category:** cache-freshness -- **Evidence:** `sync-workout.ts` invalidates server measurement readers after committed writes; the Google card invalidates workouts, Dashboard snapshot, analytics, Google and integration-status query families. -- **Affected path:** Google write → server projections → browser queries → Dashboard and workouts -- **Attempted refutation:** Invalidation is emitted only after the relevant write resolves; failed and zero-write paths cannot announce nonexistent data. -- **Impact:** Successful imports become visible without waiting for expiry or remount. -- **Requirement:** GH-02, GH-04, SYNC-03 -- **Planned verification:** The real-PostgreSQL workout-terminal cache test passed again. -- **Confidence:** high -- **Unresolved:** no -- **Closure:** Closed. - -## AUD-013 — Resolved Google failures are not counted as synced - -- **Source:** correctness:merged-013; performance:cohort-fold -- **Status:** cleared -- **Severity:** high -- **Category:** silent-success -- **Evidence:** Google cohort code awaits the typed verdict and `poll-cohort.ts` increments synced users only for complete outcomes, including legitimate clean zero. -- **Affected path:** per-user verdict → cohort fold → operator counters -- **Attempted refutation:** Isolated user failures still do not fail the whole cohort, but they increment failed, parked, skipped or partial counters rather than success. -- **Impact:** Green cohort counters can no longer conceal a resolved failure. -- **Requirement:** SYNC-02, JOB-01 -- **Planned verification:** Mixed-outcome cohort tests passed in the 251-test run. -- **Confidence:** high -- **Unresolved:** no -- **Closure:** In-memory and structured cohort accounting closed; durable job-row persistence is tracked separately by AUD-029. - -## AUD-014 — Rollup failure prevents complete Google success - -- **Source:** correctness:merged-014; research:rollup-verdict -- **Status:** cleared -- **Severity:** medium -- **Category:** outcome-honesty -- **Evidence:** `sync-core.ts` records a stable rollup failure; `sync.ts` returns a failed or partial resource and withholds the complete-success watermark. -- **Affected path:** raw write → rollup recomputation → progress → watermark -- **Attempted refutation:** Raw rows may have committed, so the result remains partial rather than pretending no work occurred; it is never complete. -- **Impact:** Derived readers are not represented as current after rollup failure. -- **Requirement:** GH-04, GH-06, SYNC-03 -- **Planned verification:** Forced rollup-failure outcome tests passed in the focused Google run. -- **Confidence:** high -- **Unresolved:** no -- **Closure:** Closed. - -## AUD-015 — Reminder stamp-before-send policy is explicit - -- **Source:** correctness:merged-015; research:delivery-policy -- **Status:** cleared -- **Severity:** medium -- **Category:** delivery-policy -- **Evidence:** `medication-reminder-check.ts` deliberately writes the dedup anchor before dispatch and records a failed counter when dispatch throws. -- **Affected path:** reminder candidate → durable anchor → channel dispatch → next tick -- **Attempted refutation:** The order prevents a crash from repeating a phase every worker tick, but it cannot guarantee delivery without an outbox. -- **Impact:** A transient dispatch failure can suppress one reminder phase. -- **Requirement:** OPS-01 -- **Planned verification:** The explicit at-most-once decision and future durable-outbox boundary are recorded in `01-OPS-DECISION.md`. -- **Confidence:** high -- **Unresolved:** no -- **Closure:** Decision-complete in `01-OPS-DECISION.md`; the release makes no reliable-delivery claim and adds no reminder residual. - -## AUD-016 — Recovery assessment uses the verified shared presentation - -- **Source:** correctness:merged-016; research:recovery-visual -- **Status:** cleared -- **Severity:** low -- **Category:** visual-consistency -- **Evidence:** Recovery uses the same populated assessment component, foreground token and stable geometry that passed the shared component run and is covered by the separate release visual gate. -- **Affected path:** Recovery metric state → shared assessment card → responsive theme -- **Attempted refutation:** The audit required the shared component contract plus the release visual owner rather than inferring parity from CSS alone. -- **Impact:** No distinct Recovery presentation defect is established. -- **Requirement:** INS-06, INS-07 -- **Planned verification:** The separate release-gate artifact matrix owns final browser evidence. -- **Confidence:** high -- **Unresolved:** no -- **Closure:** Cleared; no independent residual. - -## AUD-017 — Records retain explicit revision and delete semantics - -- **Source:** correctness:merged-017; research:records-semantics -- **Status:** cleared -- **Severity:** info -- **Category:** records-semantics -- **Evidence:** Save, correction history and confirmed removal remain separate paths after the empty-state action cleanup. -- **Affected path:** records editor → revision persistence → history and removal -- **Attempted refutation:** Conditional rendering was traced to ensure it changes affordance count only and not mutation selection. -- **Impact:** No adjacent overwrite or accidental delete defect is established. -- **Requirement:** REC-02, REC-04, REC-05 -- **Planned verification:** Plan 16 one-mutation and history contracts remain applicable. -- **Confidence:** high -- **Unresolved:** no -- **Closure:** Cleared. - -## AUD-018 — Navigation and enrollment authorization fail closed - -- **Source:** correctness:merged-018; research:authorization-boundary -- **Status:** cleared -- **Severity:** info -- **Category:** authorization -- **Evidence:** Admin content waits for an authorized role; passkey registration requires a live cookie session, an existing-factor proof and a session-bound single-use challenge. -- **Affected path:** session/role/proof → privileged shell or credential enrollment -- **Attempted refutation:** Positive admin and eligible enrollment paths still succeed; bearer, stale, foreign, replay and unprivileged paths fail. -- **Impact:** No adjacent authorization bypass was found. -- **Requirement:** NAV-01, NAV-03, SEC-05 -- **Planned verification:** Passkey and admin-reset real-PostgreSQL files passed in the 42-test run. -- **Confidence:** high -- **Unresolved:** no -- **Closure:** Cleared. - -## AUD-019 — Written-plus-failed Google work remains partial - -- **Source:** correctness:merged-019; research:partial-classifier -- **Status:** cleared -- **Severity:** info -- **Category:** outcome-honesty -- **Evidence:** The route and card preserve a partial outcome when rows were written and another resource failed. -- **Affected path:** resource counts → aggregate outcome → manual-sync response -- **Attempted refutation:** Zero-write failures and complete success were checked separately so partial is not a universal fallback. -- **Impact:** Useful partial work is visible without being painted as complete. -- **Requirement:** GH-04 -- **Planned verification:** Route and card outcome matrices passed. -- **Confidence:** high -- **Unresolved:** no -- **Closure:** Cleared. - -## AUD-020 — Watched Settings and Admin bundles remain within budget - -- **Source:** performance:merged-020; research:eager-imports -- **Status:** cleared -- **Severity:** medium -- **Category:** bundle-size -- **Evidence:** The regenerated Plan 15 bundle snapshot passed the total client budget and all four watched route budgets with recorded headroom. -- **Affected path:** route module graph → client chunks → parse and hydration -- **Attempted refutation:** Aggregate size alone was not used; watched route rows were required after the analyzer workflow was repaired for the current bundler. -- **Impact:** No release-level route-size breach is established. -- **Requirement:** PERF-01 -- **Planned verification:** The release gate must rerun the bundle budget on its recorded snapshot. -- **Confidence:** high -- **Unresolved:** no -- **Closure:** Cleared against the measured budget; future growth remains monitored. - -## AUD-021 — Google page ceilings cannot silently advance completeness - -- **Source:** performance:merged-021; correctness:pagination-cap -- **Status:** cleared -- **Severity:** high -- **Category:** pagination-completeness -- **Evidence:** Client walkers record a truncated resource when a continuation token remains at the cap; `sync.ts` propagates truncation and withholds complete-success watermarking. -- **Affected path:** provider pagination → progress resource → terminal state → watermark -- **Attempted refutation:** A long successful history with 786 completed pages remains complete, proving the fix does not impose an artificial smaller ceiling. -- **Impact:** Omitted history cannot be represented as a complete sync. -- **Requirement:** GH-05, GH-07 -- **Planned verification:** Fairness, truncation and progress fixtures passed in the focused Google run. -- **Confidence:** high -- **Unresolved:** no -- **Closure:** Closed. - -## AUD-022 — Google full-sync fairness is bounded at the release contract - -- **Source:** performance:merged-022; research:sync-scalability -- **Status:** cleared -- **Severity:** medium -- **Category:** sync-scalability -- **Evidence:** Workouts run before dense history, the large successful fixture completes, and durable progress exposes terminal state when the request boundary is uncertain. -- **Affected path:** manual request → provider pages → mapping → persistence -- **Attempted refutation:** Unbounded provider histories cannot be proven, but no tested fairness, truncation or terminal-state contract is violated. -- **Impact:** No silent or release-budget failure is established. -- **Requirement:** PERF-01, GH-07 -- **Planned verification:** Continue performance monitoring before any future structural optimization. -- **Confidence:** high -- **Unresolved:** no -- **Closure:** Cleared against the defined release contract; no residual adopted. - -## AUD-023 — Reminder cohort baseline shows no release-budget breach - -- **Source:** performance:merged-023; research:reminder-scan -- **Status:** cleared -- **Severity:** medium -- **Category:** worker-scalability -- **Evidence:** The worker remains a bounded global pass with serial per-medication work; the fixture baseline did not establish a release-budget violation. -- **Affected path:** reminder tick → medication scan → recurrence and dispatch -- **Attempted refutation:** The bounded baseline and current cohort evidence show no present breach; future growth is monitoring, not a current defect. -- **Impact:** No release-blocking worker cost is established. -- **Requirement:** PERF-01 -- **Planned verification:** Add a database/network-stub p95 and query budget before a future structural change. -- **Confidence:** high -- **Unresolved:** no -- **Closure:** Cleared for this release; no residual adopted. - -## AUD-024 — Dense Health Score baseline stays within the release evidence - -- **Source:** performance:merged-024; research:score-density-cost -- **Status:** cleared -- **Severity:** medium -- **Category:** score-scalability -- **Evidence:** The dense local baseline passed but the read path still scales with the amount of retained source data. -- **Affected path:** score request → measurement reads → canonical score -- **Attempted refutation:** The audit required exact score parity and measured timing; no threshold breach was observed. -- **Impact:** No current score-read regression is established. -- **Requirement:** PERF-01 -- **Planned verification:** Require row count, heap and cold p95 evidence with exact score parity before any future optimization. -- **Confidence:** high -- **Unresolved:** no -- **Closure:** Cleared for this release; no residual adopted. - -## AUD-025 — Digest prefetch shows no duplicate-work release breach - -- **Source:** performance:merged-025; research:digest-prefetch -- **Status:** cleared -- **Severity:** low -- **Category:** request-amplification -- **Evidence:** Digest assembly is bounded and cache-coalesced, but a timeout does not by itself prove the underlying builder stopped. -- **Affected path:** cold navigation → prefetch timeout → builder/cache -- **Attempted refutation:** No duplicate-work breach appeared in the measured route baseline, so cancellation is not justified release work. -- **Impact:** No current user-visible or capacity defect is established. -- **Requirement:** PERF-01 -- **Planned verification:** Trace a future cold timeout only if monitoring shows a breach. -- **Confidence:** medium -- **Unresolved:** no -- **Closure:** Cleared; no residual adopted. - -## AUD-026 — Slow screened Insights generation has a safe terminal result - -- **Source:** live:merged-026; correctness:screened-generation -- **Status:** cleared -- **Severity:** high -- **Category:** generated-content-latency -- **Evidence:** Derived assessment generation screens before persistence and maps blocked, malformed or ungrounded prose to a deterministic fallback; polling ends explicitly. -- **Affected path:** provider generation → screen → cache → visible assessment -- **Attempted refutation:** Screening remains fail closed and slow providers can still consume their bounded request time; the change closes disappearance, not provider latency. -- **Impact:** A slow rejected response no longer ends as unexplained absence. -- **Requirement:** AI-02, AI-03, AI-04 -- **Planned verification:** Generator, screen, hook and card tests passed in the focused run. -- **Confidence:** high -- **Unresolved:** no -- **Closure:** Safety/outcome defect closed; provider latency remains observable. - -## AUD-027 — Provider degradation preserves deterministic status text - -- **Source:** live:merged-027; correctness:provider-resilience -- **Status:** cleared -- **Severity:** high -- **Category:** provider-resilience -- **Evidence:** Cache and card state prioritize screened fallback text while retaining bounded provider retry metadata. -- **Affected path:** degraded provider chain → safe fallback → status card -- **Attempted refutation:** Healthy generated text still supersedes fallback when it passes all screens; fallback is not mislabeled as provider content. -- **Impact:** Provider degradation does not create a blank assessment. -- **Requirement:** AI-02, AI-04 -- **Planned verification:** Focused fallback and provider-state matrices passed. -- **Confidence:** high -- **Unresolved:** no -- **Closure:** Closed in code; live reason counters should be compared after deployment. - -## AUD-028 — Parked Withings targets are not counted as synced - -- **Source:** live:merged-028; correctness:withings-cohort -- **Status:** cleared -- **Severity:** medium -- **Category:** silent-success -- **Evidence:** Withings handlers fold connected, parked, partial, failed and clean-zero verdicts through the shared provider-neutral cohort aggregate. -- **Affected path:** integration state → per-user result → cohort counters -- **Attempted refutation:** Zero imported rows can still be healthy and count as clean success; parked state cannot. -- **Impact:** Operator counters distinguish a parked target from a healthy no-new-data target. -- **Requirement:** SYNC-02, JOB-01 -- **Planned verification:** Mixed provider cohort tests passed in the 251-test run. -- **Confidence:** high -- **Unresolved:** no -- **Closure:** In-memory accounting closed; durable row persistence is AUD-029. - -## AUD-029 — Successful job outcomes are still absent from durable completed rows - -- **Source:** live:merged-029; correctness:job-output-wiring -- **Status:** confirmed -- **Severity:** medium -- **Category:** outcome-honesty -- **Evidence:** `job-outcome.ts` validates a bounded serializer, but its only callers are tests; `run-job.ts` returns immediately with `void` when `outcome.ok` and never persists or returns the serialized facts to pg-boss. -- **Affected path:** handler `JobOutcome` → run wrapper → pg-boss completed row → operator reader -- **Attempted refutation:** Cohort handlers now construct truthful scalar facts and failed outcomes reach retry/error reporting, but neither makes successful facts durable. -- **Impact:** Completed jobs still cannot distinguish useful work, clean zero, skips, partial cohorts or fallbacks after logs expire. -- **Requirement:** JOB-01 -- **Planned verification:** Wire `serializeJobOutcome` to a bounded durable success result, then prove completed rows contain only the allowlisted facts for useful, zero, partial and skipped runs. -- **Confidence:** high -- **Unresolved:** yes -- **Closure:** Release blocker; Plan 22's serializer is not an end-to-end persistence implementation. - -## AUD-030 — Measurement planner statistics remain materially stale - -- **Source:** live:merged-030; performance:planner-statistics -- **Status:** confirmed -- **Severity:** medium -- **Category:** operations -- **Evidence:** The read-only snapshot reports 628 estimated live and 24,253 dead tuples while `reltuples` remains 389,116; the relation uses about 664 MB across heap and 13 indexes with no recorded vacuum or analyze. -- **Affected path:** planner statistics → measurement query plans → maintenance thresholds -- **Attempted refutation:** There was no active contention or old transaction and earlier request timings were healthy; this refutes an outage but not stale cardinality. -- **Impact:** Bad estimates can select inefficient plans and delay threshold-based maintenance as data changes. -- **Requirement:** OPS-01 -- **Planned verification:** Follow the separate read-plan, approved maintenance and remeasurement sequence in `01-OPS-DECISION.md`. -- **Confidence:** high -- **Unresolved:** yes -- **Closure:** Controlled operational residual; no release/deploy mutation was performed or authorized. - -## AUD-031 — Shared-host peaks do not establish a HealthLog defect - -- **Source:** live:merged-031; performance:host-pressure -- **Status:** cleared -- **Severity:** low -- **Category:** host-pressure -- **Evidence:** The prior aggregate host sample showed no HealthLog-specific causal trace for occasional shared-host peaks. -- **Affected path:** shared host load → container scheduling → request latency -- **Attempted refutation:** Healthy containers and bounded application timings provide no causal HealthLog link; future incidents require their own evidence. -- **Impact:** No application release defect is established. -- **Requirement:** AUD-03, OPS-01 -- **Planned verification:** Correlate any future slow event with per-container metrics and event-loop lag. -- **Confidence:** medium -- **Unresolved:** no -- **Closure:** Cleared for this release; no residual adopted. - -## AUD-032 — Status transport errors cannot masquerade as no provider - -- **Source:** correctness:merged-032; live:transport-state -- **Status:** cleared -- **Severity:** medium -- **Category:** terminal-state -- **Evidence:** The hook maps abort, timeout and server failure to fixed error kinds; the card reserves provider setup for a genuine no-provider state. -- **Affected path:** status transport → hook normalization → card branch -- **Attempted refutation:** A real no-provider response still reaches setup guidance, proving transport failure is not collapsed into the same branch. -- **Impact:** Users receive an honest retryable or exhausted state instead of incorrect configuration advice. -- **Requirement:** AI-03, AI-04, AI-05 -- **Planned verification:** Hook and card transport matrices passed. -- **Confidence:** high -- **Unresolved:** no -- **Closure:** Closed. - -## AUD-033 — No general runtime outage is evidenced - -- **Source:** live:merged-033; research:runtime-health -- **Status:** cleared -- **Severity:** info -- **Category:** runtime-health -- **Evidence:** The bounded production database snapshot completed immediately with one active session and no transaction older than one minute; the earlier same-day app audit found healthy containers and no general error cluster. -- **Affected path:** production runtime → database availability → application health -- **Attempted refutation:** This does not prove every provider integration is healthy and is not used to clear the reporter-specific Google finding. -- **Impact:** No general outage blocks interpretation of the targeted findings. -- **Requirement:** AUD-03 -- **Planned verification:** Repeat served-version and health evidence in the release/deploy gate. -- **Confidence:** high -- **Unresolved:** no -- **Closure:** Cleared for the bounded audit window. - -## AUD-034 — Schema delta is singular and additive - -- **Source:** live:merged-034; correctness:migration-boundary -- **Status:** cleared -- **Severity:** info -- **Category:** schema-health -- **Evidence:** From the recorded base, the only migration delta is `0287_google_health_sync_progress`; its SQL adds one nullable JSONB column and the Prisma mapping is the only matching schema change. -- **Affected path:** base tree → migration set → Google progress persistence -- **Attempted refutation:** Package, migration name, SQL body and Prisma schema were checked separately so a renamed or hidden schema delta could not pass by filename count alone. -- **Impact:** The release does not carry an unrelated schema or dependency change. -- **Requirement:** AUD-03, GH-07 -- **Planned verification:** Migration-name, SQL-content and package-baseline commands pass before handoff. -- **Confidence:** high -- **Unresolved:** no -- **Closure:** Cleared. - -## AUD-035 — No unrelated queue-lock or server-error cluster was introduced - -- **Source:** live:merged-035; correctness:cross-domain-regression -- **Status:** cleared -- **Severity:** info -- **Category:** runtime-health -- **Evidence:** The focused merged run passed 251 tests and the real-PostgreSQL boundary run passed 42 tests across medication, archive encryption, Google progress/cache, MCP replay, passkey and reset paths. -- **Affected path:** merged security/reliability tree → shared runtime boundaries -- **Attempted refutation:** Passing focused tests cannot replace the full release suite and does not clear AUD-010 or AUD-029. -- **Impact:** No additional cross-domain failure was found within the audited boundaries. -- **Requirement:** AUD-03, AUD-04 -- **Planned verification:** The independent full release gate must still pass with zero release-critical failure or skip. -- **Confidence:** high -- **Unresolved:** no -- **Closure:** Cleared within Task 3 scope. - -## AUD-036 — Nightscout token warning logs remain an accepted scanner residual - -- **Source:** research:merged-036; correctness:nightscout-log-residual -- **Status:** confirmed -- **Severity:** low -- **Category:** privacy -- **Evidence:** The accepted scanner ledger still identifies legacy Nightscout token warning-log exposure; the exact-origin SSRF work did not claim or implement log redaction. -- **Affected path:** Nightscout error handling → warning log sink -- **Attempted refutation:** Connect, sync and operator-origin failures use stable redacted policy codes, but that does not refute the separately accepted token-warning path. -- **Impact:** A Nightscout credential could enter operator-controlled logs on the legacy warning branch. -- **Requirement:** DEFER-SEC-01 -- **Planned verification:** Retain documentation and owner follow-up; do not claim closure without a dedicated log-redaction test and implementation. -- **Confidence:** high -- **Unresolved:** yes -- **Closure:** Accepted Low residual; unchanged and not release-blocking. - -## AUD-037 — PWA logout favors privacy over immediate offline asset reuse - -- **Source:** research:merged-037; correctness:pwa-privacy-tradeoff -- **Status:** confirmed -- **Severity:** low -- **Category:** privacy -- **Evidence:** Logout clears all current and legacy HealthLog static, page and data caches; the next offline launch can therefore show the data-free fallback until public assets are fetched again. -- **Affected path:** session end → owned cache eviction → next offline launch -- **Attempted refutation:** Unrelated cache namespaces survive and immutable public assets are cached again online; preserving the old HealthLog static cache would weaken complete account-data eviction. -- **Impact:** A user may temporarily lose the rich offline shell after logout, while authenticated Dashboard HTML cannot survive the session. -- **Requirement:** SEC-02, SEC-08 -- **Planned verification:** Service-worker and query-persister privacy tests passed in the 251-test merged run. -- **Confidence:** high -- **Unresolved:** yes -- **Closure:** Accepted Low privacy-first behavior; unchanged and not release-blocking. - -## Verification summary - -| Gate | Result | -| --- | --- | -| Focused merged unit/component audit | PASS — 21 files, 251 tests | -| Real-PostgreSQL boundary audit | PASS — 8 files, 42 tests | -| Normalized ledger checker | PASS — 37 unique findings, privacy scan clean | -| Branch and immutable package baseline | PASS | -| Sole migration delta and SQL shape | PASS | -| Production planner diagnostic | PASS, read-only | -| Exact residual set | AUD-029, Nightscout warning logs, PWA offline-refetch behavior, AUD-030 | -| Release decision | BLOCK — AUD-010 evidence checkpoint and AUD-029 implementation gap | diff --git a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-OPS-DECISION.md b/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-OPS-DECISION.md deleted file mode 100644 index 17ae4417b..000000000 --- a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-OPS-DECISION.md +++ /dev/null @@ -1,133 +0,0 @@ -# v1.34.1 controlled operations decision - -**Decision date:** 2026-07-29 - -**Decision:** Do not run PostgreSQL maintenance as part of the v1.34.1 -release or deploy path. Preserve the current reminder at-most-once delivery -policy for this point release. - -**Operations gate:** PASS with one controlled planner-statistics residual. - -**Overall merged re-audit:** BLOCK for the independent release findings in -`01-MERGED-REAUDIT.md`. - -## Production evidence boundary - -The production check was deliberately read-only, bounded, and aggregate-only: - -- connected to the existing production PostgreSQL container; -- forced `default_transaction_read_only=on`; -- used a five-second statement timeout and one-second lock timeout; -- read only PostgreSQL catalog/statistics views and relation sizes; -- retained no account identifiers, credentials, row payloads, error payloads, - or health values; and -- ran no `ANALYZE`, `VACUUM`, reindex, schema command, sync, deploy, restart, - cleanup, or other mutation. - -The snapshot completed at 2026-07-29 13:16 UTC. PostgreSQL had started at -2026-07-28 22:43 UTC, and no statistics reset was recorded. - -## Planner-statistics evidence - -| Observation | Read-only result | -| --- | ---: | -| Estimated live tuples | 628 | -| Estimated dead tuples | 24,253 | -| `pg_class.reltuples` | 389,116 | -| Heap size | 137,682,944 bytes | -| Index size | 526,458,880 bytes | -| Total relation size | 664,215,552 bytes | -| Database size | 1,015,815,191 bytes | -| Index count | 13 | -| Recorded table vacuum/analyze events | 0 | -| Active database sessions during sample | 1 | -| Transactions older than one minute | 0 | - -Autovacuum is enabled with the default observed table thresholds: vacuum -threshold 50 and scale factor 0.2; analyze threshold 50 and scale factor 0.1. -The old `reltuples` estimate therefore materially inflates the scale-factor -thresholds. The index statistics also show that the footprint is not uniformly -dead: the principal live covering index is heavily used, while several large -indexes have much lower recorded scan counts. Statistics alone do not prove -that any individual index is redundant. - -The attempted refutation is only partial. There is no current database -contention signal, the earlier live audit found healthy bounded measurement -request timings, and planner counts are estimates rather than an exact row -count. Those facts refute an active outage, but they do not refute the -cardinality mismatch or the maintenance risk. - -## Explicit planner decision - -The stale statistics are a Medium operational residual, not an automatic -release-deploy mutation. - -1. Do not add `ANALYZE`, `VACUUM`, `VACUUM FULL`, or reindexing to the - container entrypoint, migration, or release command sequence. -2. Open a separate approved maintenance change. Before mutation, capture - representative read-only plans for the measurement list, series, rollup, - and retention paths and record their estimates. -3. In that separate window, start with ordinary table statistics maintenance, - then repeat the same plans and aggregate statistics. This document does not - authorize that action. -4. Consider low-lock index maintenance only after plan and usage evidence - identifies a concrete candidate and disk headroom is verified. Never use - `VACUUM FULL` in the point-release path. -5. Roll back the maintenance change by stopping after measurement and leaving - the application release untouched; no schema or application rollback is - coupled to this decision. - -## Reminder delivery policy - -`medication-reminder-check.ts` intentionally writes the durable dedup anchor -before notification dispatch. A crash or dispatch failure can therefore cost -one phase, while stamping after send can repeat a notification every worker -tick. The merged implementation exposes `notifications_failed` and preserves -the canonical occurrence identity, but it does not add a durable outbox. - -For v1.34.1 the explicit decision is to retain at-most-once phase delivery. -Changing to retry-until-delivered would require a separately designed durable -outbox/idempotent channel contract and is not safe point-release scope. The -decision closes the release ambiguity: phase delivery is not represented as -reliable delivery, and no additional reminder residual is created for this -release. - -## Immutable execution baseline - -Recomputed on the merged working tree: - -| Baseline item | Required | Observed | Result | -| --- | --- | --- | --- | -| Branch | `release/v1.34.1` | `release/v1.34.1` | PASS | -| Binary package/workspace patch SHA-256 | `b1206decc31f09b621676ac558919caf23a2190869d948fbc585fa3f38170f95` | same | PASS | -| `package.json` SHA-256 | `7a6dbc488beaf210808bf959b84717be462781f2aca74a5fbc157699d7b3668a` | same | PASS | -| `pnpm-lock.yaml` SHA-256 | `d9fa8c99e86eb4977f55395c43ca2c1781de26400870003c783e7112405970d6` | same | PASS | -| `pnpm-workspace.yaml` SHA-256 | `979d04c351ba838e6c8da97aa34545c9a76513710f86d84f6c07d1289d0cd054` | same | PASS | - -The semantic allowlist is unchanged: - -- `package.json` adds only `@openai/codex-security` at `^0.1.1`; -- `pnpm-workspace.yaml` adds only the matching scanner release-age exclusion; -- `pnpm-lock.yaml` contains the corresponding resolved installation delta; and -- the line deltas remain +1/-0, +657/-21, and +1/-0 respectively. - -No package install, normalization, upgrade, or lockfile write was performed. - -## Migration boundary - -Against base commit -`6528d9f289faedf54e16f0531d862282b53847ed`, the only migration delta is: - -`prisma/migrations/0287_google_health_sync_progress/migration.sql` - -Its complete SQL adds one nullable `sync_progress` JSONB column to -`google_health_connections`. The matching Prisma schema delta adds only the -nullable `syncProgress` mapping. No other migration, table, index, constraint, -enum, package, or dependency delta was found. - -## Closure - -OPS-01 is decision-complete: production evidence was refreshed without -mutation, planner maintenance is separated from release deployment, and the -reminder delivery tradeoff is explicit. This operations PASS does not override -the merged audit's release BLOCK. diff --git a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-PERFORMANCE-BASELINE.md b/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-PERFORMANCE-BASELINE.md deleted file mode 100644 index 7035d3dfd..000000000 --- a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-PERFORMANCE-BASELINE.md +++ /dev/null @@ -1,114 +0,0 @@ -# v1.34.1 Performance Baseline - -**Generated:** 2026-07-29T12:38:03.677Z - -**Commit:** `efe203212a7269c2bd3d16f011985dd888e35679` - -**Exit status:** PASS - -**Mode:** deterministic local fixture only - -## Safety and interpretation - -- No network, database, provider, production, or staging endpoint was contacted. -- Fixtures contain generated counters only. The report emits no tokens, identifiers, payloads, health values, hostnames, usernames, or working-directory paths. -- Concurrency is fixed at one, warm sample count is 11, and the whole run has a 30-second deadline. -- Timing is a local code-shape baseline, not a production latency or capacity claim. Database, provider, network, framework, cache, and rendering costs remain unmeasured. -- The Health Score fixture exercises dense/sparse row construction, source canonicalization, and a stable composite proxy. It does not replace production algorithm parity or database-backed performance tests. - -## Environment - -| Attribute | Value | -| --- | --- | -| Node | v25.9.0 | -| Platform | darwin 25.3.0 | -| Architecture | arm64 | -| Logical CPUs | 14 | -| Process | single local Node process | -| Total harness duration | 152.805 ms | - -## Fixture scale - -| Path shape | Fixed scale | -| --- | --- | -| Google history | 786 serial pages × 32 generated rows; terminal page has no remaining token | -| Reminder cohort | 2000 medications × 4 slots plus 2000 cleanup records | -| Health Score | 42 dense days × 1440 samples plus 379 sparse days × 8 samples | - -## Measurements - -| Local path shape | Cold | Warm median | Warm p95 | Warm max | Cold heap delta | Max warm heap delta | -| --- | ---: | ---: | ---: | ---: | ---: | ---: | -| Google history pagination shape | 8.539 ms | 7.499 ms | 10.600 ms | 10.600 ms | 2.00 MiB | 6.19 MiB | -| Medication reminder cohort shape | 2.723 ms | 0.083 ms | 1.586 ms | 1.586 ms | 0.18 MiB | 0.06 MiB | -| Health Score read/canonicalization shape | 6.232 ms | 4.141 ms | 5.758 ms | 5.758 ms | 7.20 MiB | 8.17 MiB | - -### Determinism and bounded counters - -- **Google history pagination shape:** checksum `47f760c2`; pages=786; rows=25152; concurrency=1; finalTokenPresent=false. -- **Medication reminder cohort shape:** checksum `73e03617`; medications=2000; slots=8000; cleanupBacklog=2000; dueCandidates=1582; modeledQueries=7165; concurrency=1. -- **Health Score read/canonicalization shape:** checksum `5c395b1a`; denseRows=60480; sparseRows=3032; canonicalDays=42; domains=8; concurrency=1; parity=stable-checksum. - -## Route/path interpretation - -| Representative path | What this fixture measures | What it does not measure | -| --- | --- | --- | -| Google full-sync pagination | Serial JSON page materialization and accumulation at the observed 786-page shape | Provider latency, DB writes, token refresh, rollups, or a safe universal page ceiling | -| Medication reminder worker | Serial cohort/slot decisions, cleanup scan, and modeled query amplification | Prisma latency, locks, dispatch latency, or the live cohort size | -| Dashboard / analytics Health Score | Dense/sparse row processing, canonical selection, and stable composite proxy | SQL plans, cache behavior, exact production score parity, or user-visible route latency | - -## Hypotheses and unmeasured risks - -- **Hypothesis:** Serial provider latency and write statements dominate the Google full-sync wall time; this local CPU result neither confirms nor refutes that hypothesis. -- **Hypothesis:** Reminder query/dispatch amplification, not local loop cost, determines whether a high-water pass approaches its 15-minute cadence. -- **Hypothesis:** Cold database row materialization and canonicalization dominate dense-user Health Score work; production impact remains unconfirmed. -- **Unmeasured:** network and database tails, cache hit/miss ratios, cross-process invalidation, framework rendering, device/browser performance, and production cohort distributions. -- **Do not infer:** a provider defect, a safe page cap, a production regression, or permission to optimize from this report alone. - -## Reproduction - -`node scripts/v1341-performance-baseline.mjs --fixture --output .planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-PERFORMANCE-BASELINE.md` - -## Release build and bundle evidence - -The production build, fixture timings above, and bundle report below were -generated from one isolated archive of commit -`efe203212a7269c2bd3d16f011985dd888e35679`. Generated Prisma artifacts and the -existing local dependency store were supplied as build prerequisites; no -uncommitted product source was copied into the snapshot. - -| Bundle signal | Measured | Budget / gate | Result | -| --- | ---: | ---: | --- | -| Shared root baseline | 130 KB gzip | informational | measured | -| All client chunks | 3,133 KB gzip | 3,140 KB gzip | pass; 7 KB headroom | -| Largest emitted chunk | 387 KB gzip | informational | measured | -| Recharts fingerprint chunks | 1 | maximum 1 | pass | -| Dashboard route (`/page`) | 431 KB gzip | 460 KB gzip | pass; 29 KB headroom | -| Insights route (`/insights/page`) | 417 KB gzip | 445 KB gzip | pass; 28 KB headroom | -| Measurements route (`/measurements/page`) | 417 KB gzip | 445 KB gzip | pass; 28 KB headroom | -| Mood route (`/insights/mood/page`) | 435 KB gzip | 460 KB gzip | pass; 25 KB headroom | -| Static message-catalog references | none reported | none allowed | pass | - -Commands: - -- `NODE_OPTIONS=--max-old-space-size=8192 ./node_modules/.bin/next build` -- `node scripts/check-bundle-budget.mjs` -- `node scripts/check-bundle-budget.mjs --check` - -The normal 4 GiB Node heap compiled the shared workspace but exhausted memory -during TypeScript verification. The isolated snapshot passed after raising only -the build-process heap to 8 GiB; repository configuration and runtime limits -were not changed. - -### Bundle interpretation - -- The existing budget checker emits the four budgeted route entries above. It - does not currently publish per-route gzip rows for Settings, Admin, or - Documents when a budget file is present, so this report does not invent - comparable figures for them. -- Passing the aggregate gate does not refute the earlier hypothesis that eager - Settings/Admin section imports create avoidable route cost. That hypothesis - remains unmeasured by the current watched-route output. -- The 7 KB aggregate headroom is narrow enough that the final merged release - should rerun the same gate. It is evidence of the measured snapshot, not - permission to raise a budget or a claim about browser parse/hydration time. diff --git a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-RELEASE-GATE.md b/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-RELEASE-GATE.md deleted file mode 100644 index 1d685b33f..000000000 --- a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-RELEASE-GATE.md +++ /dev/null @@ -1,283 +0,0 @@ -# v1.34.1 merged technical release gate - -**Decision:** **BLOCK — DO NOT RELEASE** - -**Gate date:** 2026-07-29 - -**Gate window:** 2026-07-29T13:16:37Z–2026-07-29T13:40:50Z - -**Operator timezone:** Europe/Berlin -**Runtime:** Node v25.9.0, pnpm 11.15.1, Docker Server 29.4.0 - -This is the Plan 01-24 Task 1 technical/experience gate only. No production, -test, package, lockfile, scanner, locale, schema, or migration change was made -while diagnosing the failures below. - -## Frozen candidate identity - -The gate started on branch `release/v1.34.1` at -`8a3c3d096fd6aeed59587118da00fd2a0976febb`. - -The initial working tree contained exactly the pre-existing unstaged changes -to `package.json`, `pnpm-lock.yaml`, and `pnpm-workspace.yaml`; staged and -untracked sets were empty. - -| Identity item | Value | -|---|---| -| Initial porcelain-v2 status SHA-256 | `580eab95900630baf9f0602ce9f322ee908bdcb79e976b4557503aefd8c175ff` | -| Initial tracked-worktree manifest SHA-256 | `92c94fab5fe562c4de84567d011f03a70684ab881b1c8ee11039f0d48df07e6e` | -| Binary worktree patch SHA-256 | `b1206decc31f09b621676ac558919caf23a2190869d948fbc585fa3f38170f95` | -| `package.json` SHA-256 | `7a6dbc488beaf210808bf959b84717be462781f2aca74a5fbc157699d7b3668a` | -| `pnpm-lock.yaml` SHA-256 | `d9fa8c99e86eb4977f55395c43ca2c1781de26400870003c783e7112405970d6` | -| `pnpm-workspace.yaml` SHA-256 | `979d04c351ba838e6c8da97aa34545c9a76513710f86d84f6c07d1289d0cd054` | - -During the long integration run, HEAD advanced to -`8b3b2a388e7aeea20c28b60005aa668a7dfe08ad` through the concurrent -docs-only commit `docs(01-24): record merged re-audit block`. Its only tree -deltas from the initial HEAD are `01-MERGED-REAUDIT.md` and -`01-OPS-DECISION.md`. The candidate source, tests, package worktree, and binary -patch did not change; the final binary patch and all three package hashes -remain exactly the values above. - -## Locked command ledger - -All durations are wall-clock seconds. “Skip” is blocking under Plan 01-24 even -when the command exits zero. Playwright retries were disabled, so the flaky -count is zero. - -| Gate | Exact command | UTC start–end | Duration | Result and counts | -|---|---|---:|---:|---| -| Typecheck | `pnpm typecheck` | 13:16:37–13:16:42 | 4s | **PASS**, rc 0 | -| Lint | `pnpm lint` | 13:16:49–13:17:29 | 40s | **PASS**, rc 0; 0 errors, 6 warnings | -| Format | `pnpm format:check` | 13:17:36–13:17:59 | 23s | **FAIL**, rc 1; 10 files | -| UTC unit | `TZ=UTC pnpm test` | 13:18:10–13:19:38 | 87s | **FAIL**, rc 1; 1,630 files passed / 11 failed; 19,020 tests passed / 29 failed / 12 skipped | -| Locked build | `pnpm build` | 13:19:47–13:20:53 | 66s | **FAIL**, rc 1; compile passed, TypeScript worker aborted on default ~4 GB heap OOM | -| Real PostgreSQL integration | `pnpm test:integration` | 13:21:06–13:33:33 | 747s | rc 0 but **BLOCKED BY SKIPS**; 139 files passed; 782 passed / 3 skipped | -| Diagnostic build only | `NODE_OPTIONS=--max-old-space-size=8192 pnpm build` | 13:33:47–13:34:40 | 53s | PASS, rc 0; proves the locked-build failure is heap-ceiling dependent, but does not clear it | -| Bundle report, plan-literal invocation | `node scripts/check-bundle-budget.mjs` | 13:35:21–13:35:22 | 1s | rc 0; report only | -| Enforced bundle gate | `node scripts/check-bundle-budget.mjs --check` | 13:35:21–13:35:22 | 1s | **FAIL**, rc 1; total 3,234 KB gzip > 3,140 KB budget | -| Locale/i18n/literal guard cluster | `TZ=UTC pnpm exec vitest run src/__tests__/v1341-locale-parity.test.ts src/__tests__/i18n-reverse-coverage.test.ts src/__tests__/i18n-call-site-coverage.test.ts src/__tests__/dynamic-key-exhaustiveness.test.ts src/__tests__/insights-unit-literal-guard.test.ts src/components/measurement-reminders/__tests__/type-labels-i18n.test.ts src/components/settings/integrations/__tests__/nightscout-card.test.tsx src/components/settings/integrations/__tests__/google-health-card.test.tsx src/components/settings/import-panel/__tests__/apple-health-import-card.test.tsx src/components/insights/__tests__/insight-status-card.test.tsx` | 13:35:21–13:35:23 | 2s | **PASS**, rc 0; 9 files / 470 tests passed; 0 failed, 0 skipped | -| Privacy ledger guard | `node scripts/v1341-audit-ledger-check.mjs .planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-MERGED-REAUDIT.md` | 13:35:21 | <1s | **PASS**, rc 0; 37 unique findings, privacy scan clean | -| Full Playwright, local `.env` diagnostic | `NODE_OPTIONS='--import dotenv/config' pnpm exec playwright test --project=chromium-desktop --project=chromium-mobile` | 13:35:43–13:37:13 | 90s | **FAIL**, rc 1; 249 passed / 42 failed / 85 skipped / 36 did not run / 0 flaky | -| Full Playwright, repository CI-documented env | `CI-documented-env pnpm exec playwright test --project=chromium-desktop --project=chromium-mobile` | 13:38:59–13:40:50 | 111s | **FAIL**, rc 1; 313 passed / 3 failed / 96 skipped / 0 flaky | - -The CI-documented browser environment is the environment declared in -`.github/workflows/e2e.yml`: the public dummy `ENCRYPTION_KEYS` key ring, -`ENCRYPTION_ACTIVE_KEY_ID=v1`, public dummy `API_TOKEN_HMAC_KEY`, localhost RP -settings, `SESSION_COOKIE_SECURE=false`, `NODE_ENV=production`, -`E2E_BASE_URL=http://localhost:3000`, and the local `.env` database URL. No -secret value is reproduced here. - -## Blocking failures, prioritized - -### P0 — Browser privacy/document serving contract fails - -The configured full browser run failed the same release-critical clinician -document path in both projects: - -- `e2e/share-documents.spec.ts:154` on `chromium-desktop` -- `e2e/share-documents.spec.ts:154` on `chromium-mobile` - -The test expected the unlocked shared PDF request to return HTTP 200; it -returned HTTP 500 at the first Class A PDF serve assertion -(`e2e/share-documents.spec.ts:195`). The failing artifacts are under: - -- `test-results/share-documents-clinician--c8ddd-e-EXIF-strip-→-revoke-→-404-chromium-desktop/` -- `test-results/share-documents-clinician--c8ddd-e-EXIF-strip-→-revoke-→-404-chromium-mobile/` - -This remains a product/privacy-boundary failure after the correct test key ring -is supplied; it is not explained by the first run's local encryption-key -configuration. - -### P0 — UTC unit suite is red - -`TZ=UTC pnpm test` produced 29 failures in 11 files: - -- `src/__tests__/written-outcome-response-consumer-guard.test.ts` — the - consumer guard does not support the newly encountered spread form. -- `src/lib/dashboard/__tests__/meds-today.test.ts` — equal-time overdue - boundary expectations disagree with current scheduling semantics. -- `src/app/api/admin/import-apple-health-export/__tests__/route.test.ts` -- `src/app/api/import/apple-health-export/__tests__/route.test.ts` — Apple - Health import expectations still pin parser revision 2 while the candidate - emits revision 3. -- `src/lib/jobs/__tests__/withings-subscription-retry.test.ts` -- `src/lib/jobs/__tests__/withings-ecg-worker.test.ts` — Withings retry/ECG - mocks and expectations have drifted; some paths also attempted unintended - database connections after mock fall-through. -- `src/lib/jobs/__tests__/google-health-sleep-repair.test.ts` -- `src/lib/google-health/__tests__/sync-upsert-resurrect.test.ts` — mocks are - missing new outcome/progress exports such as - `noteGoogleHealthOutcomeFailure` and `noteGoogleHealthMapped`. -- `src/lib/google-health/__tests__/sync-failsoft.test.ts` — assertions still - expect the older sync DTO. -- `src/lib/jobs/reminder/__tests__/poll-cohort.test.ts` — reminder poll/cohort - expectations are red. -- `src/lib/jobs/__tests__/apple-health-import-send-policy.test.ts` — Apple - Health import notification/send-policy expectations are red. - -The exact suite totals were 1,630 passed files / 11 failed files and 19,020 -passed tests / 29 failed tests / 12 skipped tests. Both failures and skips are -release blockers. - -### P0 — Formatting gate is red - -Prettier rejected these exact 10 files: - -1. `scripts/v1341-audit-ledger-check.mjs` -2. `scripts/v1341-performance-baseline.mjs` -3. `src/app/api/auth/passkey/register-options/route.ts` -4. `src/app/mcp/__tests__/route.test.ts` -5. `src/lib/apple-health/ecg-csv.ts` -6. `src/lib/medications/scheduling/__tests__/next-due.test.ts` -7. `tests/integration/bearer-scope-enforcement.test.ts` -8. `tests/integration/mcp-oauth-refresh-race.test.ts` -9. `tests/integration/medication-reminder-cross-midnight.test.ts` -10. `tests/integration/medication-rolling-overdue-consumers.test.ts` - -### P0 — Enforced bundle budget is red - -`node scripts/check-bundle-budget.mjs --check` reports total client JavaScript -of **3,234 KB gzip**, exceeding the **3,140 KB** budget by **94 KB**. - -The measured route values themselves stayed below their route budgets: - -- `/insights/mood/page`: 438 / 460 KB gzip -- `/page`: 431 / 460 KB gzip -- `/insights/page`: 420 / 445 KB gzip -- `/measurements/page`: 417 / 445 KB gzip - -The shared baseline is 130 KB gzip, the largest chunk is 387 KB gzip, and one -Recharts-fingerprint chunk was found. - -### P1 — Configured browser download contract times out - -The configured run also failed: - -- `e2e/settings-export.spec.ts:215` on `chromium-desktop` - -`page.waitForEvent("download")` timed out after 30 seconds after clicking the -JSON import “Download example” button. Evidence: -`test-results/settings-export-Settings-→-883d1-ample-fires-a-real-download-chromium-desktop/`. -The same test is project-skipped on mobile. - -### P1 — Locked build is not reproducibly green - -The exact `pnpm build` compiled successfully, then the Next.js TypeScript -worker exhausted its default approximately 4 GB V8 heap and aborted with -SIGABRT. The diagnostic 8 GB invocation passed in 53 seconds. This identifies -the immediate cause but cannot substitute for the locked command required by -the release plan. - -### P1 — Full real-PostgreSQL suite contains explicit skips - -All executed integration coverage passed against a fresh Testcontainers -PostgreSQL database with all 281 migrations through 0287 applied: 139 files, -782 tests. The only three skips are explicit `it.skip` cases in -`tests/integration/source-priority-two-axis.test.ts`: - -- line 151: per-metric override pins Withings first when Apple Health also - reported sleep -- line 194: per-device override drops iPhone rows in favour of Apple Watch rows -- line 233: user override flips the device-type ladder so phone wins - -These are deterministic coverage gaps, not flakes, and are blocking under the -plan. - -### P2 — Browser project skips remain blocking - -The correctly configured browser matrix reported 96 skipped tests. These are -mostly deliberate project/viewport selection guards, including the canonical -five-image capture being desktop-only and the desktop sidebar skip-link check -being skipped on mobile. Plan 01-24 nevertheless says any skip is blocking. - -The first browser invocation is retained as diagnostic evidence. Its 40 -document/share failures were all `RangeError: Invalid key length` from -`e2e/setup/vault-fixture.ts:65`, matching the server's missing/invalid -encryption-key boot warning; one download timeout and one cross-project -“Today” state assertion accounted for the other two failures. Supplying the -repository's documented test key ring removed that environmental failure -class, leaving the three configured-run failures above. - -## Real-boundary coverage - -The full `pnpm test:integration` invocation includes the required MCP refresh -race, reset-password rollback/revocation, medication reminder midnight, -rolling-overdue consumers, Apple Health ECG import, Google Health workout -terminal/progress, and the remainder of the real-PostgreSQL integration suite. -No focused suite was substituted for the full run. - -## Axe/accessibility result - -The configured browser run's main `e2e/a11y.spec.ts` matrix completed with no -reported blocking axe violations: - -- Desktop: 11 passed, 0 failed, 0 skipped. -- Mobile/phone: 10 passed, 0 failed, 1 project-design skip (desktop sidebar - logo/skip-link interaction). -- Both light and dark themes passed the public login surface, Insights/workout/ - Coach matrix, measurement/mood/medication/lab/document matrix, retained - settings/admin matrix, and open OCR/Coach/medication/document states. -- The document-vault axe cases also passed all three desktop states. On mobile, - the vault timeline axe case passed while the open-sheet and bulk-state cases - were project-skipped. - -Thus no serious/critical WCAG blocker was emitted by executed axe scans, but -the accessibility gate cannot be declared green because Plan 01-24 treats its -skips as blocking. - -## Deterministic visual artifacts - -The configured rerun regenerated the following final files: - -| Artifact | Viewport | Final SHA-256 | -|---|---:|---| -| `test-results/v1341/dashboard-desktop.png` | 1440×900 | `f73649b68b048711be9ea2abd73b95a15b63d5b76d2390bef80648ffa93ad89f` | -| `test-results/v1341/insights-mobile.png` | 390×844 | `0c85df2fbcb721d4b96015e44306908e32c13f741a523cfdad97950b55fc0c3e` | -| `test-results/v1341/anamnesis-desktop.png` | 1440×900 | `dc02dfa25e579b59c1476f61f6f286a5b3f4e0d71fad862a7972addf16ccf694` | -| `test-results/v1341/settings-short-desktop.png` | 1280×600 | `10d83524d87bf7b4d5c962c3c304bd6e3b585b2363cd7f76f158b9defa2ed43a` | -| `test-results/v1341/admin-tablet.png` | 900×800 | `6776d340f777cb9003373d7a23436b18845eb980cda699df19b9168af1637aa3` | - -Manual review of the final pixels found no visible horizontal overflow, -unintended overlap, broken glyph, raw i18n key, or clipped primary control: - -- Dashboard desktop: sidebar, hero, four metric cards, ranges, and chart frame - align; the chart continues below the viewport as expected. -- Insights phone: the pill strip remains within the viewport, long pill text is - intentionally truncated, the score card is readable, and fixed bottom/Coach - controls remain usable. -- Anamnesis desktop: both navigation columns align, all access toggles remain - inside the card, and the lower form scrolls naturally below the viewport. -- Settings short desktop: the global and settings navigation stay pinned and - the account cards remain contained at 600 px height. -- Admin tablet: condensed global rail, admin navigation, host chart, and status - cards remain contained and aligned at 900 px. - -There is a determinism caveat: two successive captures on the same candidate -produced different hashes for Dashboard -(`173622c4…` then `f73649b6…`) and Admin -(`2c93cb58…` then `6776d340…`) because their pixels include live “Just in” / -started times and live host metrics. The other three hashes were identical -between runs. The required named artifacts exist and were reviewed, but the -Dashboard/Admin files are not byte-deterministic. - -## Non-blocking observations - -Lint completed with six warnings and no errors: - -- `_request` in `src/app/api/google-health/sync/status/route.ts` -- four warnings in `safe-fetch-pinned.test.ts` -- `_args` in `sync-progress.test.ts` - -The high-memory build also emitted the existing Next.js -`middlewareClientMaxBodySize` deprecation and repeated missing `metadataBase` -warnings. - -## Exit decision - -The merged technical candidate is **not releasable**. At minimum, the clinician -shared-document HTTP 500, UTC unit failures/skips, 10 formatting failures, -94 KB bundle-budget overage, locked-build OOM, integration skips, configured -browser download failure, and browser project skips must be resolved and the -entire frozen-snapshot gate rerun with zero failures, zero skips, and zero -flakes before any version, PR, tag, image, or deployment action. diff --git a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-SECURITY-FOCUSED-GATE.md b/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-SECURITY-FOCUSED-GATE.md deleted file mode 100644 index a81e39791..000000000 --- a/.planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-SECURITY-FOCUSED-GATE.md +++ /dev/null @@ -1,107 +0,0 @@ -# Phase 01 focused security gate - -**Date:** 2026-07-29 -**Started:** 2026-07-29T12:01:29Z -**Source commit:** `ddbfcb4588f22f29f807a42c437847de9e811b24` -**Branch:** `release/v1.34.1` -**Verdict:** **GREEN — all focused security boundaries pass with zero skipped tests** - -## Results - -| Boundary | Command | Files | Passed | Failed | Skipped | Result | -| --- | --- | ---: | ---: | ---: | ---: | --- | -| Focused unit/component gate | `TZ=UTC pnpm exec vitest run <20 security files>` | 20 | 305 | 0 | 0 | Pass | -| Focused real-PostgreSQL gate | `pnpm exec vitest run --config vitest.integration.config.mts <5 security files>` | 5 | 46 | 0 | 0 | Pass | -| Types | `pnpm typecheck` | — | — | 0 | 0 | Pass | -| Focused lint | `pnpm exec eslint <19 exact security paths>` | 19 | — | 0 errors | 0 skipped | Pass (4 pre-existing test-fixture warnings) | - -The first draft of Plan 01-09 inserted a standalone `--` after Vitest's `run` -command. With Vitest 4.1.5 that did not forward the following paths as a file -allowlist; both package scripts selected their entire configured test catalog -instead of the named files. The planner corrected the commands to the direct -forms shown above, which are the exact revised Plan 01-09 gate commands. - -For auditability, the unintended repository-wide unit run completed with 1,629 -files and 18,918 tests: 18,905 passed, 1 failed, and 12 unrelated tests were -skipped. Its only failure was the out-of-scope -`src/__tests__/i18n-reverse-coverage.test.ts`, which found the two unused -locale keys `settings.nightscoutPrivateHost` and -`settings.nightscoutPrivateHostHelp`. The obsolete chained command stopped -before PostgreSQL, typecheck, and lint. No locale file or out-of-scope source -was edited. The corrected focused commands above contain no test skip or -failure and are the authoritative security evidence. - -The exact revised ESLint allowlist exits zero. Its only diagnostics are four -pre-existing `@typescript-eslint/no-unused-vars` warnings in -`src/lib/__tests__/safe-fetch-pinned.test.ts`; there are no lint errors. - -## Requirement evidence - -| Requirement | Legitimate-positive boundary | Attacker-negative boundary | Evidence | -| --- | --- | --- | --- | -| SEC-01 | `health:read` and `*` tokens register and invoke MCP read tools. | Empty, write-only, FHIR, ingest, unrelated, revoked, expired, and unknown tokens fail before tool registration or health-data access. | MCP auth/scopes/route and real bearer-scope suites | -| SEC-02 | Public immutable assets and the data-free offline shell remain available; allowed query snapshots restore. | Authenticated root HTML is never precached, stale navigation shells are refused, logout removes current and legacy HealthLog caches, and sensitive query families are never persisted. | Service-worker, SW-hardening, and query-persister suites | -| SEC-03 | Genuinely public IPv4 and IPv6 literals and DNS results pass through the pinned dispatcher. | Private IPv4 embedded in mapped, compatible, NAT64, and 6to4 IPv6 forms is rejected for literal and resolved destinations; redirect and rebinding paths remain pinned. | Notifications, dispatcher, and pinned-fetch suites | -| SEC-04 | Sequential MCP refresh rotation produces one usable successor. | Concurrent replay transactionally revokes the connection family and linked access tokens; the losing branch and every successor are unusable. | OAuth token/connections unit suites and real PostgreSQL refresh-race suite | -| SEC-05 | Fresh password, TOTP, primary-passkey, and MFA-WebAuthn proofs can enroll a passkey from the same cookie session. | Bearer, missing/stale proof, foreign session/user, wrong-purpose, expired, and replayed challenges cannot create a credential. | Passkey route/library/component suites and real PostgreSQL registration suite | -| SEC-06 | Public Nightscout origins and an exact operator-approved private `scheme://host:port` origin connect and sync. | The legacy user opt-in, mismatched scheme/port/host, related hostname, unapproved private DNS, and unsafe redirects do not grant egress. | Nightscout validation/client/sync/route/card suites | -| SEC-07 | A valid operator reset changes the target password and revokes sessions, API tokens, refresh tokens, trusted devices, and elevations while preserving unrelated users. | Weak, unauthorized, missing-user, and injected-failure paths do not leave partial password or credential-family state. | Real PostgreSQL operator-reset and admin-reset suites | -| SEC-08 | All named final enforcement boundaries retain their public or authorized positive behavior. | The focused 20-file and 5-file runs contain zero failures, zero skips, and no surviving replay/session/race branch. | 305 unit/component plus 46 PostgreSQL tests | - -## Corrected command deviation - -No security-source fix was required. The planner corrected the test invocation -after the first draft's wrapper syntax selected the full catalogs. The -unrelated reverse-i18n orphan keys remain outside Plans 01-05 through 01-08 -and were not edited here; Plan 23 owns locale reconciliation. - -This focused gate is green but does not authorize release. The final -merged-tree release gate must supersede it. - -## Security scope freeze - -The immutable execution baseline was recomputed immediately before this -attestation and matched exactly: - -| Baseline item | Required and observed value | -| --- | --- | -| Branch | `release/v1.34.1` | -| Binary package/workspace diff SHA-256 | `b1206decc31f09b621676ac558919caf23a2190869d948fbc585fa3f38170f95` | -| `package.json` SHA-256 | `7a6dbc488beaf210808bf959b84717be462781f2aca74a5fbc157699d7b3668a` | -| `pnpm-lock.yaml` SHA-256 | `d9fa8c99e86eb4977f55395c43ca2c1781de26400870003c783e7112405970d6` | -| `pnpm-workspace.yaml` SHA-256 | `979d04c351ba838e6c8da97aa34545c9a76513710f86d84f6c07d1289d0cd054` | - -The package/workspace changes remain the user-owned -`@openai/codex-security` installation baseline. Plan 01-09 did not reinstall, -upgrade, normalize, stage, or otherwise change those files. - -The security-remediation scope is frozen as follows: - -- no migration is added for the security work at this gate; -- no package, lockfile, or workspace change is made; -- no dependency upgrade is performed; -- no Deep scan is run; and -- the five accepted Low scanner findings receive no code change in v1.34.1. - -Plan 21 may later add only the safe additive -`0287_google_health_sync_progress` migration for GH-07: one nullable JSONB -`sync_progress` column on `google_health_connections`, with bounded -current-run-only status. That planned correctness migration is not a security -remediation and does not relax this freeze. - -Plan 24 still requires the final merged standard working-tree security scan -and dated dependency reconciliation. Until that later gate is green, the -current scanner and dependency evidence is provisional. - -## Superseding execution sequence - -1. Complete this focused security gate first. -2. Wave 3 establishes RED contracts in Plans 10–13. -3. Wave 4 establishes the cohort contract and baseline, then implements the - disjoint UI/navigation work in Plans 14–17. -4. Wave 5 implements correctness, Apple Health, AI, and Google Health work in - Plans 18–21. -5. Wave 6 implements cohort behavior in Plan 22. -6. Wave 7 reconciles locale copy in Plan 23. -7. Only Plan 24's merged-tree re-audit supersedes the pre-implementation - correctness, live, and performance reports and can authorize release. diff --git a/.planning/todos/pending/2026-07-29-remediate-deferred-dependency-advisories-in-v1-34-2.md b/.planning/todos/pending/2026-07-29-remediate-deferred-dependency-advisories-in-v1-34-2.md deleted file mode 100644 index 47df4ab3f..000000000 --- a/.planning/todos/pending/2026-07-29-remediate-deferred-dependency-advisories-in-v1-34-2.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -created: 2026-07-29T14:44:48.825Z -title: Remediate deferred dependency advisories in v1.34.2 -area: tooling -files: - - package.json - - pnpm-lock.yaml - - pnpm-workspace.yaml - - .planning/phases/01-v1-34-1-dashboard-insights-records-admin-and-integration-reliability/01-DEPENDENCY-AUDIT.md ---- - -## Problem - -The dated v1.34.1 audit reports 19 transitive advisories: 9 High, 9 Moderate -and 1 Low. The High records are currently confined to lint, test, build and -bundle-analysis tooling. Five known Moderate Hono/@hono/node-server/Valibot -records remain in the production dependency graph, although HealthLog does not -directly import their affected adapters, JSX utilities or flattening paths. - -The release owner explicitly decided not to churn dependencies during the -already-gated v1.34.1 point release. The exact advisory IDs, installed versions, -roots and reachability assessment are recorded in `01-DEPENDENCY-AUDIT.md`. - -## Solution - -Prepare v1.34.2 immediately after v1.34.1: - -1. Upgrade the direct roots or use the narrowest safe transitive resolution for - Hono, @hono/node-server, Valibot, ESLint, Testcontainers, Tailwind/PostCSS, - bundle analyzer and Vite/esbuild. -2. Do not apply blind overrides where the owning package has not declared a - compatible range. -3. Re-run MCP/Prisma boundary tests, all unit and real-PostgreSQL integration - suites, production build/bundle, desktop/mobile Playwright and a final - Standard Codex Security working-tree scan. -4. Re-run `pnpm audit --json` and reconcile every advisory by ID and resolved - production reachability. -5. Preserve the intentional `@openai/codex-security` installation while - reviewing the package/lock/workspace delta independently from v1.34.1. diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c7ccb220..bcb5e8857 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,46 +4,42 @@ ## [1.34.1] — 2026-07-29 -Security and reliability point release. This release preserves the existing -authentication, consent and health-data boundaries while hardening the MCP -scope contract, refresh replay, passkey step-up, SSRF/private-origin checks, -password-reset revocation and PWA cache isolation. +Security and reliability point release. -### Highlights +### Added -- Google Health now shows bounded per-resource progress and terminal outcomes; - workouts resolve to canonical WHOOP-enriched records without synthetic pulse - curves or duplicate full syncs. -- Medication reminders retain unresolved rolling occurrences as overdue until - their exact actionable band ends; exact occurrence identity prevents repeats - and duplicate intakes. -- Intake imports now distinguish source-file capability, completed writes and - already-recorded replays. Bounded row details never expose medication facts, - credentials, keys or raw health rows. -- Coach follow-ups and Hero numbers use the current authoritative evidence; - future/invalid arrival timestamps cannot win freshness selection. -- Shared documents, native JSON downloads, public pages under nonce CSP and - authenticated PWA cache policy are covered by desktop/mobile regression and - accessibility gates. +- Google Health imports now show bounded progress and a clear final result. +- Workouts keep their provider details and can be enriched with available WHOOP + data without inventing pulse curves. +- Medication reminders retain overdue occurrences until their actionable window + has ended and identify each occurrence uniquely. +- Intake imports report completed writes and already-recorded entries clearly. -### Boundaries and residuals +### Changed -- Nightscout private-network access now requires the exact operator-approved - origin and pinned final socket. This is a breaking policy for arbitrary - user-selected private origins. -- Apple ECG remains encrypted, bounded and source-labelled; WHOOP ECG is not - inferred. WHOOP pulse aggregates do not create a sample curve. -- Antonios's provider-specific Google full-sync error remains an open reporter - confirmation; this release does not claim that exact external incident is - resolved. -- The five accepted Low paths remain documented in the audit ledger: - Coach plaintext titles/provenance, GlitchTip capability URLs, Nightscout - token-warning logs and Codex response-body/PHI logs. The Nightscout warning - sink is incidentally mitigated by bounded cohort logging in this tree, but - still needs a dedicated regression before it is considered closed. -- Dependency advisories are intentionally deferred to v1.34.2. The dated audit - records 19 transitive advisories (9 High, 9 Moderate, 1 Low) without changing - the dependency tree in this release. +- Dashboard scores, follow-up suggestions and hero metrics use the latest + authoritative readings. +- Public pages, documents and downloads now behave consistently on desktop and + mobile, with tighter spacing and clearer empty states. +- The administration and settings areas use a more consistent navigation and + grouping structure. + +### Fixed + +- Prevented duplicate medication intakes and reminders for the same occurrence. +- Fixed stale or future readings replacing newer measurements. +- Fixed cache isolation for authenticated PWA pages. +- Fixed passkey step-up, password-reset session revocation and refresh replay + edge cases. +- Restricted private-network integration requests to approved origins and final + sockets. + +### Security + +- Hardened MCP read permissions, refresh handling, passkey verification and + server-side request validation. +- Apple ECG data remains encrypted and source-labelled. WHOOP ECG is not + inferred from pulse data. ## [1.34.0] — 2026-07-28 diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml index 52fd9d8aa..d6817840d 100644 --- a/docs/api/openapi.yaml +++ b/docs/api/openapi.yaml @@ -3363,6 +3363,7 @@ paths: - hiit - dance - golf + - badminton - tennis - basketball - soccer diff --git a/messages/de.json b/messages/de.json index 33381547f..b3e350ba5 100644 --- a/messages/de.json +++ b/messages/de.json @@ -3120,6 +3120,7 @@ "hiit": "HIIT", "dance": "Tanz", "golf": "Golf", + "badminton": "Badminton", "tennis": "Tennis", "basketball": "Basketball", "soccer": "Fußball", diff --git a/messages/en.json b/messages/en.json index c899eeca1..989eea5b8 100644 --- a/messages/en.json +++ b/messages/en.json @@ -3120,6 +3120,7 @@ "hiit": "HIIT", "dance": "Dance", "golf": "Golf", + "badminton": "Badminton", "tennis": "Tennis", "basketball": "Basketball", "soccer": "Soccer", diff --git a/messages/es.json b/messages/es.json index b43859020..d245c47c4 100644 --- a/messages/es.json +++ b/messages/es.json @@ -3120,6 +3120,7 @@ "hiit": "HIIT", "dance": "Baile", "golf": "Golf", + "badminton": "Bádminton", "tennis": "Tenis", "basketball": "Baloncesto", "soccer": "Fútbol", diff --git a/messages/fr.json b/messages/fr.json index 6a53fac75..9044978ac 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -3120,6 +3120,7 @@ "hiit": "HIIT", "dance": "Danse", "golf": "Golf", + "badminton": "Badminton", "tennis": "Tennis", "basketball": "Basket", "soccer": "Football", diff --git a/messages/it.json b/messages/it.json index 773eefecd..fd21b5691 100644 --- a/messages/it.json +++ b/messages/it.json @@ -3120,6 +3120,7 @@ "hiit": "HIIT", "dance": "Danza", "golf": "Golf", + "badminton": "Badminton", "tennis": "Tennis", "basketball": "Pallacanestro", "soccer": "Calcio", diff --git a/messages/pl.json b/messages/pl.json index c19f6064e..9d48e9cab 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -3120,6 +3120,7 @@ "hiit": "HIIT", "dance": "Taniec", "golf": "Golf", + "badminton": "Badminton", "tennis": "Tenis", "basketball": "Koszykówka", "soccer": "Piłka nożna", diff --git a/src/app/insights/__tests__/assessment-spine-order.test.ts b/src/app/insights/__tests__/assessment-spine-order.test.ts index 66fb9f720..dd121c8a5 100644 --- a/src/app/insights/__tests__/assessment-spine-order.test.ts +++ b/src/app/insights/__tests__/assessment-spine-order.test.ts @@ -176,11 +176,11 @@ describe("assessment presentation contract", () => { ); expect(cardSource).toContain( - 'className="animate-insight-in gap-2 py-3 md:py-4"', + 'className="animate-insight-in gap-1.5 py-3 md:py-4"', ); expect(cardSource).toContain('className="text-foreground text-sm"'); expect(moodSource).toContain( - 'className="animate-insight-in gap-2 py-3 md:py-4"', + 'className="animate-insight-in gap-1.5 py-3 md:py-4"', ); expect(moodSource).toContain('data-slot="insight-assessment"'); expect(moodBodySource).toContain( diff --git a/src/app/insights/page-client.tsx b/src/app/insights/page-client.tsx index 439522754..772d30b99 100644 --- a/src/app/insights/page-client.tsx +++ b/src/app/insights/page-client.tsx @@ -26,6 +26,7 @@ import { QueryErrorCard } from "@/components/ui/query-error-card"; import { Skeleton } from "@/components/ui/skeleton"; import { cn } from "@/lib/utils"; import { HeroStrip } from "@/components/insights/hero-strip"; +import { HealthScoreCard } from "@/components/insights/health-score-card"; import { useInsightsAdvisorQuery } from "@/components/insights/use-insights-advisor"; import { useAnalyticsQuery } from "@/lib/queries/use-analytics-query"; import { useDashboardSnapshot } from "@/lib/queries/use-dashboard-snapshot"; @@ -474,36 +475,41 @@ export default function InsightsPageClient() { /> ), "daily-briefing": flags.briefing ? ( - + <> + + {analytics?.healthScore ? ( + + ) : null} + ) : null, vitals: , trends: ( diff --git a/src/components/daily/__tests__/today-hero.test.tsx b/src/components/daily/__tests__/today-hero.test.tsx index f21a9ae03..00ca9ccc4 100644 --- a/src/components/daily/__tests__/today-hero.test.tsx +++ b/src/components/daily/__tests__/today-hero.test.tsx @@ -175,6 +175,28 @@ describe("", () => { expect(html).toContain('data-slot="today-hero-lead"'); }); + it("promotes the top signal when score de-duplication removes the only lead", () => { + const html = render( + , + ); + + expect(html).toContain('data-slot="today-hero-lead"'); + expect(html).toContain("Pulse is settling lately"); + expect(html).not.toContain('data-slot="today-hero-signal"'); + }); + it("does not invent a score narrative while the digest is provisional", () => { const html = render( {/* Hero numeric face: the read leads large in the foreground token, calm and legible — the day's read, not a slogan. */} - {lead ? ( + {displayLead ? (
- +
) : null} {/* Top signal — present-tense headline + optional delta, one muted step down so it supports the lead without competing. */} - {topSignal ? ( + {topSignal && lead ? (

", () => { healthScore={SCORE_REPORT} />, ); - expect(html).toMatch(/data-slot="health-score-card"/); + expect(html).toMatch(/data-slot="health-score-card-compact"/); expect(html).toMatch(/data-band="green"/); expect(html).toContain(">86<"); }); @@ -406,7 +406,7 @@ describe("", () => { healthScore={SCORE_REPORT} />, ); - expect(html).toContain('data-slot="health-score-card"'); + expect(html).toContain('data-slot="health-score-card-compact"'); expect(html).not.toContain('data-slot="health-score-card-skeleton"'); }); }); diff --git a/src/components/insights/__tests__/insight-status-card.test.tsx b/src/components/insights/__tests__/insight-status-card.test.tsx index d40928eb2..85ac75a17 100644 --- a/src/components/insights/__tests__/insight-status-card.test.tsx +++ b/src/components/insights/__tests__/insight-status-card.test.tsx @@ -183,11 +183,15 @@ describe("", () => { expect(empty).toContain("No assessment yet."); expect(populated).toContain('data-slot="insight-assessment"'); - for (const html of [loading, preparing, noProvider, empty, populated]) { + for (const html of [loading, preparing, noProvider, empty]) { expect(html).toContain("gap-2"); expect(html).toContain("py-3"); expect(html).toContain("md:py-4"); } + expect(populated).toContain("gap-1.5"); + expect(populated).toContain("space-y-1.5"); + expect(populated).toContain("py-3"); + expect(populated).toContain("md:py-4"); expect(populated).toContain("text-foreground"); }); }); diff --git a/src/components/insights/health-score-card.tsx b/src/components/insights/health-score-card.tsx index a4e7b7ee1..ec642dc1e 100644 --- a/src/components/insights/health-score-card.tsx +++ b/src/components/insights/health-score-card.tsx @@ -14,6 +14,7 @@ import type { } from "@/lib/analytics/score/types"; import { useTranslations } from "@/lib/i18n/context"; import { cn } from "@/lib/utils"; +import { ScoreRing } from "@/components/insights/derived/score-ring"; const PILLAR_LABEL_KEY: Record = { BLOOD_PRESSURE: "insights.healthScore.pillar.bloodPressure", @@ -83,6 +84,8 @@ export interface HealthScoreCardProps { negative: string[]; } | null; returnToBand?: { metricLabel: string; daysInside: number } | null; + /** Keep only the score face when the card is used above the fold. */ + compact?: boolean; className?: string; } @@ -90,6 +93,7 @@ export function HealthScoreCard({ report, tension, returnToBand, + compact = false, className, }: HealthScoreCardProps) { const { t } = useTranslations(); @@ -223,6 +227,36 @@ export function HealthScoreCard({ ? 1 / eligibleCount : 0, })); + if (compact) { + return ( +

+ + {composite.status === "ok" ? ( +

+ {t(`insights.healthScore.band.${composite.value.band}`)} +

+ ) : ( +

+ {t("insights.healthScore.insufficient", { + count: composite.coverage.presentInputs, + })} +

+ )} +
+ ); + } return (
)} diff --git a/src/components/insights/insight-status-card.tsx b/src/components/insights/insight-status-card.tsx index 6718a4178..82c9b2f11 100644 --- a/src/components/insights/insight-status-card.tsx +++ b/src/components/insights/insight-status-card.tsx @@ -264,7 +264,7 @@ export function InsightStatusCard({ // v1.12.2 — stable hook for the spine guard test, which asserts the // assessment is the LAST content block on every bespoke metric page. data-slot="insight-assessment" - // Compact card density (`gap-2 py-3 md:py-4`) — the sanctioned tight + // Compact card density (`gap-1.5 py-3 md:py-4`) — the sanctioned tight // tier, so the assessment shares one header→body rhythm with the // target summary, the read strip, and the stat strip on the same // spine. Earlier bespoke values (`gap-1.5` + `md:py-5`) predate the @@ -272,7 +272,7 @@ export function InsightStatusCard({ // lands on every status variant so the gap is consistent across // states. (The one-time purple left accent stays dropped — the plain // card border carries enough separation.) - className="animate-insight-in gap-2 py-3 md:py-4" + className="animate-insight-in gap-1.5 py-3 md:py-4" > {/* v1.11.5 — the top-right "cached" label was removed: it surfaced @@ -298,7 +298,7 @@ export function InsightStatusCard({ } /> - + {/* v1.4.27 — defence-in-depth strip. Cached status text from pre-v1.4.27 rows can still carry literal `metric:` tokens the model embedded on the assumption a chart would diff --git a/src/components/insights/metric-stat-strip.tsx b/src/components/insights/metric-stat-strip.tsx index ab25b0c48..0000a5008 100644 --- a/src/components/insights/metric-stat-strip.tsx +++ b/src/components/insights/metric-stat-strip.tsx @@ -197,7 +197,7 @@ function SeriesBlock({ data-slot="metric-stat-series" data-series={dataKey} data-windowed={windowed ? "true" : undefined} - className="space-y-2" + className="space-y-1.5" > {seriesLabel ? ( ) : null} {description ? ( -

- {description} -

+

{description}

) : null}
{cells.map((cell) => ( @@ -260,7 +258,7 @@ export function MetricStatStrip({ data-multi-series="true" role="group" aria-label={groupLabel ?? t("insights.subPage.stats.label")} - className="gap-2 py-3 md:py-4" + className="gap-1.5 py-3 md:py-4" >
@@ -293,7 +291,7 @@ export function MetricStatStrip({ ? `${t("insights.subPage.stats.label")} — ${seriesLabel}` : t("insights.subPage.stats.label") } - className="gap-2 py-3 md:py-4" + className="gap-1.5 py-3 md:py-4" > {/* Row 1: the canonical tile header — a single-word "Ziel" / "Target" heading (the range numbers live on the range bar below, so the diff --git a/src/components/insights/mood/mood-insights-sections.tsx b/src/components/insights/mood/mood-insights-sections.tsx index 19a5156e0..054bc861a 100644 --- a/src/components/insights/mood/mood-insights-sections.tsx +++ b/src/components/insights/mood/mood-insights-sections.tsx @@ -288,7 +288,7 @@ export function MoodInsightsSections({ // v1.13.1 — match the canonical `gap-1.5` + `pb-1` heading-to-body // rhythm the other assessment cards use, so the heading sits tight // above its body instead of floating ~16-24 px above it. - className="animate-insight-in gap-2 py-3 md:py-4" + className="animate-insight-in gap-1.5 py-3 md:py-4" > ) : null} {description && !explainerMetric ? ( -

{description}

+

{description}

) : null} {/* v1.12.6 — the Min / Max / Median / Mittelwert stat strip LEADS diff --git a/src/lib/google-health/__tests__/mappers.test.ts b/src/lib/google-health/__tests__/mappers.test.ts index 2eacc6d0a..c32d36c88 100644 --- a/src/lib/google-health/__tests__/mappers.test.ts +++ b/src/lib/google-health/__tests__/mappers.test.ts @@ -41,6 +41,13 @@ import { mapWristTemperature, } from "../client"; +describe("mapGoogleHealthSportType", () => { + it("keeps Badminton as a first-class workout sport", () => { + expect(mapGoogleHealthSportType("BADMINTON")).toBe("badminton"); + expect(mapGoogleHealthSportType("Badminton")).toBe("badminton"); + }); +}); + describe("mapWeight — spot sample", () => { it("maps a weightGrams sample to a WEIGHT reading in kg anchored on its physical time", () => { const rows = mapWeight({ diff --git a/src/lib/google-health/mappers.ts b/src/lib/google-health/mappers.ts index df5de3049..a0684fbaa 100644 --- a/src/lib/google-health/mappers.ts +++ b/src/lib/google-health/mappers.ts @@ -1027,6 +1027,7 @@ const GOOGLE_HEALTH_EXERCISE_TYPE_MAP: Record = { dance: "dance", dancing: "dance", golf: "golf", + badminton: "badminton", tennis: "tennis", basketball: "basketball", soccer: "soccer", diff --git a/src/lib/measurements/__tests__/pick-canonical-workout-rows.test.ts b/src/lib/measurements/__tests__/pick-canonical-workout-rows.test.ts index 131aebb9e..be2f4f13d 100644 --- a/src/lib/measurements/__tests__/pick-canonical-workout-rows.test.ts +++ b/src/lib/measurements/__tests__/pick-canonical-workout-rows.test.ts @@ -6,6 +6,8 @@ interface RowFixture { id: string; userId?: string; startedAt: Date; + endedAt?: Date; + durationSec?: number; sportType: string; source: "APPLE_HEALTH" | "WHOOP" | "WITHINGS" | "MANUAL" | "IMPORT"; avgHeartRate?: number | null; @@ -257,6 +259,24 @@ describe("pickCanonicalWorkoutRows", () => { }); }); + it("collapses repeated same-source sync rows with the same exact session", () => { + const base = { + startedAt: new Date("2026-07-29T12:54:00Z"), + endedAt: new Date("2026-07-29T13:02:00Z"), + durationSec: 480, + sportType: "strength", + source: "APPLE_HEALTH" as const, + }; + const result = pickCanonicalWorkoutRows([ + { ...base, id: "duplicate-a" }, + { ...base, id: "duplicate-b", avgHeartRate: 90 }, + { ...base, id: "duplicate-c" }, + ]); + + expect(result).toHaveLength(1); + expect(result[0].id).toBe("duplicate-b"); + }); + it("does not collapse or enrich rows belonging to different users", () => { const result = pickCanonicalWorkoutRows([ { diff --git a/src/lib/measurements/pick-canonical-workout-rows.ts b/src/lib/measurements/pick-canonical-workout-rows.ts index fd0567330..31fe6dce2 100644 --- a/src/lib/measurements/pick-canonical-workout-rows.ts +++ b/src/lib/measurements/pick-canonical-workout-rows.ts @@ -46,6 +46,9 @@ import { export interface WorkoutPickerRow { userId?: string; startedAt: Date; + /** Optional exact-session fields used to collapse repeated same-source sync rows. */ + endedAt?: Date; + durationSec?: number; sportType: string; source: MeasurementSource; avgHeartRate?: number | null; @@ -53,6 +56,49 @@ export interface WorkoutPickerRow { metadata?: unknown; } +/** + * HealthKit can resend the same session with a new external UUID. The source + * ladder correctly chooses Apple over WHOOP, but previously kept every Apple + * row in that winning bucket, which made the web list show the same workout + * two or three times. Only collapse rows when the exact session identity is + * available; fixtures and legacy callers without end/duration remain + * governed by the original bucket contract. + */ +function exactSessionKey(row: WorkoutPickerRow): string | null { + if (row.endedAt == null || row.durationSec == null) return null; + return [ + row.sportType, + row.startedAt.getTime(), + row.endedAt.getTime(), + row.durationSec, + ].join(":"); +} + +function rowRichness(row: WorkoutPickerRow): number { + return ( + (row.avgHeartRate != null ? 2 : 0) + + (row.maxHeartRate != null ? 2 : 0) + + (row.metadata != null ? 1 : 0) + ); +} + +function deduplicateWinningRows(rows: T[]): T[] { + const bySession = new Map(); + const passthrough: T[] = []; + for (const row of rows) { + const key = exactSessionKey(row); + if (key == null) { + passthrough.push(row); + continue; + } + const existing = bySession.get(key); + if (!existing || rowRichness(row) > rowRichness(existing)) { + bySession.set(key, row); + } + } + return [...passthrough, ...bySession.values()]; +} + /** * Bucket-slot length. Two workouts whose `startedAt` falls in the * same 5-minute slot AND share the same sportType are treated as the @@ -253,7 +299,9 @@ export function pickCanonicalWorkoutRows( continue; } - const winners = slot.rows.filter((row) => row.source === picked); + const winners = deduplicateWinningRows( + slot.rows.filter((row) => row.source === picked), + ); const whoopDonors = picked === "WHOOP" ? [] diff --git a/src/lib/validations/workout.ts b/src/lib/validations/workout.ts index 8352a8d4d..95fa195bb 100644 --- a/src/lib/validations/workout.ts +++ b/src/lib/validations/workout.ts @@ -33,6 +33,7 @@ export const workoutSportTypeEnum = z.enum([ "hiit", "dance", "golf", + "badminton", "tennis", "basketball", "soccer", diff --git a/src/lib/workouts/sport-icons.ts b/src/lib/workouts/sport-icons.ts index f126579a5..243a07e96 100644 --- a/src/lib/workouts/sport-icons.ts +++ b/src/lib/workouts/sport-icons.ts @@ -32,6 +32,7 @@ export const SPORT_TYPE_ICON: Record = { hiit: Activity, dance: Activity, golf: Activity, + badminton: Activity, tennis: Activity, basketball: Activity, soccer: Activity,