Commit Graph
3251 Commits
Author SHA1 Message Date
Marc-André Bombeck 27a3e08436 fix(01-07): make private Nightscout access operator-owned
- remove the user-facing private-host bypass
- render a redacted operator-action state for denied legacy connections
2026-07-29 13:57:48 +02:00
Marc-André Bombeck f95da50ba0 fix(01-07): enforce exact Nightscout origins
- move private-host authority to the server-owned exact-origin policy
- preserve stable redacted policy failures through connect and sync
2026-07-29 13:57:07 +02:00
Marc-André Bombeck 7874eea04d feat(01-08): require reauthentication before passkey enrollment
- offer password, TOTP, passkey, and security-key proof choices
- start registration only after the selected existing factor succeeds
2026-07-29 13:53:59 +02:00
Marc-André Bombeck 89c83510d4 feat(01-08): bind passkey enrollment to fresh cookie proof
- require an existing-factor proof before issuing registration options
- atomically claim session-bound challenges before credential persistence
2026-07-29 13:52:22 +02:00
Marc-André Bombeck db08f6c175 fix(01-05): serialize MCP refresh families
- lock and rotate refresh families inside one database transaction
- revoke CAS losers and insert successors before commit
- sign refresh artifacts only after committed state
2026-07-29 13:50:14 +02:00
Marc-André Bombeck a177869609 fix(01-07): classify embedded IPv4 destinations
- Normalize IPv6 literals before transition-prefix inspection
- Reject mapped, compatible, 6to4, and NAT64 private embeddings
- Share the verdict with pinned public and operator dispatchers
2026-07-29 13:49:49 +02:00
Marc-André Bombeck 36cb64c840 test(01-02): exercise DNS transition cases past fetch port guard
- replace blocked port 9 with non-blocked test port 8089
- preserve pre-connect ENOTFOUND assertions for all transition forms
2026-07-29 13:49:46 +02:00
Marc-André Bombeck 20a6731c8b fix(01-05): enforce MCP health read scope
- require health:read or wildcard for MCP read capability
- reject insufficient scopes before downstream gates and server construction
2026-07-29 13:47:46 +02:00
Marc-André Bombeck 338dbe6c1f fix(01-06): purge owned PWA caches on session end
- delete current and legacy HealthLog static page and data caches
- preserve unrelated CacheStorage namespaces during activation and logout
- keep IndexedDB query-cache removal in the session-end boundary
2026-07-29 13:46:15 +02:00
Marc-André Bombeck 51f5f43957 test(01-01): preserve explicit transaction contract
- keep the red transaction-client call aligned with the planned signature
- isolate the concurrent documentation commit from MCP test content
2026-07-29 13:43:27 +02:00
Marc-André Bombeck cc8567d82f docs(01-02): complete PWA and SSRF red contracts plan
- document expected cache-isolation and transition-form failures
- record preserved public and redirect behavior
- capture atomic task commits and verification evidence
2026-07-29 13:42:30 +02:00
Marc-André Bombeck 38501f6574 test(01-01): specify transactional refresh rotation
- require one transaction for rotation and successor insertion
- pin CAS-loss revocation and insertion rollback semantics
2026-07-29 13:40:03 +02:00
Marc-André Bombeck 3f4afb26a4 test(01-03): pin passkey enrollment reauthentication UX
- require password, passkey, TOTP and WebAuthn proof choices
- order fresh-factor completion before challenge creation
- keep successful enrollment in one cookie-bound ceremony
2026-07-29 13:39:55 +02:00
Marc-André Bombeck e25080b214 test(01-02): add red DNS-pinned SSRF contracts
- refuse standard embedded-private IPv6 resolver answers pre-connect
- preserve public IPv6 to IPv4 fallback connectivity
- prove followed redirects repeat the pinned DNS verdict
2026-07-29 13:39:52 +02:00
Marc-André Bombeck 5569b8259b test(01-04): pin Nightscout operator-action UI
- Remove the user-facing private-host bypass contract
- Require redacted operator approval guidance for legacy failures
- Preserve usable public and approved-private connected states
2026-07-29 13:39:50 +02:00
Marc-André Bombeck 14f451093e test(01-04): pin Nightscout egress enforcement
- Deny user-authorized and mismatched private origins before network access
- Preserve exact operator-private and public positive flows
- Require stable redacted policy reasons through sync boundaries
2026-07-29 13:39:06 +02:00
Marc-André Bombeck 9f6cd86c0d test(01-03): add passkey enrollment security contracts
- require fresh existing-factor proof on both cookie-only legs
- bind registration challenges to user, purpose, session, expiry and one use
- cover bearer, ambient-cookie, stale, foreign and replay attacks
2026-07-29 13:38:53 +02:00
Marc-André Bombeck 6c0f47acad test(01-01): pin MCP read scope boundaries
- cover health read and wildcard positive capability paths
- reject unrelated scopes before server registration and database reads
2026-07-29 13:38:41 +02:00
Marc-André Bombeck fb533147d3 test(01-02): add red IPv6 transition classifier matrix
- cover mapped compatible 6to4 and NAT64 private embeddings
- exercise both literal URL and raw DNS address verdicts
- preserve public IPv4 native IPv6 and transition forms
2026-07-29 13:38:11 +02:00
Marc-André Bombeck 3a813b51bd test(01-02): add red PWA cache isolation contracts
- exercise signed-in service-worker install at CacheStorage boundary
- require data-free offline navigation and immutable public assets
- require current and legacy session cache cleanup
2026-07-29 13:37:20 +02:00
Marc-André Bombeck 668f85a58d test(01-04): specify exact Nightscout origin policy
- Parse canonical server-owned private origins
- Reject malformed and non-exact trust grants
- Preserve public and approved-private positive contracts
2026-07-29 13:36:46 +02:00
Marc-André Bombeck 1d784c6367 fix(analytics,ci): merge health-score meta annotations, migrate integration tests, remove dead exports, raise bundle budget 2026-07-28 23:02:00 +02:00
Marc-André Bombeck 668e3ca6b6 fix(insights,settings): restore hero-strip column width, gate hero fieldset on hydration 2026-07-28 22:03:39 +02:00
Marc-André Bombeck 2bab06e323 feat(medications): resolve each dose slot's own window duration 2026-07-28 21:26:53 +02:00
Marc-André Bombeck ff8ead2db9 fix(analytics): repair route/i18n/guard fallout from health-score merge 2026-07-28 21:19:18 +02:00
Marc-André Bombeck 7b8fb98e48 feat(analytics): replace health score with reference composite 2026-07-28 21:12:05 +02:00
Marc-André Bombeck 93e1b83779 test(api): guard response field consumers 2026-07-28 21:11:56 +02:00
Marc-André Bombeck 3aff2c16db fix(insights): scope generation guard to prompt fields, clear cache on restore 2026-07-28 21:11:43 +02:00
Marc-André Bombeck c91558f18f feat(analytics): replace health score with reference composite 2026-07-28 21:10:15 +02:00
Marc-André Bombeck 87544af2cd Fix medication reminder slot attribution 2026-07-28 21:06:07 +02:00
Marc-André Bombeck af47bae046 test(api): guard response field consumers 2026-07-28 21:05:42 +02:00
Marc-André Bombeck 666a85d311 feat(insights): persist correlation patterns 2026-07-28 14:01:11 +02:00
Marc-André Bombeck 8ece6f39cc fix(status): expose background failures 2026-07-28 14:00:44 +02:00
Mathew Sims 08e535a0d9 fix(reminders): resolve each dose slot's own window duration, not the whole schedule's span
The 15-minute reminder tick computed windowDuration ONCE per schedule
as windowEnd - windowStart, then applied that SAME span to every
timeOfDay slot in the schedule. For a single-dose schedule that span
genuinely is the one slot's window, but for a multi-dose schedule
(e.g. windowStart=08:00/windowEnd=18:00 with timesOfDay=[08:00,18:00])
it silently gave the 08:00 slot a 600-minute grace before even
entering ORANGE phase — ten hours nobody configured — while an
identically-intended single-dose schedule with a narrow legacy span
(08:00-09:00) correctly escalated on schedule but then had only a
30-minute radius for a logged-but-late dose to suppress its reminder.
Same misapplied field, opposite-looking symptoms: one medication
silently never reminded, the other kept escalating despite being
logged as taken.

resolveSlotWindowDurationMinutes resolves each slot's duration
independently, checked in priority order: an explicit per-dose
doseWindows entry for that exact timeOfDay (the actual per-dose rule
set in the dose-window editor) > the schedule's reminderGraceMinutes
(documented for exactly this purpose but never actually read by the
reminder tick until now — the write path already reads it via
graceToleranceMs) > the same default an unconfigured "point" dose
gets everywhere else in the app, bounded by the gap to the nearest
sibling slot so two doses can never share a window. A single-dose
schedule with neither override keeps the exact legacy windowEnd -
windowStart behaviour, unchanged, for backward compatibility with
schedules that predate the per-dose doseWindows system.

The core property this restores: a dose's resolved window never
depends on how many sibling doses share its schedule. Two
identically-configured doses now resolve identically regardless of
whether one of them happens to have a second dose elsewhere in the
day.

Full suite (18k+ tests) passes with no regressions; 13 new tests
cover both real-world cases that surfaced this (a single 08:00-09:00
schedule and a twice-daily 08:00/18:00 one) plus the sibling-
independence property directly.
2026-07-28 12:55:34 +01:00
Marc-André Bombeck a689329e79 feat(dashboard): configure Today highlights 2026-07-28 13:22:08 +02:00
Marc-André Bombeck a2f2a30ac2 fix(quality): close backup and localization guard gaps 2026-07-28 12:03:02 +02:00
Marc-André Bombeck 81825ce23b merge: forward-port v1.33.2 into release/v1.34.0 2026-07-28 11:43:44 +02:00
Marc-André Bombeck ddbce306be fix(backup): drop the manifest key the records spread already carries
The assembly picked `manifest` out of the records section by hand, one line
under the comment explaining that both sections are spread precisely so nobody
has to pick their keys. It resolved to the same value the spread had already
placed, at the same position, so the emitted file is byte-identical either way.

Left in, it is the pattern that lost the custom cycle symptoms sitting next to
the note about how that happened.
2026-07-28 00:09:10 +02:00
Marc-André Bombeck dddd2164b1 test(withings): teach the fallback mock the shape its function now returns
The sync function was widened to answer with what it wrote rather than a bare
count. This mock still resolved to a number, so the handler destructured
`imported` off it, got undefined, and reported `measurements_imported: NaN`.

Nothing in the type system could see it: `vi.fn().mockResolvedValue(0)` carries
no relation to the real signature, so a widened contract drifts past every mock
in the tree unnoticed. This was the only sibling; the class is worth a sweep of
its own.
2026-07-28 00:07:53 +02:00
Marc-André Bombeck 51dbfefbcb fix(backup): carry the intraday day shape through export and restore
`IntradayCumulativeProfile` shipped classified as backed up and carried by
nothing. The drain folds the per-sample rows into twenty-four running totals
and deletes the originals in the same transaction, so past the grace window
the table is the only copy of a day's curve: a restore rebuilt the account
without it, reported success, and left the same-time baseline reading "still
learning" for a fortnight.

Builder and restore sit together in one file, the way the profile and cycle
pairs do, because the restore route delegates and a grep of the route alone
lies. The section spreads into the payload rather than being copied key by
key. Two curves for the same metric and day, or a curve that is not a whole
day, throw naming the day instead of writing a row every reader drops.

The pair is proven end to end rather than at each end: the round-trip suite
builds a real payload from a populated account, serialises it, parses it back
through the real parser, drives the real restore, and asserts on the rows
written. Remove the read and it goes red.
2026-07-28 00:07:10 +02:00
Marc-André Bombeck 9d1f21408f fix(insights): floor the same-time band from the metric, not by hand
Two parallel lines of work reached the same conclusion independently: a
baseline band over a quantity that cannot go negative must not report a lower
edge below zero. One of them answered it locally with a `Math.max(0, …)` at the
call site; the other gave every metric's numeric domain a single home and made
`buildBaselineBand` take the metric so the floor comes from the declaration.

Keep the second and drop the first. The required parameter is what surfaced
this at all: the new call site could not compile without naming its metric,
which is the whole reason it was made required rather than defaulted.
2026-07-27 23:45:00 +02:00
Marc-André Bombeck 2aeb925e28 merge: s15-stepsdisplay into release/v1.34.0 2026-07-27 23:41:34 +02:00
Marc-André Bombeck 66017a8b06 merge: s10-google into release/v1.34.0 2026-07-27 23:41:34 +02:00
Marc-André Bombeck b14943ea1b merge: s8-baselines into release/v1.34.0 2026-07-27 23:41:34 +02:00
Marc-André Bombeck fbdadae0ce merge: s5-export into release/v1.34.0 2026-07-27 23:41:33 +02:00
Marc-André Bombeck b402be221d merge: s4-yardstick into release/v1.34.0 2026-07-27 23:41:33 +02:00
Marc-André Bombeck 602ca36ee2 merge: s3-written into release/v1.34.0 2026-07-27 23:41:33 +02:00
Marc-André Bombeck e5b958cd86 fix(backup): carry the account's own cycle symptoms into the payload
The section builder returned them, the wire schema declared them, and the
restore has read them since v1.33.1 — including the throw that replaced the
silent drop. The payload assembly copied its keys out one by one and was never
extended, so every backup written carried an empty list. An account with a
custom symptom did not get the silent drop that release removed; it got a
restore that failed outright on a key nothing had written back.

Spread the cycle and records sections instead of hand-picking their keys, so a
field added to either section interface reaches the wire by construction. Declare
labelEncrypted on the cycle section, which the builder already emitted.
2026-07-27 23:40:44 +02:00
Marc-André Bombeck b56f5721a2 test(measurements): pin that the day's shape is written before the delete
The drain's existing transaction mock had no profile delegate, so the fold
could have thrown inside the per-day error boundary and every assertion would
still have passed. Cover the merge directly: the upsert lands before the
deleteMany, the stored curve matches where the samples actually fell, and a
serialised tick that finds the rows already drained writes nothing.
2026-07-27 23:39:37 +02:00
Marc-André Bombeck 6bd8d387ae feat(insights): surface the same-time baseline on Today and in the grid
The rail gains a same_time_baseline kind and a server builder, which is the
growth path that type documents for itself. It speaks only when today is
actually outside the typical band: a card that reports doing exactly what you
usually do teaches people to stop reading the rail. Dismissing it is keyed by
day and metric, not by the hour, so it stays dismissed instead of coming back
every sixty minutes.

The Insights grid gains a section of four tiles, one per cumulative metric,
each honestly quiet until the history accrues.

The two-trajectory chart it needs did not exist, so DeltaSparkline gains a
second mode rather than a second chart family: the comparison curve rides the
same container, the same fixed height and the same shared y-domain as a dashed
muted line behind the live one, which is the only way the two can be read
against each other.

Six locales. The wipe and backup plans learn about the new table: the profile
cannot be rebuilt once the samples are gone, so it is carried, not derived.
2026-07-27 23:32:22 +02:00