Commit Graph
9007 Commits
Author SHA1 Message Date
Marc-André Bombeck ff49bbb5fc fix(insights): shade the SpO2 chart from the own target when one is set
The oxygen saturation page drew a fixed clinical band, so a person with
a lower target of their own saw their normal range shaded as a warning.
The page now reads the resolved range from /api/user/thresholds and
draws it; without a target the clinical bands stay as they were.

Refs #1261
2026-10-22 10:23:15 +02:00
Marc-André Bombeck 90106d88c8 fix(dashboard): shade blood pressure, pulse and body fat from the own target
The dashboard bands took only the weight target into account. Blood
pressure, resting pulse and body fat were resolved from age and sex
alone, so a target set on /targets left the chart zones and the tile
ranges on the standard band.

buildDashboardBands now reads the stored targets blob and routes every
override through getEffectiveRange, the same resolver the targets page
and the doctor report use. A metric without an override keeps exactly
the band it drew before. The snapshot's targetBands carry the resolved
values, so a native client reading them needs no change; the field
descriptions now say the own target wins.

Refs #1261
2026-10-22 10:23:14 +02:00
Marc-André Bombeck 72e6fd6620 test(e2e): drive the bottom sheet on WebKit with an iPhone profile
A webkit-iphone project runs one spec: drag, spring back, flick, scroll before drag, a slider sideways, the three closes, focus return, the page's scroll position and pull-to-refresh. CI installs WebKit beside Chromium.
2026-10-22 10:20:30 +02:00
Marc-André Bombeck 55211c0e69 fix(pwa): never arm pull-to-refresh while a sheet holds the page
The listeners sit on window, so a drag inside an open sheet on the dashboard or the insights page armed the refresh behind it. They stand down while the scroll lock marks the body.
2026-10-22 10:20:30 +02:00
Marc-André Bombeck 1cc1cda7aa feat(ui): make every bottom sheet draggable
One bottom sheet on top of the Radix dialog, so focus, Escape, the
backdrop, aria and the page scroll lock stay where they were. The sheet
follows a touch with a transform, the backdrop lightens with it, and a
release past a quarter of the height or a flick down closes it; anything
less springs back. Grip and header always drag; content only from its
very top, and sideways gestures never count.

Every `side="bottom"` sheet and ResponsiveSheet on a phone render through
it: the More sheet, the capture and form sheets, the Coach drawer on a
phone and the day. The day opens on the new preview step and pulls up to
90 dvh, its footer held on the bottom edge. Focus now returns to whatever
opened a sheet, not only to a Radix trigger. The command palette fills
the window and keeps no grip or drag.
2026-10-22 10:20:28 +02:00
Marc-André Bombeck 601d5ea447 test(guards): walk the tree once in the timezone formatter guard
Each of the guard's scans started its own grep over the whole of src,
the generated client included, five times per run, and the floor case
timed out at five seconds under a loaded unit suite. The tree is read
once now, without the generated client, and a file is kept only when a
plain substring says a pattern could match; each scan's result is
reused by the cases that ask about it. The file runs in 1.9 s instead of
6.5 s alone, and the floor case in 2 ms instead of 2.3 s.
2026-10-22 10:19:11 +02:00
Marc-André Bombeck 703c92c776 test(integration): take the clock and the probe budget out of two verdict tests
The grant expiry case gave the grant 400 ms and slept 500: on a loaded
runner accepting, signing in and switching took longer than the grant
lived, and the grant expired before it was accepted. The grant now runs
30 seconds and the case moves the clock the resolver reads to the
instant the grant names, so the row still does not change and nothing
sleeps.

The key check's start-up probe is bounded at ten seconds and two per
query. Under load a probe query ran past two seconds and the check
answered inconclusive where the data says mismatch. The verdict tests
now run the check with time to finish; the cases that test the bounds
pass their own budget or sampler and keep them.
2026-10-22 10:14:03 +02:00
Marc-André Bombeck 0cefac9d68 Merge branch 'release/v1.45.0' into feat/v145-backup
# Conflicts:
#	src/__tests__/baselines/source-file-lines.json
#	src/__tests__/baselines/source-function-lines.json
#	src/__tests__/baselines/version-stamp-comments.json
2026-10-22 09:59:37 +02:00
Marc-André Bombeck 6dfb7eada6 Merge branch 'release/v1.45.0' into feat/v145-runtime 2026-10-22 09:41:54 +02:00
Marc-André Bombeck 3524d7c289 test(integration): lower the hot-route statement budgets to the measured counts
/api/auth/me 18 to 13, dashboard snapshot 103 to 99, summary 86 to 84,
the 365- and 3650-day pulse series 6 to 5, the day view 48 to 47,
insights health status 60 to 59, correlations 44 to 43 and a 500-row
batch ingest 31 to 28.
2026-10-22 09:34:42 +02:00
Marc-André Bombeck a451a114a2 docs(bench): record the boot profile and the runtime results
The profile section in docs/contributing/benchmarks.md says how to run
scripts/bench/boot-profile.ts, what the profile of the v1.44 runtime
found (the worker graph as one 753 ms block, a live heap of about
165 MB of which the query compiler holds 50 and the catalogs 33) and what
changed. scripts/bench/results/v1.45.0-*.md hold the before and after
numbers per area, from back-to-back runs on the same machine. The size
baselines take the lowered numbers.
2026-10-22 09:10:14 +02:00
Marc-André Bombeck 83661319d2 refactor(cache): keep the closed-block key helpers in a leaf module
Readers that tag a cell as a closed block no longer import the
invalidation module, which several tests replace wholesale. The cache
route test counts the slice's pre-fold cell apart from the route's two
cells, and the provenance test reads the shared pick's ORDER BY over its
grouped rows.
2026-10-22 09:09:44 +02:00
Marc-André Bombeck 4c86b68363 perf(import): skip the update of a re-imported row that is already stored
Re-importing an Apple Health export the account already holds sent one
UPDATE per row the insert skipped, about two million statements for a
large export, nearly all writing back what was stored. One read per chunk
now finds the rows whose stored values equal what the update would write;
they count as updated as before, without the statement. A key the chunk
carries twice is written in order as before. The natural-key rescue moves
into the same module.
2026-10-22 09:09:44 +02:00
Marc-André Bombeck f50b6b6637 perf(series): read a dense window's rows once for buckets and figures
A long-window read of a dense series sent two statements over the same
canonical rows, the day or hour buckets and the strip's figures, so a
ten-year pulse read picked the canonical source per day and scanned the
readings twice. Both aggregates now run in one statement over the window
materialised once, with the same SQL each had (dense-series-read.ts).

An integration test keeps the two old statements as the reference and
requires the same buckets and figures for pulse (two ranked sources),
glucose and oxygen saturation, day and hour grains, in UTC,
Europe/Berlin, Asia/Kolkata and America/New_York, and the same empty
answer for a type without readings.
2026-10-22 09:09:42 +02:00
Marc-André Bombeck 8f1f4dc691 build: keep the test harness out of the image and the font bindings out of the export list 2026-10-22 08:38:04 +02:00
Marc-André Bombeck 3a4dc16e1d test(guards): read each file and the schema once in the backup plan check
The two-ended checks ask every declared file about every model and each
of its relations, and read the schema and the files again for each
question. With the backup split into more files that took two seconds a
case and timed out on a loaded runner; the reads are memoised now.
2026-10-22 08:36:35 +02:00
Marc-André Bombeck 5902401ab8 chore(release): v1.44.1 2026-10-22 08:25:49 +02:00
Marc-André Bombeck 1fa33f2aee fix(security): turn the image optimizer off
Nothing renders through next/image, so /_next/image only offered a route
that fetches, decodes and re-encodes whatever local path a request names.
With images.unoptimized Next answers it with 404 before reading a
parameter. A guard fails when next/image is imported while the optimizer
is off, or when the optimizer returns with SVG input allowed.
2026-10-22 08:25:49 +02:00
Marc-André Bombeck 44c2973a64 fix(deps): update next to 16.4.2 and nodemailer to 10.1.7
Next 16.4.2 carries the fixes for GHSA-673q-mjm6-r5xr and
GHSA-2464-fhr4-w9xw (unauthenticated remote code execution) and
GHSA-p3m5-59mp-jv5r (denial of service through Server Actions).
eslint-config-next and @next/bundle-analyzer move with it, both pinned
exactly. nodemailer 10.1.7 fixes the address-parser advisories.

The 16.4.0 ignore in the Dependabot config is gone: the history journeys
it was guarding pass on 16.4.2 with the day-step fix, and the note says
what was measured.
2026-10-22 08:25:48 +02:00
Marc-André Bombeck 76af868064 fix(day): step the open day from the URL, not from the last rendered day
A step read the day the layer had last published, which reaches the
shortcut and the header arrows through useSearchParams, a render and an
effect. Next 16.4 lets that chain trail a replaceState by more than a key
repeat, so two quick presses of ] both stepped from the same day and one
was lost. stepDay now reads ?day= from window.location, which the step
itself writes, and refuses a step past today on that value too.
2026-10-22 08:25:47 +02:00
Marc-André Bombeck 4fc6b0e386 Merge branch 'release/v1.45.0' into feat/v145-backup 2026-10-22 08:12:22 +02:00
Marc-André Bombeck 6f20ae4a8d test(backup): hold each schema slice's own rules at their edges
The whole-payload tests meet a slice's refinements only by accident. Each
pattern, bound and two-ended free-text rule now has the value just inside
and just outside it, with the issue it raises: the day keys, a long day's
time away, the Coach's table and trail caps, a life event's title and a
symptom's label, a profile fact's readable value and validity. The backup
validation mutation score goes from 25.5 to 41.6 %; the floor follows.
2026-10-22 08:12:00 +02:00
Marc-André Bombeck 12749f6839 refactor(backup): give each domain its own slice of the payload schema
The element schemas move out of validations/backup.ts into one
<domain>-backup-schema.ts per domain, beside its export and restore, with
the shared primitives in backup-schema-kit.ts. validations/backup.ts now
composes the slices into backupPayloadSchema in the same key order, with
the same defaults and the checks that span sections, so the parsed
payload, every validation message and the OpenAPI contract are unchanged.

The lazy-schema guard now covers the slices too: a slice is reached only
through the payload schema or another slice, never from a domain's export
or restore. The external-id guard carries the payload's exemption to its
slices, and mutation testing scores the slices with the payload schema.
2026-10-22 08:02:14 +02:00
Marc-André Bombeck 5a9a6ce78b Merge branch 'release/v1.45.0' into feat/v145-deps 2026-10-22 07:54:27 +02:00
Marc-André Bombeck 322652a86e refactor(backup): build the export from per-domain slices
buildFullBackupPayload now asks every domain for its slice at once and
lays the slices out in the file's key order (backup-export-sections.ts),
merging the manifest where it first appears. The measurement, medication,
mood, instance-settings and nutrient readers and serialisers move out of
the payload builder into their domain modules; the records section keeps
its composition and manifest, while labs, illness, allergies, family
history, workouts and documents each export from their own module. The
measurement restore half moves to measurements-restore.ts so the export
graph does not pull in the restore's dependencies.

The file, its counts and their order are unchanged; the golden copies
still match. The measurement completeness guard now reads the domain
module and also checks that every carried column is written back.
2026-10-22 07:46:02 +02:00
Marc-André Bombeck 508e19f190 test(backup): fill the parity account by key order and match audit rows by copy
A table read without an order comes back in whatever order its last
updates left on disk, so the column fill could pick a different row of a
two-row table from run to run. The audit rows of a restore are now found
by the copy's id instead of by time, since the database clock and the
test's clock do not agree to the millisecond. The golden copies are
written again from the code before the restore and export moved.
2026-10-22 07:45:19 +02:00
Marc-André Bombeck 5199df56db test(jobs): restate the backfill lane's expiry behaviour for pg-boss 12.38
Up to 12.37 pg-boss retried a job whose handler outlived its expiry in
the same worker beside the run it had given up on, and the lane test
pinned that as an executable statement. 12.38 fails the job and fires
the signal as before, but the worker fetches nothing more until the
handler returns, and the retry starts after it. The test now pins that:
one run at a time, the retry starting after the overrun ends, and the
lane's handler importing once per delivery with never two at once. The
lane's lock stays, as the guard for a second worker or instance.
2026-10-22 07:33:01 +02:00
Marc-André Bombeck 5d3794e734 build(fonts): self-host Inter so the build fetches nothing
next/font/google downloads Inter from fonts.googleapis.com and
fonts.gstatic.com while the app builds, and when that request fails the
build fails with it, which is what broke the arm64 image build and
several CI jobs. The files it downloaded now live in src/app/fonts/inter
under the SIL Open Font License, and next/font/local declares them.

They are the same bytes: Inter 4.001, one variable file per script
subset (Cyrillic and its extension, Greek and its extension,
Vietnamese, Latin Extended, Latin), each used for weights 400 to 700.
The declarations rebuild the stylesheet Google answered, the twenty-eight
faces with Google's unicode ranges in Google's subset order, swap
display, and only the Latin file preloaded. The local loader emits one
unicode range per call, so each subset is its own call under the
family Inter.

The Arial fallback face and the font-sans stack move into globals.css
with the metric overrides next/font/google shipped for Inter; the local
loader would have computed slightly different ones from the file.

A production build with every outbound request sent to a dead proxy
completes, where the same build with next/font/google fails on the
fonts.googleapis.com request. The content security policy already
allowed fonts from self only. A guard refuses a next/font/google import
anywhere under src.
2026-10-22 07:31:16 +02:00
Marc-André Bombeck eda90eeb56 fix(strava): read a dead refresh token from Strava's own fault shape
Strava's token endpoint does not answer RFC 6749 errors. A refresh
token it no longer honours, after the athlete revoked the app or the
token was rotated away, is a 400 with the API's generic Fault:
{"message":"Bad Request","errors":[{"resource":"RefreshToken",
"field":"refresh_token","code":"invalid"}]}. The classifier looked for
invalid_grant, which never arrives, so the failure was booked
persistent and the connection never asked for a reconnect.

The token error now carries the fault reduced to its resource, field
and code (or an RFC error code, should one arrive), and the classifier
lifts a 400 naming RefreshToken to reauth_required, from the error and
from its message after a retry loses the prototype. Every other refusal
is classified by its status as before, including the faults that name
the Application.

Strava documents the Fault and Error model but not these values; the
body is the one several unrelated client libraries captured from live
traffic between 2023 and 2026.
2026-10-22 07:31:15 +02:00
Marc-André Bombeck a57fa8335a style(mcp): tighten the metric index note 2026-10-22 07:25:08 +02:00
Marc-André Bombeck f8a88d1361 perf(auth): read the operator settings once per request
An app boot's /api/auth/me read the AppSettings singleton six times, each
reader with its own few columns: the MFA policy, the Assistant switches,
the module availability, the provider presence, the AI usage bounds and
the reasoning controls. They now take their columns from one read,
memoised for the request and read afresh inside a background job like the
readers themselves. No answer changes.
2026-10-22 07:25:00 +02:00
Marc-André Bombeck e00943ff7f perf(day): read the record sections six at a time
The day view started about twenty-five section reads at once beside the
readings, scores and movement, more than the pool holds, so a
neighbouring request waited for a connection. The sections now run six at
a time and come back in the same order.
2026-10-22 07:25:00 +02:00
Marc-André Bombeck f5d6710670 perf(analytics): keep closed blocks of past readings cached across syncs
Every sync marked all of an account's analytics cells stale, so the slice
re-read the pre-fold remainder (every reading older than five years,
folded per source and day, the slice's largest statement on a long
history) and the day view re-read its past history blocks, although a
sync of today's readings cannot change either.

A cache key can now name the instant its value's readings end before
(closedBlockKeyPart). invalidateUserMeasurements takes touchedFrom, the
earliest measuredAt a write created, changed or removed, and leaves such
blocks in place when it is not earlier; without it every block goes as
before. The batch route passes it from the identities it wrote. The
pre-fold remainder moves into its own module and is cached as a closed
block; the day view's past history blocks carry their end, with a day of
margin for a night across midnight.

The batch route's two up-front reads move into batch-stored-rows.ts with
the once-per-batch key rule they share.
2026-10-22 07:24:59 +02:00
Marc-André Bombeck b6e9eb98f7 test(integration): size the all-time count case to its measured cost
The rollup parity case writes over half a million readings and folds them
into DAY buckets: about 35 s on a CI runner alone. Run beside two other heavy
files on the same Postgres it took 1.7 times as long and crossed the 60 s
default. Nothing in the case waits on a lock: its advisory locks are scoped
to the worker's own database, and the measurement with the database capped
at two CPUs (14.6 s alone, 25.3 s beside the sharing matrix and the restore
memory test) shows plain contention. The case gets a 150 s budget, twice the
contended estimate. HEALTHLOG_INTEGRATION_PG_CPUS caps the database
container so a CI runner's contention can be reproduced locally.
2026-10-22 07:18:57 +02:00
Marc-André Bombeck 7248347188 test(integration): release the held lock when the migration has met it
The 0412 lock-retry case let go of the lock on a 2.5 s timer. On a loaded
runner the migrate CLI took longer than that to start, so the first attempt
found the lock already free, applied cleanly and never produced the 55P03
the case is about. The lock is now released once the attempt is seen queued
behind it in pg_stat_activity and gone again, which is the moment its own
timeout fired, and the case fails outright if no attempt ever queued. It
holds with the CLI start delayed by four seconds.
2026-10-22 07:18:56 +02:00
Marc-André Bombeck d396945f44 fix(api): publish the backup upload
POST /api/admin/backups/upload stores a backup file as a new copy, the
copy whose id the published restore takes, and it refuses a file for
reasons an operator has to be able to look up: a key this server does
not hold (backup.key.missing, with the key ids), an owner account that
does not exist here, a newer schema, a copy too large to store
(backup.upload.too_large). It was exempted from the contract as console
plumbing while its two siblings were published for exactly that kind
of refusal.

It is registered with the three bodies it reads (raw JSON, raw gzip, a
small multipart form) and every status it answers; the summary is the
three identity fields plus one integer count per class, so a class the
backup format gains later does not break a decoder. The round-trip
test calls it through the contract now.
2026-10-22 07:08:02 +02:00
Marc-André Bombeck 579789ec38 fix(api): declare the 400 and 415 the single-value profile writes answer
The six /api/auth/me scalar writes (unit preference, glucose unit,
automatic document reading, local lab OCR, workout routes, the central
Codex switch) read their body through safeJson with a 1 KB cap, so each
answers 415 for a body that is not JSON by content type and 400 for one
that does not parse, beside the 413 they already declared. Their
descriptions said so and the responses did not. All three now come from
one block.

The modules and disable-coach route tests had been skipping the
contract check over a 400 and a 409 the registry already declares; the
skips are gone.
2026-10-22 07:07:46 +02:00
Marc-André Bombeck 5ca3994523 fix(api): declare the FHIR refusals as the OperationOutcomes they are
A /api/fhir/* handler answers its own refusals as FHIR: a bad search
parameter is a 400 OperationOutcome, the hourly fhir bucket a 429
OperationOutcome (issue code throttled), both served as
application/fhir+json, the same way the 404 on a read already was. The
contract declared no 400 and the generic JSON 429 for the 429, and no
403 for the doctorReport module gate, which every data route runs.

The searches now declare the 400, every data route the 403
module.disabled envelope and the FHIR 429 with the rate-limit headers
apiHandler attaches to it. The module gate keeps answering the JSON
envelope: it is the errorCode every client already branches on for a
disabled module, and it runs before the FHIR face does. The route tests
drop their skips and assert the 429's content type and Retry-After.
2026-10-22 07:07:34 +02:00
Marc-André Bombeck c272324c1e fix(api): declare the auth gate's 403 on every operation behind it
requireAuth answers 403 auth.scope.insufficient to a narrow Bearer token
on a route that names no scope it holds, and requireAdmin answers 403
auth.admin.required to a session that is not an administrator's. Both
run before any route body, and the contract only said so in the prose
of the shared 401, so a client generated from it had no typed branch
for either. The route kit accepted the scope refusal on any operation
that declared a 401 for the same reason.

The 403 is added to the assembled path table rather than to
stdResponses. That set is spread into most operations, about three
hundred of them carry a 403 of their own, and most spread the set after
it, so a shared 403 there would have overwritten every module-off and
sharing refusal in the document. withScopeRefusal adds the shared
response where an operation has none and prefixes an operation's own
403 with the gate's code, keeping the sharing sentence last. Operations
that take no credential, and the three surfaces that authenticate by a
credential of their own, are left as they were.

The kit no longer tolerates the refusal on an operation's behalf, and a
guard holds every operation behind the gate to declaring it.
2026-10-22 07:07:19 +02:00
Marc-André Bombeck 4f2af7c544 test(release): hold the image's Prisma CLI install to the app's versions
The guard pinned the npm-installed dotenv in the Dockerfile to a literal,
which is how that install came to trail the app tree by a patch without
anything noticing. It now reads the Prisma and dotenv versions from
package.json and asks the Dockerfile for the same ones, engines included.
2026-10-22 07:06:51 +02:00
Marc-André Bombeck 901cd884e8 perf(measurements): skip unchanged stats: re-posts in the batch route
The phone re-sends the same daily totals on every sync. Each went through
the reconciler, about ten round trips in its own transaction, to write the
same values back, and as an update it started the DAY refold, the rollup
jobs, PR detection and a cache stale for the account.

One read over the batch's stats: ids now finds the rows whose live stored
row already holds every value the update would write; those are reported
updated exactly as before, per entry and in the summary, and nothing is
written or started for them. A changed column, a tombstoned row, a key
that occurs twice in the batch or a value the plausibility gate refuses
still goes through the reconciler.
2026-10-22 07:01:04 +02:00
Marc-André Bombeck 850988c832 perf(analytics): group before the canonical-source pick, and run without JIT
The shared canonical-source CTE sorted every raw reading in its window by
a CASE expression no index can supply; on the benchmark account the pulse
pick over ten years sorted 260,000 rows and spilled to disk. Its order
depends only on (type, source), so the pick now groups the window to its
distinct (type, day, source) triples first and runs DISTINCT ON over
those. The picked source per day is the same by construction; an
integration test runs the old statement beside the new one over a fixture
with overlapping sources, sources off the ladder, tombstones, empty days
and readings either side of midnight, under default and reordered ladders,
for every reader shape, and requires identical rows.

Every app session now sets jit=off. Postgres compiled the larger folds to
machine code, which took 100 to 140 ms of a 220 ms statement and saved
nothing for statements that run once and are bound by reading rows. JIT
never changes a result.
2026-10-22 07:01:04 +02:00
Marc-André Bombeck b5f5a5a580 perf(i18n): build the every-locale match indexes on first use
Seven matchers built their index over every shipped catalog at module
load (the GLP-1 tag map, the nutrient and metric names of the MCP reads,
the SNOMED and LOINC lab names, the Coach's health words, the lab-name
pattern), so loading the worker graph read all eight catalogs at boot.
Each index is now built the first time it is asked for.

The boot profile records the change: the longest busy stretch of the
main thread at boot fell from 753 ms to 241 ms, the live heap after five
idle minutes from 164 to 152 MB, and the catalog chunk sources (two of
9.7 MB) are gone.
2026-10-22 07:01:03 +02:00
Marc-André Bombeck e7b31fe8e0 refactor(backup): run the restore as ordered steps from per-domain modules
restoreBackup becomes an orchestrator: the checks before the first delete
move to restore-preflight.ts, and the transaction runs the steps listed in
backup-domains.ts in dependency order, emptying the account first and then
rebuilding it domain by domain. The inline restores for measurements,
medications, mood, labs, illness, allergies, family history, workouts,
nutrient days, documents, the instance settings and the cleared
notification channels move into one module per domain, beside the
existing section modules. Cross-domain references travel through one
typed context instead of local variables.

The order of every write, the refusals and their audit rows, the skip
report, the cleared counts and the reported phases are unchanged; the
golden copies of the restored account still match. Guards that pinned
the restore by path now point at the domain files.
2026-10-22 06:59:54 +02:00
Marc-André Bombeck 96062d198a chore(deps): keep vite on the 8.1 line its advisories were fixed on
Re-resolving Vitest 5.0.5 had carried vite from 8.1.5 to 8.3.4, two
minors nobody asked for: 8.2 resolves the root through real paths,
which moves file ids in a symlinked worktree, and 8.3 warns on every
run about config-loader features the test configs use. 8.1.6 carries
the same five fixes as a backport, so the lockfile takes that and the
floor in the workspace file stays as it is.
2026-10-22 06:58:12 +02:00
Marc-André Bombeck 01c935261b chore(deps): take nodemailer 10.1.7 for the address parser advisories
GHSA-8c8m-p3m8-cjg8 and two sibling advisories against the address
parser are fixed in 10.1.4; 10.1.5 to 10.1.7 are linear-time and
encoding fixes on the same line. The email channel hands the parser a
recipient address each account sets for itself.
2026-10-22 06:53:16 +02:00
Marc-André Bombeck 73362dc0ae chore(deps): take the weekly production and development updates
Production: the AWS SDK S3 client and lib-storage 3.1152.0, Prisma
(client, adapter-pg, CLI) 7.10.1, TanStack Query 5.104.3 and Virtual
3.14.21, dotenv 18.0.8, jspdf-autotable 5.0.9, lucide-react 1.59.0,
nodemailer 10.1.2, pg-boss 12.38.0, React and React DOM 19.3.1 (still
exact) and undici 8.12.

React 19.3.1 and undici 8.12.2 are security releases. Prisma 7.10.1
stops a nested interactive transaction from leaking its id into a
later top-level one.

pg-boss moves its schema from 45 to 46; the worker applies the
migration on start and the index builds continue in the background.
Its list and delete helpers now page and batch, which touches only the
queue retirement at boot, and that is idempotent. The retention test
pins the installed version, so it moves to 12.38.0 after re-checking
every source line it asserts.

Development: @axe-core/playwright 4.14.0, Playwright 1.64.1, Vitest and
its coverage provider 5.0.5, shadcn 4.22.0. The Stryker runner patch
still applies; it touches the runner's own name join, and 5.0.5 changes
nothing in how Vitest builds a test's full name. axe-core 4.14 turns on
label-content-name-mismatch for the WCAG 2.1 A tag the a11y specs run.

The S3 client is held on the same release as lib-storage rather than
the newest one its range allows, so the two share one SDK core.

Vite gets a floor at 8.1.6 for GHSA-587h-7wmj-h5pm and four sibling
advisories against its dev server and bundled launch-editor. It arrives
only under Vitest and never reaches the image.

The image's separate Prisma CLI install moves with the app tree to
7.10.1, and its dotenv to 18.0.8, which it had trailed by a patch.
2026-10-22 06:53:04 +02:00
Marc-André Bombeck dcf49dd14e refactor(backup): let the account-settings domain own the User column verdicts
The SETTING verdicts are now the keys of the codec table, so a column
becomes a setting by getting a codec and nothing else has to name it. The
verdicts for every other User column move beside it into
account-settings-classes.ts, and the restore half into
account-settings-restore.ts. The backup plan keeps the model verdicts.

The classification guard now also refuses a column that has both a codec
and another verdict, since the composed table would quietly carry it as a
setting.
2026-10-22 06:50:58 +02:00
Marc-André Bombeck 66f8b18cf8 Merge branch 'release/v1.45.0' into feat/v145-backup 2026-10-22 06:42:29 +02:00
Marc-André Bombeck a859adb59c Merge branch 'release/v1.45.0' into feat/v145-runtime 2026-10-22 06:41:30 +02:00