You either die a hero... #710

Merged
Griefed merged 15 commits from develop into main 2026-10-11 15:41:46 +02:00
Owner
No description provided.
Griefed self-assigned this 2026-10-10 22:50:52 +02:00
A release failed on 2026-10-07 because the Windows app-image job existed twice,
in `devbuild.yml` and in `release-build.yml`, with two different invocation
shapes -- and only the rehearsed one worked. Everything here follows from that:
the immediate breakages, then the structure that let one job drift from its
twin, then a runner host that can be asked what went wrong.

Verified by a real run: the Windows app-image builds.

THE BREAKAGES

  - the Windows step wrote its output with a UTF-8 BOM. Forgejo parses
    `$GITHUB_OUTPUT` byte-wise, so the first key silently carried a BOM in its
    name and the value arrived empty at the consumer.
  - `-JarPath` then received that empty string and advised a Gradle build
    instead of failing, turning a broken hand-off into a confusing success.
    It now refuses an empty path.
  - the dev build shipped install4j media built at `version=dev` rather than at
    the build's own version, and died if the install4j installer was already
    absent.
  - `devbuild.yml`'s jar job was missing the Gradle setup every other job had.
  - the nightly published `main` as the dev build.

THE STRUCTURE

`artifacts-reusable.yml` is one definition of what a build produces: `jars`
once, then `appimage`, `winimage` and `media` from those same bytes, fanned
into `stage`. The contract is the `release-assets` artifact, so a caller has no
collection step of its own to drift. `devbuild.yml` calls it.

Not yet `release-build.yml` -- it still has its own `winimage` and `assets`
jobs. The file header and claude-docs/WINDOWS-RUNNER.md both say otherwise and
are wrong about it; that is the next piece of work, not a finished claim.

Underneath, three composite actions remove the repeated job preamble
(`setup-build`, version resolution, registry login), `test.yml`, `qodana.yml`
and four more jobs take them, and a check fails the build when a workflow step
is written twice. Five repeats survive and are documented as unshareable rather
than left looking like oversights -- `secrets: inherit` is all-or-nothing on
Forgejo, so `on.workflow_call.secrets` cannot narrow what a caller passes.

Measured rather than assumed, in throwaway probes that are deleted again here:
runs 987 and 988 answered how Forgejo propagates a failing job out of a
reusable workflow, and that a workflow absent from the default branch is
invisible to the dispatch menu but reachable by REST against an explicit ref.

THE RUNNER HOST

`misc/windows-runner/` replaces a `dockurr/windows` container with a libvirt
domain. During run 1005 the app-image job sat in `git checkout` for 38 minutes
with the container's own console unresponsive -- a stall below Windows, which
the container offered no way to interrogate and `virsh domstate`, `domjobinfo`
and `domblkstat` answer in seconds.

`domain.xml` is the whole definition, validated against libvirt's RelaxNG
schema, with the Windows 11 requirements that a hand-written file loses: q35
for SMM for Secure Boot, firmware auto-selection rather than a hardcoded OVMF
path, an emulated TPM 2.0, suspend disabled because a sleeping runner is an
offline runner with no error anywhere, and a NAT-only NIC so nothing outside
the host can reach an unauthenticated runner. The guest agent channel makes it
answerable when Windows is not.

The job's checkout is sparse -- 182 files of 2570, where a full checkout is
what hung -- and the runner binary is built in the guest, so the provisioning
log names the commit that produced it. There is no published Forgejo runner
binary for Windows.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
fix(ci): the release build takes its artifacts from the shared definition
All checks were successful
Continuous / Preparations (push) Successful in 27s
Documentation / Writerside webhelp (push) Successful in 4m20s
Documentation / Dokka HTML (push) Successful in 6m57s
Grinder Container IT / containers (push) Successful in 9m22s
Docker Test / build image (push) Successful in 16m55s
Qodana / scan (push) Successful in 12m48s
Documentation / Help image (push) Successful in 2m28s
Qodana / notify (push) Successful in 36s
Continuous / JARs (push) Successful in 12m41s
Test / build (push) Successful in 13m17s
Continuous / AppImage (aarch64) (push) Successful in 2m20s
Continuous / AppImage (x86_64) (push) Successful in 2m13s
Continuous / Windows app-image (x86_64) (push) Successful in 8m45s
Continuous / install4j media (push) Successful in 7m21s
Continuous / Stage the release assets (push) Successful in 2m1s
Continuous / Build (push) Successful in 0s
Continuous / Continuous Pre-Release (push) Successful in 3m13s
3a71fe746d
`release-build.yml` kept its own `winimage` and `assets` jobs, so the "one
definition" the shared workflow was built to be had two callers' worth of
claims and one caller. Three places said otherwise and were wrong about it:
the header of `artifacts-reusable.yml`, `claude-docs/WINDOWS-RUNNER.md`, and
the commit that merged the branch. They are true now.

`fix:` rather than `refactor:`, because behaviour changes on both paths. What
a release does differently:

  - the Windows app-image is inside `release-assets` and therefore inside
    `checksum.txt`. It could not be before: `assets` wrote the checksums and
    `winimage` ran after it, so the artifact most worth verifying was the one
    file the checksum list did not cover. `stage` runs after `winimage`.
  - `winimage` no longer runs after `assets`, and byte-identity is kept rather
    than bought: the shared `jars` job builds once and every consumer takes
    that artifact, so the Windows image and the released jar are the same bytes
    with the two jobs in parallel instead of in series.
  - per-artifact `.sha256` files now ship on the release path too; they were
    dev-only. `updates.xml` and the API reference now ship on the dev path;
    they were release-only. One collection step, so the two sets cannot differ.
  - the plugin-jar count is checked on a release (it was dev-only), and the
    dokka zip is produced unconditionally instead of behind `if [ -d ... ]`,
    which failed open on every rehearsal and ran for the first time on release
    day.
  - `release`, `mirror` and `virustotal` no longer merge `winimage-*` in
    separately; it is already in `release-assets`.

And on both paths:

  - the `media` job regains `-x :serverpackcreator-api:test -x
    :serverpackcreator-app:test`. The root build declares
    `tasks.register<Install4jTask>("media") { dependsOn(tasks.build) }`, so
    without them it re-runs the whole api and app suite that `jars` just
    skipped and `test.yml` already ran on the commit. release-build.yml's old
    `assets` job had the exclusions; `artifacts-reusable.yml` never did, so the
    dev build has been paying for a second full test run and the conversion
    would have put one on the release's critical path.

`virustotal`'s comment claimed the Windows app-images are excluded by not being
downloaded. They were not, and are not: the loop globs `*.jar *.sh *.dmg *.exe`
and a `.zip` never matched it. The comment now says what actually excludes them.

misc/ci-duplication-check.py went from 5 unexplained repeats to 0. Three
disappeared with the duplicate jobs (`setup-install4j`, `Remove install4j
script`, the `winimage-*` download), and the two that remain are allowlisted
with the argument rather than silenced: both are one producer and three
consumers of an artifact, and a step cannot be shared between jobs except
through a composite action, whose files arrive only with a checkout -- which
`mirror` and `virustotal` do not have and need nothing else from.

NOTE, because it is a gap rather than a fix: nothing runs that check. It is in
no workflow, which is why it was already red at HEAD and nobody knew.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
perf(runner): tune the Windows runner guest, and the guide that builds it
All checks were successful
Continuous / Preparations (push) Successful in 38s
Documentation / Writerside webhelp (push) Successful in 3m51s
Documentation / Dokka HTML (push) Successful in 4m34s
Grinder Container IT / containers (push) Successful in 10m45s
Docker Test / build image (push) Successful in 18m9s
Qodana / scan (push) Successful in 15m30s
Test / build (push) Successful in 9m16s
Qodana / notify (push) Successful in 13s
Documentation / Help image (push) Successful in 3m52s
Continuous / JARs (push) Successful in 9m31s
Continuous / AppImage (aarch64) (push) Successful in 2m55s
Continuous / AppImage (x86_64) (push) Successful in 2m56s
Continuous / Windows app-image (x86_64) (push) Successful in 6m17s
Continuous / install4j media (push) Successful in 8m9s
Continuous / Stage the release assets (push) Successful in 2m0s
Continuous / Build (push) Successful in 0s
Continuous / Continuous Pre-Release (push) Successful in 3m48s
0817b783de
The Windows guest exists because `jpackage` cannot cross-compile the Windows
app-image, so a release waits on it. It was built to work, never to be fast.
This makes the guest faster on the same hardware, and makes the guide produce
that guest the first time rather than after an offline edit.

HOST SIDE -- domain.xml

Hyper-V enlightenments gain `<runtime/>` and a flattened `<tlbflush/>`;
`<vmport/>` goes off; spinlock retries drop 8191 -> 4095. The CPU topology
becomes 4 cores x 1 thread rather than 2 x 2, because threads='2' without
`<vcpupin>` describes sibling pairs that do not exist and lies to the Windows
scheduler about which vcpus share a core.

`vmx` and `svm` are withheld. Microsoft turns Memory Integrity on through
quality updates, and VBS runs the Windows kernel under a Hyper-V root
partition -- which, inside KVM, makes this guest a nested hypervisor and taxes
every exit twice. A registry switch is something an update can switch back; a
CPU feature the guest was never given is not. The cost is stated rather than
hidden: nothing in the guest can use a hypervisor any more.

Storage gains an IOThread, four virtqueues to match the vcpus, and
`discard='unmap'`. That last one is half a setting. Windows' retrim issues one
request per reported granularity, and virtio-blk unset reports a single block,
so a 10 GiB retrim becomes millions of requests -- Red Hat bug 2020998 measures
615.61 s at 4 KiB against 1.22 s at 32 MiB on the same volume, and the viostor
fix in virtio-win build 229 narrows that to 3 min 25 s against under 10 s
rather than closing it. Hyper-V reports 32 MiB, which is where the value comes
from.

It is set through `<qemu:commandline>` and a raw `-set` because that is the
spelling every libvirt understands. The native `<blockio discard_granularity>`
needs libvirt 10.0.0 and `<qemu:override>` needs 8.2.0; Ubuntu 22.04 ships
8.0.0, Debian 12 ships 9.0.0. For the same reason `<xmm_input/>` (libvirt
10.7.0) and `<tlbflush><extended/>` (11.0.0) are commented out rather than
shipped -- a domain that will not define is worse than one that is marginally
slower.

KSM is kept off this domain with `<nosharepages/>`, and the balloon gains
`freePageReporting`.

GUEST SIDE -- optimise.ps1

Every `powercfg`, `bcdedit` and `fsutil` call in this script reported `ok`
regardless of what it did: native commands set $LASTEXITCODE instead of
throwing. `Invoke-Native` makes a non-zero exit a failure, which is the reason
to trust anything else in the file.

VBS and HVCI are turned off from inside as well, and `hypervisorlaunchtype` is
set to off. Not redundancy for its own sake -- it is what makes the guest
correct on a host whose domain definition predates the CPU-feature change.

31 services and 20 scheduled tasks are disabled, behind a `$requiredServices`
guard. That guard is pinned by a mutation test rather than by observation:
putting `Schedule` into the disable table makes the run print `skip  Schedule
is required by this guest` while the ordinary entries still go down. The four
Defender tasks are listed knowing Tamper Protection refuses them, so the run
reports them as refused rather than pretending.

The Defender exclusions named `$env:USERPROFILE\.gradle`, which is the
provisioning admin's profile. Jobs run as SYSTEM. The paths now come from
`ProfileList\S-1-5-18`, so they are the profile the builds actually use.

GUEST SIDE -- provision.ps1

A fresh Windows client defaults its execution policy to `Restricted`, and act
runs every `run:` step by writing a .ps1 and invoking `powershell -command .
'<file>'`. So every step of every job on this runner is a PowerShell script
that Windows refuses to run, which is what has blocked builds here before.
LocalMachine is set to `Bypass` -- LocalMachine because jobs run as SYSTEM
rather than as the provisioning user, and Bypass rather than RemoteSigned
because RemoteSigned's zone check needs the Windows Desktop Shell and fails
with `AuthorizationManager check failed` without it.

The guard asserts the *effective* policy and names the scope responsible when
it is wrong. A scope-by-scope scan would have missed `Set-ExecutionPolicy` not
taking at all, which is the failure worth catching.

THE GUIDE

The performance work was written after the guide, so it arrived as a section at
the end whose first subsection was "Applying this to a guest that already
exists" -- read in order, the guide built an untuned VM and then told you to
shut it down and edit it. The host profile now sits in §1, the disk format in
§3, the memory backing in §4, the Windows Update decision in §6, the
measurements in §7, and retrofitting in an appendix. The `virsh edit` namespace
trap went with it: a domain defined from the shipped `domain.xml` already
carries `xmlns:qemu` and cannot hit it.

Also documented: converting an existing image to extended_l2, where the parts
that bite are not the conversion but the guest having to be shut off (or
`qemu-img convert` copies a torn image silently), the file the domain actually
uses, the internal snapshots dropped without a word, and swapping by rename so
neither the XML nor the AppArmor profile is touched.

VERIFICATION

The PowerShell is not executable on this workstation, so the service, task and
clock blocks were sliced verbatim out of the script and run against stubs
rather than reimplemented: both clock branches, 29 services disabled with
RetailDemo and Fax correctly reported absent, 16 non-Defender tasks disabled
with the four Defender ones refused, and the SYSTEM profile read from the
registry. The execution-policy guard passes 7 of 7 cases against the real
ExecutionPolicy and ExecutionPolicyScope enums.

Four failures during that work were harness bugs, not production defects, and
one "fix" was neither: `-At 03:00` binds to a DateTime perfectly well, and the
change to `-At ([datetime]'03:00')` fixed nothing. The real fault was a stub
declaring `-StartWhenAvailable` as a value parameter rather than a switch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`vitest.config.js` imports `@vitejs/plugin-vue` and `@quasar/vite-plugin` at its
top, and neither has ever been in `package.json`. They resolve only because npm
hoists them flat out of `@quasar/app-vite`'s dependency tree, which makes the
test configuration depend on a transitive layout nobody declared and nothing
guarantees.

That is fine until app-vite's own dependency set moves. The next commits take it
across a major, so this lands first and on its own: both are pinned at the
versions already resolved in the lockfile, so nothing changes shape today.

The lockfile moves by exactly two lines - the packages were already there at
these versions, they were simply not reachable from any declaration.

Verified: 15 files / 37 tests, unchanged from before; `quasar build` produces
its output folder.

Note for anyone grepping for a third: `postcss-rtlcss` appears in
`postcss.config.js` but only inside a commented-out example, so it is not an
undeclared import and is not added here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`npm update`, with no change to `package.json`. Every direct dependency sat at
the exact FLOOR of its own caret range - axios 1.16.0 for `^1.16.0`, quasar
2.19.3 for `^2.19.3`, and so on - so the lockfile had never been refreshed since
those ranges were written, and transitive dependencies had been frozen at
whatever first resolved.

This is the single highest-value commit of the sweep. Measured against npm's
bulk advisory endpoint, before and after, over the lockfile's full resolved set:

  before   29 vulnerable packages,  83 advisories   (4 critical, 45 high, 30 moderate, 4 low)
  after     8 vulnerable packages,  12 advisories   (2 critical,  6 high,  3 moderate, 1 low)

71 of 83 advisories closed without editing a single declared range.

Direct dependencies that moved, and what they fix:

  axios        1.16.0  -> 1.20.0   13 advisories (6 high, 7 moderate)
  quasar       2.19.3  -> 2.35.0   the CRITICAL unescaped-SSR-meta XSS, plus 2 more
  vue          3.5.35  -> 3.5.43   @vue/server-renderer XSS (needed >= 3.5.42)
  vue-i18n    11.1.12  -> 11.4.14
  vue-router    4.6.3  -> 4.6.4

Plus 21 transitive packages, `brace-expansion` alone accounting for 16 of the
closed advisories.

What is left is deliberate, and three groups:

  - @quasar/app-vite 2.6.2 and its two @quasar/* children: `^2.4.0` cannot reach
    the fix, which needs > 3.2.0. Next commit.
  - vitest 3.2.7, @vitest/mocker, tinypool, esbuild: `^3.2.6` cannot reach the
    fix, which needs >= 4.1.11. The commit after that.
  - braces 3.0.3, reached through @intlify/unplugin-vue-i18n -> fast-glob ->
    micromatch. THIS ONE HAS NO FIX. The advisory covers <= 3.0.3 and 3.0.3 is
    the newest version published, so no amount of updating closes it; micromatch
    4.0.8 still asks for ^3.0.3. It is build-time glob matching in a devDependency
    and has to be dismissed on the alert page rather than fixed here.

Verified: 15 files / 37 tests, unchanged; `quasar build` succeeds.

Noted while here, not fixed: npm reports all three options in
`serverpackcreator-web-frontend/.npmrc` as unknown project config
(`shamefully-hoist`, `strict-peer-dependencies`, `resolution-mode`). They are
pnpm options in an npm project and have never done anything.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Not a discretionary bump. `@quasar/app-vite@3.10.2` declares a peer dependency
of `vue-router: ">= 5"`, so the security fix in the next commit cannot land
while the router is on 4.x. It goes in first and on its own, because it is the
only change here that touches a RUNTIME dependency rather than build tooling,
and a separate commit is what makes it bisectable if the SPA misbehaves.

The migration surface turned out to be two files. `src/router/index.ts` uses
`createRouter`, the three history factories and `scrollBehavior`;
`src/router/routes.ts` uses the `RouteRecordRaw` type. No navigation guards, no
`addRoute`, no dynamic routing - so none of what vue-router 5 changed is
reachable from this application, and neither file needed an edit.

vite moved 7.3.5 -> 8.3.4 along with it: vue-router 5.4.0 peers on
`vite: ^7.3.0 || ^8.0.0` and npm resolved the newer. Its other peers were
already satisfied - vue 3.5.43 against `^3.5.34`, pinia 3.0.4 against `^3.0.4`.

This is Dependabot PR #1291, which was closed unmerged.

Verified: 15 files / 37 tests, unchanged; `vue-tsc --noEmit` clean; `quasar
build` succeeds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`^2.4.0` cannot reach the fix: the advisories cover <= 3.2.0, so the range has
to cross a major. Taking 3.10.2 rather than the 3.3.0 floor costs nothing extra -
same major, same migration - and avoids repeating this in a month.

Clears 6 of the 7 advisories that were left outside vitest:

  @quasar/app-vite          3 high + 1 moderate   (dev TLS key permissions, SSR/SSG
                                                   error page env disclosure, unsafe
                                                   SSR nonce, recursive build cleanup)
  @quasar/render-ssr-error  1 high                transitive, same disclosure
  @quasar/ssl-certificate   1 high                transitive, same key permissions
  esbuild                   1 low                 pulled forward by vite 8

Running total: 29 packages / 83 advisories at the start of this branch, 4 / 5 now.

THE MIGRATION IS WIDER THAN THE BUMP, and all of it is forced:

1. `#q-app/wrappers` is gone. The wrappers moved to the package's own main
   export, so `defineConfig`, `defineBoot`, `defineRouter` and `defineStore` now
   come `from '@quasar/app-vite'`. Five files: `quasar.config.js`, both boot
   modules, the router and the store index.

2. `build.allowOutsideProjectDistDir` is now required. app-vite 3 refuses a dist
   directory outside the project, and this SPA is deliberately built straight
   into `serverpackcreator-app/src/main/resources/static` so the Spring app can
   serve it. The flag sits beside `distDir` with the reason, because the pairing
   is what makes either readable.

3. THE PATH ALIASES ARE RENAMED. app-vite 2 generated one alias per directory -
   `src/`, `boot/`, `stores/`, `components/`, `layouts/`, `pages/`, `assets/`.
   app-vite 3 generates exactly one, `@`, pointing at `src/`. That is not
   configuration this project chose; it is what `quasar prepare` writes into
   `.quasar/tsconfig.json`, which the root tsconfig does nothing but extend.

   67 specifiers across 31 files, rewritten mechanically and verified by grep
   leaving no occurrence of the old scheme behind. Three forms needed separate
   handling, and the last two are why the suite went red in between:

     - `from`/`import()`/`vi.mock()` specifiers        -> `@/...`
     - Quasar's `~assets/x` in .vue template attributes -> `~@/assets/x`
     - `url("assets/x")` in src/css/app.scss            -> `url("@/assets/x")`

   The first pass caught only the first form, and the suite dropped from 37
   tests to 23 - three files failing to resolve webp and svg imports that live
   in markup rather than in an import statement. That is the whole reason this
   branch asserts a test COUNT rather than a green tick.

4. `vitest.config.js` carried its own copy of the per-directory alias map. It is
   now the single `@` alias, so the test resolver and the app resolver cannot
   drift apart again.

vue-tsc, ESLint and the comments in three test files all referred to the old
scheme; the comments are corrected rather than left to rot, and the module's
CLAUDE.md gains a short "Path aliases" section, because a newcomer writing an
import now has no way to guess the convention from the surrounding code alone.

Verified: 15 files / 37 tests, matching the pre-branch baseline exactly;
`quasar build` succeeds; `vue-tsc --noEmit` clean; `eslint` clean.

This is Dependabot PR #1296 and Renovate PR 705, both unmerged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`^3.2.6` cannot reach the fix, which needs >= 4.1.11. Latest is 5.0.3, so this
crosses two majors at once - taken deliberately, because the alternative is
doing the same migration again shortly for the same reason.

Clears the last four fixable advisories:

  tinypool        2 CRITICAL   prototype-pollution gadgets to RCE in worker and
                               run() options. vitest 5 drops the dependency
                               outright, so the package is gone rather than bumped.
  @vitest/mocker  1 moderate   path traversal / arbitrary file read
  vitest          1 moderate   the same, through the mocker

That takes this branch from 29 vulnerable packages / 83 advisories to 1 / 1.

A PASSING TEST RUN IS NOT EVIDENCE FOR THIS PARTICULAR BUMP. A vitest major can
change config shape or the default `include` glob so that zero tests run and the
command still exits 0, which reads exactly like success. The repo's own rule
covers it: a check that cannot run must skip, never pass. So two things were
checked instead of one.

First, the count. 15 files / 37 tests, byte-identical to the baseline taken
before any commit on this branch - not merely "green". `vitest.config.js` keeps
its explicit `include` and `setupFiles`, and both are still honoured.

Second, that the assertions still bite. Two mutations were planted and the suite
re-run:

  src/utils/format.ts   BINARY_UNITS[0] 'B' -> 'WRONG'
      -> RED, `expected '0 WRONG' to be '0 B'`. The runner is genuinely
         executing assertions under 5.0.3, not collecting zero of them.

  DrawerLink.vue        color="accent" -> color="MUTATED"
      -> GREEN, and that is a REAL FINDING, not a vitest problem.
         `test/components/DrawerLink.test.ts` asserts the icon's name but never
         its colour, so the component's colour is unguarded. The module's own
         CLAUDE.md records a bug where this prop was misspelled `colour=` and
         every drawer icon silently rendered in the default colour; the fix
         landed, a guard against it recurring did not. Fixed in the next commit,
         separately, because a test addition is not a dependency bump.

Verified: 37/37 matching baseline; `quasar build` succeeds; `vue-tsc --noEmit`
clean; `eslint` clean.

This is Dependabot PR #1241 and Renovate PR 706, both unmerged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Found by mutation while verifying the vitest 5 bump: changing
`color="accent"` to `color="MUTATED"` in DrawerLink.vue left the whole suite
green. The component's colour was unguarded.

That is not a hypothetical. This module's CLAUDE.md records the bug: the prop
was once written `colour="accent"`, Vue silently dropped the unknown attribute,
and every drawer icon rendered in the default colour. The fix landed. A guard
against it recurring did not, so the same typo would land again unnoticed - and
the failure mode is the quiet kind, a page that renders perfectly and simply
looks slightly wrong.

The assertion is on the rendered class rather than the prop, because the prop is
exactly what a typo makes disappear: Quasar turns `color="accent"` into
`text-accent` on the QIcon, and a misspelled prop produces an icon with no
colour class at all.

Run against the mutation before being committed: with `colour=` restored the
guard fails with `expected [ 'q-icon', 'on-left', ... ] to include 'text-accent'`,
and with `color=` it passes. Suite 37 -> 38.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ten actions were behind, several by two majors. All of them now run on the
node24 runtime, which is the substantive part: the node20-based releases they
were pinned to are the ones GitHub is retiring.

  actions/checkout                 v6 -> v7   25 lines
  actions/setup-java               v5 -> v6    6
  actions/cache                    v5 -> v6    2
  actions/github-script            v7 -> v9    4
  actions/setup-node               v6 -> v7    1
  peter-evans/create-pull-request  v7 -> v8    1
  docker/build-push-action         v6 -> v7    5
  docker/setup-qemu-action         v3 -> v4    2
  docker/setup-buildx-action       v3 -> v4    2
  docker/login-action              v3 -> v4    2

DONE ACROSS BOTH TREES, and that is the point. Dependabot's `github-actions`
entry is `directory: "/"`, which reaches `.github/workflows` and nothing else -
so the 40 `uses:` lines under `.forgejo/` are watched by no bot at all and drift
until somebody does this by hand. Which is why they were up to two majors
behind while the `.github` copies were merely one.

Every bump was checked before it was made, not after: for each action, the
inputs this repository actually passes were parsed out of the workflows by
indentation, then looked up in that major's own `action.yml`. All present, no
removals - so nothing here is a silent behaviour change at the input level.
`actions/setup-gradle@v6` and `actions/upload-artifact@v7` were already current
and are untouched.

Deliberately NOT bumped, each for a reason already recorded in the repo:

  - `code.forgejo.org/forgejo/upload-artifact@v5` (12 lines) and
    `download-artifact@v7` (10). The forks lag upstream on purpose; v5 and v7
    are the newest majors the forks publish, and upstream's majors abort off
    github.com with GHESNotSupportedError.
  - `tiyee/action-ssh@v1.0.1`, `nogsantos/scp-deploy@v1.3.0`,
    `luangong/setup-install4j@v1`, `jmgilman/actions-generate-checksum@v1` -
    the four non-bare-major refs, each with a recorded reason.

Version TAGS, not SHAs, per .claude/rules/ci-workflows.md - Griefed's explicit
call, and this commit is the argument for it: ten actions had drifted a major or
two behind, which is exactly the staleness an unrevisited SHA pin guarantees.

Checked before committing: every target major is mirrored on data.forgejo.org,
so the bare references under `.forgejo/` still resolve there; no stale reference
of any bumped action remains anywhere; all 24 workflow files still parse as YAML.

CI cannot be run locally, so the runtime proof is the next push.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Renovate on git.griefed.de has been running against a config written for a
version of Renovate that no longer exists, which is why it opened PR 708 against
its own configuration. Three things in it were removed or renamed upstream:

  config:base        renamed to config:recommended
  :enableGradleLite  removed, along with the `gradle-lite` object it enabled
  matchPaths         renamed to matchFileNames

`gradle-lite` is the one that mattered. It was the whole Gradle half of
Renovate's remit here, and it has not been a thing for some time - so Renovate
has been managing npm and nothing else. Replaced with `gradle: { enabled: true }`,
which is what the migration calls for.

Two changes beyond Renovate's own diff:

`baseBranches: ["develop"]`. Renovate had none, so it targeted the default
branch - main. This project's convention is that PRs target develop and main is
the release branch, so every Renovate PR has been aimed at the wrong branch
since it was set up. All six currently open ones (703-708) are against main.

The `packageRules` entry is dropped rather than migrated. It read
`{ extends: "config:base", matchPaths: ["package.json"] }`, which extends an
entire preset from inside a single rule and matches `package.json` at the
repository root. There is no package.json at the root - the only one is
`serverpackcreator-web-frontend/package.json` - so the rule matched nothing and
would have been a no-op even if the path had been right. Renovate's migration
carried it across mechanically; there is no reason to keep it.

The five open dependency PRs are superseded by earlier commits on this branch:
703 (axios 1.20.0) and 704 (quasar 2.32.2) by the lockfile refresh, 705
(@quasar/app-vite v3) and 706 (vitest v4) by the two major bumps, 707 (pin
dependencies) by declaring the two hoisted build plugins. They can be closed, or
left for Renovate to retire on its next run against develop.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Dependabot had three Gradle entries - `/serverpackcreator-api`,
`/serverpackcreator-app`, `/serverpackcreator-plugin-example` - and no entry for
`/`. Every module in this build declares its dependencies as `libs.*` aliases
with no literal coordinate (checked: 21, 30 and 6 aliases respectively, and zero
literal coordinates between them, the single apparent hit being a commented-out
line in -api). The versions all live in `gradle/libs.versions.toml`, at the root,
which nothing was watching.

So Dependabot's Gradle coverage here was zero, and had been. The catalog is
nonetheless fully current - checked against repo1.maven.org maven-metadata.xml,
every stable entry is the newest stable - because it has been bumped by hand.
The stale Gradle PRs that kept arriving and getting closed proposed versions the
catalog had already passed, which is the symptom this explains.

Now `directories:` with "/" first, the same style the docker block already uses
for the same reason. The nine module directories are listed too, including the
five that were never watched at all (-clientside, -grinder and three plugin
modules), so a literal coordinate added to any of them later is still seen.

Also recorded, as a comment rather than a change, because it cannot be fixed
here: the `github-actions` entry reaches `.github/workflows` and nothing else.
40 of this repository's 55 `uses:` lines are under `.forgejo/workflows`, and
Dependabot has no way to read them. They drift until bumped by hand - as the
previous commit had to, finding ten actions up to two majors behind. The comment
exists so nobody reads a quiet Dependabot as evidence the actions are current.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The file held three settings and npm rejects all three:

  npm warn Unknown project config "shamefully-hoist"
  npm warn Unknown project config "strict-peer-dependencies"
  npm warn Unknown project config "resolution-mode"

They are pnpm options, as the file's own first line said - "# pnpm-related
options" - left behind by the Quasar scaffold, which supports pnpm. This project
does not: `packageManager` is `npm@11.16.0`, and no pnpm-lock.yaml or yarn.lock
has ever existed in this repository's history.

So the file changed nothing, and printed three warnings on every npm invocation
in the module - which is how it got noticed, cluttering the output of the
verification runs on this branch. With nothing left in it, the file goes rather
than being emptied.

`resolution-mode=highest` is the one worth naming, because it reads like it
mattered: it would have made pnpm prefer the newest matching version. It was
inert, and the lockfile proved it - every direct dependency sat at the exact
floor of its range until `npm update` was run earlier on this branch.

Verified: 15 files / 38 tests; `quasar build` succeeds; both now run warning-free.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Griefed <griefed@griefed.de>
Merge branch 'claude-dependency-security-sweep' into develop
All checks were successful
Continuous / Preparations (push) Successful in 32s
Documentation / Writerside webhelp (push) Successful in 2m18s
Documentation / Dokka HTML (push) Successful in 3m54s
Grinder Container IT / containers (push) Successful in 8m58s
Docker Test / build image (push) Successful in 14m2s
Qodana / scan (push) Successful in 12m54s
Documentation / Help image (push) Successful in 3m3s
Qodana / notify (push) Successful in 10s
Continuous / JARs (push) Successful in 9m56s
Continuous / AppImage (aarch64) (push) Successful in 2m44s
Continuous / AppImage (x86_64) (push) Successful in 2m45s
Continuous / Windows app-image (x86_64) (push) Successful in 4m38s
Test / build (push) Successful in 22m38s
Continuous / install4j media (push) Successful in 7m13s
Continuous / Stage the release assets (push) Successful in 2m8s
Continuous / Build (push) Successful in 0s
Continuous / Continuous Pre-Release (push) Successful in 3m32s
d94ec02574
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Griefed/ServerPackCreator!710
No description provided.