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>
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→MeasurementTypemapping, with units + canonical aggregation rules - APNs Node library decision (
apnvs@parse/node-apnvs 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 reusingACTIVITY_STEPS+SLEEP_DURATIONverbatim; the iOS DTO already shippedBODY_TEMPERATUREso that joined the additive set — net new = 7) - Prisma migration
0036_apple_health_measurement_types - Sleep unit shifted from hours to minutes; new
SleepStageenum + nullableMeasurement.sleepStagecolumn (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_HEALTHappended to the enum- UI badge in measurement-list (Dracula-pink chip) + bilingual
measurements.sourceAppleHealthkeys + admin restore-route enum guard sync
F3 — POST /api/measurements/batch
- New route at
src/app/api/measurements/batch/route.ts, idempotent viaIdempotency-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 +sleepStagesblock (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 plussendViaApns(userId, payload)dispatcher entry mirroring the web-push contract- Env-var contract:
APNS_KEY_ID,APNS_TEAM_ID,APNS_BUNDLE_ID, one ofAPNS_KEY/APNS_KEY_FILE, optionalAPNS_PRODUCTION— documented in.env.example, all-or-none guard with one-shot warning when partially set DevicePrisma model extension via migration0037_apns_device_columns: nullableapnsToken+apnsEnvironment, paired CHECK constraint, single-column index onapns_tokenfor the dispatcher fan-out lookup- Dispatcher wiring:
APNSjoins 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.tswith 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/devicesaccepts pairedapnsToken+apnsEnvironment, enforces hex format, applies the cross-user-hijack guard at the APNs-token layer (409apns_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:generatescript emitsdocs/api/openapi.yamlfrom 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.yamlduring the incremental migration
F6 — Coach + Daily Briefing schema slot for new metrics
aiInsightResponseSchema.dailyBriefing.keyFindings[].sourceMetricenum extends to include the 9 Apple Health categories (hrv, sleep, resting_hr, steps, active_energy, flights, distance, vo2_max, body_temp)trendAnnotationsschema mirrors the same additive enumcoach/snapshot.tsqueries the new measurement types when scope toggles them on; web-only accounts pay zero extra SQLCoachProvenance.metrics+countsextended 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/deviceslists 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_TOKENGitHub repo secrets documented (maintainer action; see.planning/coolify-auto-deploy-howto.mdfor 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 splitscoach.keyvalues.parse_partialfrom the full-blockcoach.keyvalues.parse_failed) - H2 analytics-route unbounded
findManypagination (cursor-paged 5 000-row chunks viafetchBpSeriesChunked,analytics.bp_in_target.row_countwide-event meta for slow- query attribution; integration test seeds 6 000 rows across a chunk boundary) - H3
<CoachDrawer key={prefill}>controlled-prop refactor (useResettableValuehook + purenextResettableValuehelper;key={prefill}removed from the parent mount) - H4 per-user prompt-tuning surface (Coach settings cog return)
(
User.coachPrefsJsonmigration0038_coach_prefs; newGET/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 soexcludeMetricsfilters 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; migration0039_medication_schedule_days_of_week, NULL = daily) - H6 Pearson p-value normal-approx replacement
(raised
MIN_PAIRED_Nfrom 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_typemigration0040_recommendation_feedback_target_type; new POST/api/insights/chat/messages/:id/feedback; aggregator slicebuildCoachFeedbackBucketsby (promptVersion, tone, verbosity); admin section/admin/coach-feedbackwith 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.json1.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