11 Commits
Author SHA1 Message Date
Marc-André Bombeck f5d4dd2ec6 test: mutation testing on the core libraries with per-module floors
Stryker runs against encryption at rest, medication compliance, the rollups,
source ranking, unit conversion and backup validation, with the unit tests
that live next to them (vitest.mutation.config.mts). It is too slow for a
pull request and runs nightly and on demand (.github/workflows/mutation.yml).
scripts/mutation-floors.mjs reads the JSON report and fails when a module
scores below its floor in stryker.floors.json, so one well-tested module
cannot hide a weak one. The floors sit two points below the first measured
scores: crypto 46.9 %, compliance 66.8 %, rollups 41.7 %, source ranking
57.0 %, units 87.4 %, backup validation 25.5 %.

@stryker-mutator/vitest-runner 10.0.0 predates Vitest 5, which names a test
"describe > test" where the runner still built "describe test". Every test
inside a describe block was filtered out of the mutant runs, so nearly every
mutant survived (5.5 % across the set). A pnpm patch builds the name the way
Vitest 5 does; the Docker dependency stage copies the patches directory,
which the frozen install needs.
2026-10-22 06:08:20 +02:00
Marc-André Bombeck ed05772a8e test(e2e): run the delegated-writes journey and fail on silent skips
The delegated-writes spec checked for the invitation form's level control
right after page load, before the form had painted, found nothing and
skipped every test on every run, CI included.

- Give the journey its own account pair, seeded and reset in global setup,
  so it no longer invites the same pair as the account-sharing journey
  (a second live grant between two accounts is refused with 409).
- Wait for the form through its data-slot and fail when the WRITE level
  control is missing instead of skipping.
- Revoke the journey's grant in afterAll.
- Keep it out of the mobile project in the config rather than through a
  runtime skip.

Add two checks so a skip cannot hide a spec again:

- e2e-conditional-skip-guard.test.ts requires every conditional
  test.skip/test.fixme in e2e/ to carry a reason and refuses a bare
  test.skip().
- scripts/check-e2e-skipped-specs.mjs reads the Playwright JSON reports of
  both shards in the e2e gate job and fails when a spec file skipped all of
  its tests across every project, apart from an allowlist with reasons.
2026-09-27 04:48:08 +02:00
Marc-André Bombeck c64708cc37 test(ci): pin the third end-to-end server the notification journey drives
The determinism guard counted two Playwright servers and would have refused a third by deletion; the notification journey's mail-configured, scheduler-free process is the third. Pin it by its two distinguishing variables and assert the shared servers carry no mail configuration.
2026-09-10 06:49:14 +02:00
Marc-André Bombeck f62cd59789 chore(deps): raise the stale advisory floors and pin valibot
Three overrides had drifted behind the advisories they were written for,
and the scan found the packages at versions the pins were supposed to
exclude.

dompurify moves from <=3.4.10 to <3.4.13. GHSA-55q2-fjhq-7xh7 is fixed
in 3.4.13 and the tree carried 3.4.12. The copy is jspdf's optional HTML
renderer, an optionalDependency at ^3.3.1, so the raise stays inside the
range jspdf declares.

postcss moves from <8.5.10 to <8.5.23 for CVE-2026-69153. next declares
an exact postcss@8.4.31, so nothing but this bound moves it; the build
has run on the 8.5 line since the first bump and stays there.

valibot gets a floor it never had, <1.4.2, for CVE-2026-59952. Two
copies arrive and neither is ours to call: an optional peer of
@hookform/resolvers, which this app does not use because it validates
with zod, and prisma's local dev server. 1.4.2 is the same major, so no
consumer is moved onto a new one.

@hono/node-server moves from <1.19.13 to <1.19.15 for
GHSA-frvp-7c67-39w9. The app tree already resolved 1.19.17, so this
changes nothing here; it exists so the bound states the real floor and
so the Dockerfile mirror has something correct to match.

The floor test in ci-determinism asserted the exact key and replacement
string, which meant every legitimate raise failed it while a floor gone
stale against a newer advisory passed it unchanged. It now reduces each
key to a package name and asserts the pin is at or above the fixed
version, with a case proving the comparison can return false.
2026-09-02 15:46:55 +02:00
Marc-André Bombeck ea4d284cc9 ci: carry the report per shard and point the guard at the running job
The determinism guard reads the job that runs the browser, which is now
the shard matrix rather than the gate that only carries the required
check name. The report artifact is named per shard so the two uploads
cannot clobber each other.
2026-08-08 04:45:04 +02:00
Marc-André Bombeck 675c70eba8 Keep the dashboard the server streamed
`/` runs its reads server-side and hands them to TanStack through a
HydrationBoundary, so the first HTML already carries real tiles. React threw
that away on every cold load. The app shell hydrates first and its mount
answers `/api/auth/me`; the dashboard sits below `loading.tsx`, hydrates later,
and its first client render therefore read an account the server never had.
That disagreed with the streamed HTML, React reported #418 and re-rendered the
whole page. The prefetch paid for its queries and delivered none of its paint.

The account payload is now withheld from the hydration render wherever it is
only rendered. `useAccountOnceMounted()` answers null until the component has
mounted, which is what the server rendered, and hands the payload over on the
next render. The dashboard page, the caregiver card, the getting-started
checklist, the achievements tile and the unit labels read it that way. `useAuth`
itself still reads the cache, because the document vault redirects off
`user.modules` and a first render claiming "no account" bounces it to `/`.

Two tiles disagreed without any account involved. A query reports `isLoading`
true on the client's first render and false on the server, so both now gate on
whether the cell holds an answer.

Nothing in CI ran the shipped configuration: the suite's server turns the
prefetch off so route mocks keep governing the dashboard. A second server runs
it on, and one spec asserts that no hydration error is reported and that the
tile node the server streamed is still the node in the DOM once the page
settles.

Measured over nine cold loads with the CPU throttled 4x: seven bailouts before
and none after, median LCP 869 ms before and 145 ms after. FCP and the time to
the first tile in the DOM are unchanged, because the streamed paint was never
the problem. Keeping it was.
2026-08-06 17:28:48 +02:00
Marc-André Bombeck 62eb431d66 release: v1.32.0 — hardening and scalability (#580)
* release: prepare v1.32.0

* test: stabilize water capture re-entry
2026-07-22 04:14:15 +02:00
Marc-André Bombeck 085aa3f5c5 fix: harden pnpm supply chain and runtime image (#572) 2026-07-21 04:36:53 +02:00
Marc-André Bombeck 5fe952b63d fix(ci): harden release and e2e verification 2026-07-21 02:30:40 +02:00
Marc-André Bombeck b38f41b99d chore(release): prepare v1.31.4 2026-07-21 01:34:29 +02:00
Marc-André Bombeck 3ce6726646 fix(ci): harden test determinism and dependencies 2026-07-21 00:36:40 +02:00