Files
HealthLog/.planning/STATE.md
T
Marc-André BombeckandClaude Opus 4.7 72829b1db5 docs(planning): record W5 hygiene wave + STATE tick
Eight commits closed all seven items from `.planning/v1422-backlog.md`.
STATE entry documents the per-item changes; new
`phase-W5-v1423-report.md` captures the H5 deploy decision, the H7
admin view path + the first useful question it answers, and the
OpenAPI registry additions (3 routes — H4 GET + PUT, H7 POST).

Test deltas: 2191 → 2223 unit (+32), +2 integration files, no
regressions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 01:52:46 +02:00

9.8 KiB

v1.4.23 marathon — state log

Status: Wave 5 shipped (7 hygiene items + 8 atomic commits) Last update: 2026-05-11T02:00+02:00

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 CRITICAL + HIGH; defer MED/LOW

Wave 7 — Release v1.4.23

  • Pre-release verify
  • Bump package.json 1.4.22 → 1.4.23 + CHANGELOG
  • Release-merge develop → main
  • Tag + push v1.4.23
  • GHCR build green
  • Coolify deploy (auto, if F8 works; else host-side fallback)
  • /api/version=1.4.23 confirmed
  • Production smoke + e2e workflow on main passes
  • GH release
  • Docs site + landing site sync
  • docs/audit/v1423-summary.md (release brief)
  • v1.5 P1 plan refresh recorded