Files
HealthLog/.planning/STATE.md
T
Marc-André Bombeck ac54bb26e8 chore: sync develop with v1.4.24 release from main
v1.4.24 (security + accessibility hardening) shipped directly on main as
a hotfix-style release. Back-merging the two main-only commits
(ca03c60 chore(release): v1.4.24, 604009d per-user timezone proposal)
into develop so the v1.4.25 work continues on the standard branch model.

# Conflicts:
#	.planning/ROADMAP.md
2026-05-14 11:32:28 +02:00

11 KiB

v1.4.23 marathon — state log

Status: SHIPPED — v1.4.23 LIVE in prod Last update: 2026-05-11 (release wave)

Previous milestone: v1.4.22 live (image digest sha256:865154614303…, /api/version=1.4.22). v1.4.23 = pre-iOS prep + hygiene. Backend foundation so the iOS Health app can ship in v1.5 without contract churn, plus 7 hygiene items from .planning/v1422-backlog.md.

Wave 1 — Research (single agent, three streams)

  • Apple Health HKQuantityTypeIdentifier → MeasurementType mapping, with units + canonical aggregation rules
  • APNs Node library decision (apn vs @parse/node-apn vs raw HTTP/2) with maintenance / type-safety / footprint pros/cons
  • OpenAPI 3.1 generator tooling for Next.js routes (@asteasolutions/zod-to-openapi, ts-rest, next-rest, hand-rolled)
  • Detailed report: .planning/phase-W1-v1423-research.md

Wave 2 — Apple Health foundation (F1+F2+F3)

F1 — MeasurementType enum extension

  • Added HEART_RATE_VARIABILITY, RESTING_HEART_RATE, ACTIVE_ENERGY_BURNED, FLIGHTS_CLIMBED, WALKING_RUNNING_DISTANCE, VO2_MAX, BODY_TEMPERATURE (W1 recommended reusing ACTIVITY_STEPS + SLEEP_DURATION verbatim; the iOS DTO already shipped BODY_TEMPERATURE so that joined the additive set — net new = 7)
  • Prisma migration 0036_apple_health_measurement_types
  • Sleep unit shifted from hours to minutes; new SleepStage enum + nullable Measurement.sleepStage column (CHECK constraint scopes it to SLEEP_DURATION rows)
  • Composite unique index (user_id, type, source, external_id) — Apple Health batch dedup key
  • Unit conversions in src/lib/measurements/apple-health-mapping.ts (16 unit tests)

F2 — MeasurementSource enum expansion

  • APPLE_HEALTH appended to the enum
  • UI badge in measurement-list (Dracula-pink chip) + bilingual measurements.sourceAppleHealth keys + admin restore-route enum guard sync

F3 — POST /api/measurements/batch

  • New route at src/app/api/measurements/batch/route.ts, idempotent via Idempotency-Key
  • Validates batch ≤ 500 entries (returns 422 coach.batch.too_large)
  • Dedupes via (userId, type, source, externalId) composite
  • Returns per-entry status (inserted / duplicate / skipped) + idempotency-replay-safe
  • Integration test (tests/integration/measurements-batch.test.ts, 6 cases including idempotency replay)
  • Sleep-stage aggregation in /api/analytics: per-Berlin-day summary + sleepStages block (tests/integration/analytics-sleep-stages.test.ts)
  • Detailed report: .planning/phase-W2-v1423-report.md

Wave 3 — APNs scaffolding (F4)

  • Library install + lock-file (@parse/node-apn@8.1.0, W1 stream-2 decision)
  • src/lib/notifications/senders/apns.ts — sendApnsPush() one-shot helper plus sendViaApns(userId, payload) dispatcher entry mirroring the web-push contract
  • Env-var contract: APNS_KEY_ID, APNS_TEAM_ID, APNS_BUNDLE_ID, one of APNS_KEY / APNS_KEY_FILE, optional APNS_PRODUCTION — documented in .env.example, all-or-none guard with one-shot warning when partially set
  • Device Prisma model extension via migration 0037_apns_device_columns: nullable apnsToken + apnsEnvironment, paired CHECK constraint, single-column index on apns_token for the dispatcher fan-out lookup
  • Dispatcher wiring: APNS joins the cascade as channel-type 4; cascade order is now deterministic (APNs → Telegram → ntfy → Web Push)
  • Mock provider at src/lib/notifications/senders/__mocks__/apns.ts with queued per-token responses + recorded calls
  • Integration test tests/integration/apns-dispatch.test.ts — round-trip, hard-reject + cleanup, cascade fall-through (3 tests)
  • POST /api/devices accepts paired apnsToken + apnsEnvironment, enforces hex format, applies the cross-user-hijack guard at the APNs-token layer (409 apns_token_owned_by_other_user)
  • Detailed report: .planning/phase-W3-v1423-report.md

Wave 4 — OpenAPI + Coach schema + native auth + Coolify

F5 — OpenAPI 3.1 generator + CI gate

  • Tool wired (W1 decision — zod-openapi (samchungy) + yaml@^2)
  • pnpm openapi:generate script emits docs/api/openapi.yaml from Zod schemas
  • CI step compares generated vs committed; PR warns on drift (continue-on-error: true — flips to hard-fail in v1.4.24+)
  • Legacy hand-maintained spec preserved at docs/api/openapi-v1422-legacy.yaml during the incremental migration

F6 — Coach + Daily Briefing schema slot for new metrics

  • aiInsightResponseSchema.dailyBriefing.keyFindings[].sourceMetric enum extends to include the 9 Apple Health categories (hrv, sleep, resting_hr, steps, active_energy, flights, distance, vo2_max, body_temp)
  • trendAnnotations schema mirrors the same additive enum
  • coach/snapshot.ts queries the new measurement types when scope toggles them on; web-only accounts pay zero extra SQL
  • CoachProvenance.metrics + counts extended symmetrically
  • PROMPT_VERSION 4.22.0 → 4.23.0 with new GROUND RULE 12 ("treat Apple Health categories as silent when absent")
  • EN + DE prompt bodies, OUTPUT FORMAT block, and i18n strings all updated

F7 — Native API hardening (refresh-token per-device)

  • Refresh-token reuse-detection switches from "revoke all" to per-device-token revocation (legacy null-deviceId tokens fall back to user-wide revoke — safety hatch)
  • New route GET /api/auth/me/devices lists active devices with last-seen, label, channels, isCurrent marker
  • New route DELETE /api/auth/me/devices/[id] revokes one device (refresh + access tokens + Device row)
  • Alternate DELETE /api/devices/[id] for the iOS token-rotation cleanup path (mirror of the auth/me variant)
  • Ownership-boundary 404 on cross-user attempts

F8 — Coolify auto-deploy fix for real

  • COOLIFY_WEBHOOK + COOLIFY_TOKEN GitHub repo secrets documented (maintainer action; see .planning/coolify-auto-deploy-howto.md for the verbatim recipe)
  • Coolify "Watch image registry for new digests" UI toggle documented as the load-bearing piece (maintainer action — can't be flipped from CI)
  • Workflow step gains a ::notice:: line so future runs surface the deploy timestamp + sha without opening the verbose log
  • Verification recipe (curl /api/version | jq .data.version) + host-side fallback documented inline

Wave 5 — Hygiene (H1-H7)

  • H1 sentinel parser malformed-enum hardening (SentinelParseResult.malformedEntries[] + per-line typed reasons; chat route splits coach.keyvalues.parse_partial from the full-block coach.keyvalues.parse_failed)
  • H2 analytics-route unbounded findMany pagination (cursor-paged 5 000-row chunks via fetchBpSeriesChunked, analytics.bp_in_target.row_count wide-event meta for slow- query attribution; integration test seeds 6 000 rows across a chunk boundary)
  • H3 <CoachDrawer key={prefill}> controlled-prop refactor (useResettableValue hook + pure nextResettableValue helper; key={prefill} removed from the parent mount)
  • H4 per-user prompt-tuning surface (Coach settings cog return) (User.coachPrefsJson migration 0038_coach_prefs; new GET/PUT /api/auth/me/coach-prefs; settings cog opens a right-edge <Sheet>; system-prompt prepends a per-user OVERRIDE; snapshot reads prefs BEFORE measurement queries so excludeMetrics filters before the snapshot lands)
  • H5 schema drift on medication_schedules.days_of_week (decision: deploy — column is referenced by 9 source files across 4 user-visible surfaces; migration 0039_medication_schedule_days_of_week, NULL = daily)
  • H6 Pearson p-value normal-approx replacement (raised MIN_PAIRED_N from 14 → 20 — conservative patch; rigorous incomplete-beta queued as a v1.4.24 candidate)
  • H7 Coach helpful/unhelpful first-week observation view (polymorphic RecommendationFeedback.target_type migration 0040_recommendation_feedback_target_type; new POST /api/insights/chat/messages/:id/feedback; aggregator slice buildCoachFeedbackBuckets by (promptVersion, tone, verbosity); admin section /admin/coach-feedback with the H7 i18n bundle both EN + DE)

W5 commits (8): 58ae9bc, fa07748, 9413d29, 3f60c81, 0eda1de, 1faee95, 05c7f14, plus this STATE tick.

Test deltas: 2191 → 2223 unit (+32). Integration suite added 3 files: coach-prefs, coach-feedback, plus the 6000-row chunk boundary regression in the existing bp-in-target test.

OpenAPI registry coverage: +3 routes (GET / PUT /api/auth/me/coach-prefs, POST /api/insights/chat/messages/{id}/feedback).

Wave 6 — Multi-agent QA + Product-Lead review

  • code-reviewer
  • security review
  • design / UX review
  • senior-dev review
  • simplify
  • Product Lead — v1.5 P1 (iOS first launch) plan refresh with concrete file paths now that backend contracts exist
  • Reconcile — 1 CRITICAL + all 9 HIGH + 4 of 5 simplify + 3 MED applied; remainder triaged into .planning/v1423-backlog.md

W6 reconcile commits (11): 5486507, 13977bc, d5202e5, a2dfc5e (Session A — CRIT + HIGH 1-3); 3ee6dab, dfffe6b, d25e50e (Session B — HIGH 4 + S-02-S-04 simplify + HIGH 6); deadc73 (Session D — HIGH 5); 650f150 (Session E — HIGH 7); 1a46bfe (Session F — MED cluster); plus this STATE tick.

Detailed reconcile report: .planning/phase-W6-v1423-reconcile-report.md. Deferred items: .planning/v1423-backlog.md (settings-cog vs per-message-controls debate, rigorous Pearson incomplete-beta, surplus MEDs, all LOWs, S-05 simplify, pre-existing coach-prefs.test.ts mock issue, sandbox git commit no-op).

Test deltas: W5 ended at 2223 unit. Session A added the revoke.test.ts

  • apns-dispatch device tests landing at 2227. Session E ended at 2235. Session F added the forged-X-Device-Id regression — final at 2236 unit (+45 across the W5+W6 reconcile arc).

Wave 7 — Release v1.4.23

  • Pre-release verify (typecheck / lint / openapi:check / 2236 unit / 110 of 112 integration — 2 failures are the pre-existing coach-prefs.test.ts NextRequest URL mock carryover from v1.4.22, documented in v1423-backlog.md, non-blocking)
  • Format sweep commit (f1e6630 prettier sweep on develop) + W1 research + W5/W6 review packs commit (766a9ae) + OpenAPI regen (4183552)
  • Bump package.json 1.4.22 → 1.4.23 + CHANGELOG (d2331d9 chore(release): v1.4.23)
  • Release-merge develop → main (0dc0e16 Release v1.4.23)
  • Tag + push v1.4.23
  • GHCR build green
  • Coolify deploy + /api/version=1.4.23 confirmed
  • Production smoke (gated routes 307; /api/version=1.4.23)
  • GH release published
  • Docs site + landing site sync (image pins 1.4.22 → 1.4.23 + ai-insights v1.4.23 callout; softwareVersion JSON-LD bump)
  • docs/audit/v1423-summary.md release brief
  • v1.5 P1 plan refresh recorded (.planning/phase-W6-v1423-product-lead-review.md section C)