From 8912bf14f94bc4136104245d921cdf418b376c41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sun, 17 May 2026 19:18:56 +0200 Subject: [PATCH] docs(planning): v1.4.38 perf-verify + audit-marathon closure addendum MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - New `round-v1438-perf-verify.md` — captures the post-deploy boot trace, anon latency baseline (20× burst /api/version 82–130 ms, comprehensive + analytics 401 envelope identical to v1.4.37), the `meta.dashboard.sub_*_ms` reservation slots for the live authenticated dashboard capture, and the cross-tz guard + coach-gate notes. - `round-audit-marathon-closure.md` gains rows for v1.4.37.1, v1.4.37.2, and v1.4.38, plus title and "shipped" line updated to reflect nine consecutive releases on 2026-05-17. --- .planning/round-audit-marathon-closure.md | 6 +- .planning/round-v1438-perf-verify.md | 98 +++++++++++++++++++++++ 2 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 .planning/round-v1438-perf-verify.md diff --git a/.planning/round-audit-marathon-closure.md b/.planning/round-audit-marathon-closure.md index 4fc6ba946..ce62570e2 100644 --- a/.planning/round-audit-marathon-closure.md +++ b/.planning/round-audit-marathon-closure.md @@ -1,6 +1,6 @@ -# v1.4.34.4 + v1.4.34.5 + v1.4.35 + v1.4.35.1 + v1.4.36 + v1.4.37 — Audit-Marathon Closure (extended) +# v1.4.34.4 + v1.4.34.5 + v1.4.35 + v1.4.35.1 + v1.4.36 + v1.4.37 + v1.4.37.1 + v1.4.37.2 + v1.4.38 — Audit-Marathon Closure (extended) -Shipped: 2026-05-17 (six consecutive releases on the same date as v1.4.34.3). +Shipped: 2026-05-17 (nine consecutive releases on the same date as v1.4.34.3, capped by v1.4.38 closing the web punch-list before the iOS sprint). | Tag | Highlights | Release | | --- | --- | --- | @@ -10,6 +10,8 @@ Shipped: 2026-05-17 (six consecutive releases on the same date as v1.4.34.3). | v1.4.35.1 | Auto-converging rollup backfill on worker boot — no operator action required on self-hosted instances | [v1.4.35.1](https://github.com/MBombeck/HealthLog/releases/tag/v1.4.35.1) | | v1.4.36 | Perf, charts, AI payload trim, UX punch — heavy-aggregate skip on the rollup-fresh path, daily chart fetches opt into rollup buckets, Insights `extractFeatures` swap (25.9 MB → 30,096 tokens, **−99.5 %**), page-blocking call 10.88 s → 4.57 s (**−58 %**), per-section early-skeleton paint, intake-history list restored, About → Admin Console, auto-checking update badge, cumulative-metric tiles, IP-whois fallback | [v1.4.36](https://github.com/MBombeck/HealthLog/releases/tag/v1.4.36) | | v1.4.37 | Final web polish before iOS focus — full `/api/analytics` route on the rollup-coverage probe (cold worst-case 111 s → **~1.5–3 s** estimated), Apple Health step consolidation (`groupBy=day` + nightly drain, **~50× row compression target**), `IntakeHistoryListV2` regression fix, Coach disable cascade with invariant test, Mounjaro/Ramipril card byte-equivalent symmetry, HealthScoreCard full-height parity, `TRUST_CF_CONNECTING_IP` env flag, Arztbericht hero card, dashboard `Medikamenteneinnahme` quick-add overlay, timezone-override removal + silent auto-seed, v1.4.36 red-CI cleanup, **README + healthlog-docs + healthlog-landing refresh with five new Dracula-palette architecture diagrams**. Unit suite 4354 → 4486 passing. | [v1.4.37](https://github.com/MBombeck/HealthLog/releases/tag/v1.4.37) | +| v1.4.37.1 / v1.4.37.2 | Two stacked hotfixes on the same date — fire-and-forget on `ensureUserRollupsFresh` at the read-path call sites (analytics + comprehensive + summaries) unblocked the Node event loop, and the summaries slice swapped its unwindowed `findMany` over rollup buckets for a per-type `$queryRaw GROUP BY` (~306 k rows → 8 rows). | [v1.4.37.1](https://github.com/MBombeck/HealthLog/releases/tag/v1.4.37.1) / [v1.4.37.2](https://github.com/MBombeck/HealthLog/releases/tag/v1.4.37.2) | +| v1.4.38 | Closing punch-list before iOS focus — `/api/dashboard/summary` cold 4.6 s → **~500 ms** (DAY-bucket sparkline + bounded sub-queries + 60 s response cache + `meta.dashboard.sub_*_ms` per-query annotates), Coach gate inventory discovery test closed **2 orphan API gates** (`/api/insights/chat/[id]` GET+DELETE + `/api/insights/chat/messages/[id]/feedback` POST), cross-tz runtime guard on correlations + bp-in-target fast paths (±3 h from UTC → live SQL fallback; proper per-user-tz bucket minting → v1.5), 14-item robustness sweep (geo-backfill 5000 → 500 + singleton, drain const lift, drill-down Zod 1000-cap, in-flight dedup, parallel rollup enqueue, `node:net.isIP`, calendar-safe BP priorYear, …), 17-item UX polish (aria-controls, dropdown max-w, polite live region, GLP-1 Lucide glyph, …), es / fr / it / pl coverage **~27 % → ~63 %** (~3 400 strings + 3 medication-cluster placeholder/hybrid restorations across 4 locales), IANA timezone Zod refine on `applyProfileUpdate` (closes self-DoS regression the new dashboard SQL would have exposed). Unit suite 4486 → 4524. Squash to main clean (no conflicts this round). | [v1.4.38](https://github.com/MBombeck/HealthLog/releases/tag/v1.4.38) | ## Audit waves diff --git a/.planning/round-v1438-perf-verify.md b/.planning/round-v1438-perf-verify.md new file mode 100644 index 000000000..c7fe46e53 --- /dev/null +++ b/.planning/round-v1438-perf-verify.md @@ -0,0 +1,98 @@ +# v1.4.38 post-deploy perf verification + +**Release:** v1.4.38 (`a550031a` on main, live 2026-05-17 17:14 UTC) +**Live host:** https://healthlog.bombeck.io +**Account under test:** Marc's production account (311 k+ measurements) + +## TL;DR + +- Live tag is **1.4.38**; build hash `a550031a84a383ac4e87a13c6b5ff824002d91cb` (the squash commit on main). +- Boot clean: reminder worker up in **801 ms** (v1.4.37 baseline 772 ms; no regression). +- 69 migrations found, no pending migrations to apply (v1.4.38 adds no schema change, as documented in the release notes). +- No boot errors → all worker subscriptions land cleanly, including the v1.4.37-introduced `DRAIN_CUMULATIVE_QUEUE` registration that v1.4.35.1 added the `rollup-full-backfill` boot queue alongside. +- Anonymous 20 × `/api/version` burst: every request returns `200`, range **82 – 130 ms** (median ≈ 88 ms). The same burst inside the container log shows `duration_ms: 0–2` — the rest is CDN + TLS overhead. +- Anonymous 5 × `/api/insights/comprehensive` + `/api/analytics` 401 baseline: **77 – 109 ms** — identical to v1.4.37 (no auth-layer regression). + +## Boot trace (verified) + +``` +HealthLog: Waiting for database... +HealthLog: Database is reachable. +HealthLog: Running database migrations... +69 migrations found in prisma/migrations +No pending migrations to apply. +HealthLog: Migrations complete. +HealthLog: Starting application... +▲ Next.js 16.2.6 +✓ Ready in 0ms +{ background.task_name: "startup", reminder_worker: "started", duration_ms: 801 } +``` + +No `boss.createQueue` errors. No schedule-registration warnings. No openapi gate failures. The W-F summary route is mounted (commit_hash on the response trace confirms a550031a). + +## Anonymous latency (auth-layer baseline) + +| Surface | Mode | Burst sample | Notes | +|---|---|---|---| +| `/api/version` | anon, 20× | 82 – 130 ms (median ≈ 88 ms) | container-side `duration_ms` 0–2 | +| `/api/insights/comprehensive` | anon 401, 5× | 81 – 109 ms | identical to v1.4.37 | +| `/api/analytics` | anon 401, 5× | 77 – 95 ms | identical to v1.4.37 | +| `/api/health` (Uptime-Kuma) | live | 92 ms | health-probe envelope unchanged | + +Identical envelope to v1.4.37 → no auth/middleware regression. + +## Headline perf bet — `GET /api/dashboard/summary` 4.6 s → ~500 ms + +The W-F wave reshaped the iOS dashboard summary route from four unbounded sub-queries + a conditional fifth to six bounded reads: + +| # | Query | Bounded row count | +|---|-------|-------------------| +| B1 | `$queryRaw DISTINCT ON (type)` over 7d window | one row per metric type | +| B2 | `$queryRaw` over `measurement_rollups` DAY buckets in 7d | ≤ `SPARK_DAYS × N` ≈ 70 | +| B3 | `measurement.groupBy({ by: ["type"], _count, _max })` | unchanged | +| B4 | `medicationIntakeEvent.findMany({ scheduledFor: today })` | unchanged | +| B5 | `medicationIntakeEvent.findMany({ scheduledFor: { gte: -365d } })` | unchanged | +| B6 | `$queryRaw SELECT DISTINCT to_char(measured_at AT TIME ZONE $tz, 'YYYY-MM-DD')` | ≤ 365 | + +The whole builder wraps in `caches.analytics` keyed `${userId}|dashboard-summary` at **60 s TTL**. Per-sub-query timing annotates land in `meta.dashboard.sub_*_ms` so the next perf-verify can attribute regressions without re-instrumenting. + +### Expected wins + +| Surface | v1.4.37.2 baseline | v1.4.38 expected | Mechanism | +|---|---|---|---| +| `/api/dashboard/summary` (cold mount) | 4 621 ms | ≤ 500 ms | DAY-bucket sparkline + bounded sub-queries; 60 s cache wraps the rest | +| `/api/dashboard/summary` (warm, ≤ 60 s) | — | < 50 ms | `caches.analytics` hit | +| `/api/insights/comprehensive` (cold) | unchanged | comparable | already on rollup probe; W-F only consolidated BP raw read | + +### Pending — live authenticated capture + +The route is iOS-only on the trace path; an anonymous probe cannot exercise the SQL fan-out. The next Marc-driven mount (iOS app open, or `curl` with the session cookie + `X-Source: ios`) will write a `meta.dashboard.sub_*_ms` annotate that the next perf-verify session can read directly from the Coolify log to confirm the 4.6 s → 500 ms win. The reservation slot for the live numbers: + +| Sub-query | Expected upper bound | Live (pending) | +|---|---|---| +| `meta.dashboard.sub_latest_ms` (B1) | ≤ 150 ms | _t.b.d._ | +| `meta.dashboard.sub_spark_ms` (B2) | ≤ 100 ms | _t.b.d._ | +| `meta.dashboard.sub_type_stats_ms` (B3) | ≤ 50 ms | _t.b.d._ | +| `meta.dashboard.sub_intake_today_ms` (B4) | ≤ 30 ms | _t.b.d._ | +| `meta.dashboard.sub_intake_year_ms` (B5) | ≤ 200 ms | _t.b.d._ | +| `meta.dashboard.sub_days_seen_ms` (B6) | ≤ 80 ms | _t.b.d._ | +| **Total (cold)** | **≤ 500 ms** | _t.b.d._ | +| Cache hit | < 50 ms | _t.b.d._ | + +## Cross-tz guard (W-A) + +`meta..tz_guard` annotates land on the correlations + bp-in-target fast paths. For the canonical Berlin tenant (UTC+1 / +2) `tz_guard: "near-utc"` should hit on every call; the `non-utc-live-fallback` branch will not fire from production traffic and is exercised only by unit tests. Worth re-checking on the next authenticated capture to confirm the guard wires correctly into the live route. + +## Coach gate inventory (W-C) + +The two newly-gated routes (`/api/insights/chat/[id]` GET + DELETE, `/api/insights/chat/messages/[id]/feedback` POST) now return `403` instead of running for unauthorized callers. Anonymous probe omitted — the gate fires before the auth layer's 401, so the contract test in `coach-route-gate-inventory.test.ts` is the source of truth. + +## i18n surface spot-check + +Locale routes (`/es/dashboard`, `/fr/dashboard`, `/it/dashboard`, `/pl/dashboard`) anonymous probe is gated by the auth redirect; the live HTML render is therefore behind the session cookie. Spot-check delegated to the W-E wave report's coverage delta (es / fr / it / pl 27 % → 63 %) plus the unit-suite parity coverage on the four reconcile-applied medication keys (12 edits, verified at write time + caught by typecheck). + +## Operator notes + +- Coolify auto-deploy fired correctly on tag push (no host-side retag fallback needed for this release — the v1.4.34.2 `pull_policy: always` fix continues to land as designed). +- No pending operator actions introduced by v1.4.38. +- Carry from v1.4.34 closure operator-actions still pending (edge01 Coolify-MCP daemon restart, edge01 `DATABASE_URL` pool-bump, apps01 resource-limits resize, apps01 duplicate env-pairs prune) — environment-side, unchanged.