From c2f67c96d4b48e6a241534cebc3fb2ca1a8f3525 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Fri, 7 Aug 2026 23:58:34 +0200 Subject: [PATCH 1/3] fix(deps): float nanoid past an infinite-loop flaw A fresh advisory names nanoid below 3.3.17: a custom generator can loop forever when asked for a zero-size id. It reaches the tree only through next and postcss, but the audit gate rightly blocks on a known HIGH with a fix available. Bounded override in the established pattern; the audit drops from one high to none. --- pnpm-lock.yaml | 20 +++++++------------- pnpm-workspace.yaml | 1 + 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 92dc14bec..4f5efaea5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,7 @@ settings: overrides: dompurify@<=3.4.10: ^3.4.11 postcss@<8.5.10: ^8.5.10 + nanoid@<3.3.17: ^3.3.17 '@hono/node-server@<1.19.13': ^1.19.13 hono@<4.12.25: ^4.12.25 picomatch@<4.0.4: ^4.0.4 @@ -5556,13 +5557,8 @@ packages: nan@2.27.0: resolution: {integrity: sha512-hC+0LidcL3XE4rp1C4H54KujgXKzbfyTngZTwBByQxsOxCEKZT0MPQ4hOKUH2jU1OYstqdDH4onyHPDzcV0XdQ==} - nanoid@3.3.12: - resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - nanoid@3.3.16: - resolution: {integrity: sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==} + nanoid@3.3.17: + resolution: {integrity: sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -12636,9 +12632,7 @@ snapshots: nan@2.27.0: optional: true - nanoid@3.3.12: {} - - nanoid@3.3.16: {} + nanoid@3.3.17: {} napi-postinstall@0.3.4: {} @@ -13045,19 +13039,19 @@ snapshots: postcss@8.5.14: dependencies: - nanoid: 3.3.12 + nanoid: 3.3.17 picocolors: 1.1.1 source-map-js: 1.2.1 postcss@8.5.20: dependencies: - nanoid: 3.3.16 + nanoid: 3.3.17 picocolors: 1.1.1 source-map-js: 1.2.1 postcss@8.5.25: dependencies: - nanoid: 3.3.16 + nanoid: 3.3.17 picocolors: 1.1.1 source-map-js: 1.2.1 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index c7fabca7c..3fa8ec2d3 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,6 +1,7 @@ overrides: "dompurify@<=3.4.10": "^3.4.11" "postcss@<8.5.10": "^8.5.10" + "nanoid@<3.3.17": "^3.3.17" "@hono/node-server@<1.19.13": "^1.19.13" "hono@<4.12.25": "^4.12.25" "picomatch@<4.0.4": "^4.0.4" From 4bbf4c6d1050a8bb0d9c752b2b661735fecd0ab1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sat, 8 Aug 2026 00:38:51 +0200 Subject: [PATCH 2/3] ci: three integration shards, not two Two shards cleared in about fourteen minutes on a quiet day, but with six pull requests running side by side the larger shard hit the same 25-minute guard the split was meant to escape. Three shards keep the biggest slice around sixty files, which clears the guard with real headroom on the slowest runner observed. The gate job is unchanged. --- .github/workflows/integration.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index ceb9557f5..a4add73d3 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -10,21 +10,21 @@ permissions: contents: read jobs: - # The suite outgrew a single runner: 128 files took ~13 minutes when the - # 25-minute guard was written, and the v1.37 sharing work took it to 173 - # files and past 25 minutes of wall clock, so every run died at the timeout - # with every test to that point green. The guard's own rule applies — split, - # don't raise — so the files run as two vitest shards. Each shard boots its - # own Postgres testcontainer; the timeout stays a hang detector, sized so a - # healthy half-suite clears it on a slow runner. + # The suite outgrew a single runner, and then a half-suite outgrew a slow + # one: two shards cleared in ~14 minutes on a quiet day, but with six pull + # requests running side by side the larger shard hit the same 25-minute + # guard the split was meant to escape. Three shards keep the biggest slice + # around 60 files, which clears the guard with real headroom even on the + # slowest runner observed. Each shard boots its own Postgres testcontainer; + # the timeout stays a hang detector, not a performance budget. shard: runs-on: ubuntu-latest timeout-minutes: 25 strategy: fail-fast: false matrix: - shard: [1, 2] - name: integration shard ${{ matrix.shard }}/2 + shard: [1, 2, 3] + name: integration shard ${{ matrix.shard }}/3 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -44,7 +44,7 @@ jobs: # vitest a stray positional argument and the shard flag never applies — # both "shards" then run the full suite. Proven by a paired local run # before this workflow shipped. - - run: pnpm exec vitest run --config vitest.integration.config.mts --shard=${{ matrix.shard }}/2 + - run: pnpm exec vitest run --config vitest.integration.config.mts --shard=${{ matrix.shard }}/3 env: # The integration suite boots a Postgres testcontainer and points # Prisma at it via DATABASE_URL set inside `tests/integration/setup.ts`. From 0849d59ecf445b4b1fed4d6e1b747919cdf601f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sat, 8 Aug 2026 01:02:08 +0200 Subject: [PATCH 3/3] test(cycle): pin the fixture clock to the suite's clock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two cycle suites compute their relative day keys with local Date arithmetic under TZ=UTC while the routes resolve today in the user's zone, falling back to Europe/Berlin when the fixture sets none. For two hours every evening the two clocks disagree about the date and every day-of-cycle assertion is off by one — the suite was red from 22:00 UTC to midnight and green the rest of the day. The fixtures now carry an explicit UTC timezone so the test's clock and the route's clock are the same clock at any hour. Reproduced red at 23:07 UTC, green with the pin at 23:19 UTC. --- tests/integration/cycle-flow-opens-cycle.test.ts | 8 ++++++++ tests/integration/cycle-routes.test.ts | 9 +++++++++ 2 files changed, 17 insertions(+) diff --git a/tests/integration/cycle-flow-opens-cycle.test.ts b/tests/integration/cycle-flow-opens-cycle.test.ts index 5e81fda56..7a9fe7d84 100644 --- a/tests/integration/cycle-flow-opens-cycle.test.ts +++ b/tests/integration/cycle-flow-opens-cycle.test.ts @@ -70,6 +70,14 @@ beforeEach(async () => { username: "cycle-flow", email: "cycle-flow@example.test", gender: "FEMALE", + // The suite runs under TZ=UTC and computes its relative day keys with + // local Date arithmetic, i.e. in UTC. The route resolves "today" in the + // USER's zone and falls back to Europe/Berlin when none is set - one + // hour past 22:00 UTC the two clocks disagree about the date and every + // day-of-cycle assertion is off by one. Pinning the fixture zone to UTC + // makes the test's clock and the route's clock the same clock at any + // hour of the day. + timezone: "UTC", }, }); await loginAs(USER_ID); diff --git a/tests/integration/cycle-routes.test.ts b/tests/integration/cycle-routes.test.ts index 8dc05e92b..d75c8822f 100644 --- a/tests/integration/cycle-routes.test.ts +++ b/tests/integration/cycle-routes.test.ts @@ -69,6 +69,14 @@ beforeEach(async () => { username: "cycle-female", email: "cycle-female@example.test", gender: "FEMALE", + // The suite runs under TZ=UTC and computes its relative day keys with + // local Date arithmetic, i.e. in UTC. The route resolves "today" in the + // USER's zone and falls back to Europe/Berlin when none is set - one + // hour past 22:00 UTC the two clocks disagree about the date and every + // day-of-cycle assertion is off by one. Pinning the fixture zone to UTC + // makes the test's clock and the route's clock the same clock at any + // hour of the day. + timezone: "UTC", }, }); await prisma.user.create({ @@ -77,6 +85,7 @@ beforeEach(async () => { username: "cycle-male", email: "cycle-male@example.test", gender: "MALE", + timezone: "UTC", }, }); });