v1.4.24 (security + accessibility hardening) shipped directly on main as a hotfix-style release. Back-merging the two main-only commits (ca03c60chore(release): v1.4.24,604009dper-user timezone proposal) into develop so the v1.4.25 work continues on the standard branch model. # Conflicts: # .planning/ROADMAP.md
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→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 — 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-dispatchdevice 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.tsNextRequestURL mock carryover from v1.4.22, documented inv1423-backlog.md, non-blocking) - Format sweep commit (
f1e6630prettier sweep ondevelop) + W1 research + W5/W6 review packs commit (766a9ae) + OpenAPI regen (4183552) - Bump
package.json1.4.22 → 1.4.23 + CHANGELOG (d2331d9chore(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.23confirmed - 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.mdrelease brief- v1.5 P1 plan refresh recorded
(
.planning/phase-W6-v1423-product-lead-review.mdsection C)