docs(planning): v1.4.38 perf-verify + audit-marathon closure addendum

- 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.
This commit is contained in:
Marc-André Bombeck committed 2026-05-17 19:18:56 +02:00
1 parent 7cdfe3feef
commit 8912bf14f9
2 files changed
+102 -2

No files matched your search

+4 -2
View File
@@ -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
+98
View File
@@ -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.<branch>.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.