ci: the repo's first .github/ - a verify workflow plus Renovate with the holds encoded - #56
Conversation
… history) `pnpm run verify` was the whole quality gate and it only ran when a human remembered to, or at deploy time - which can be days after the merge that broke it. `git log --all -- .github` was empty. Closes the structural half of issue #54. The workflow runs `pnpm run verify` verbatim rather than re-listing its three phases, so CI and local cannot drift into two definitions of "consistent". No refs:sync (needs a Factorio binary), no build step, no deploy, no secrets. Third-party actions are pinned to full commit SHAs with the release named in a trailing comment; permissions are `contents: read` only. Renovate over Dependabot because the holds here need encoding, not ignoring: typescript is disabled outright, pako carries a 14-day age and a note pointing at the byte-exactness invariant, wrangler + vitest-pool-workers are grouped because pool-workers hard-pins wrangler, and the brace-expansion override and `engines.node` floor are both marked not-a-stale-version. `minimumReleaseAge` is declared at 3 days specifically so Renovate can never propose a release fresh enough to make pnpm write a `minimumReleaseAgeExclude:` bypass. Config validated with `renovate-config-validator`. Renovate stays inert until the GitHub App is enabled on the repo. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F3a2HLJ4beKARi7SPmaM54
…ailed on The first CI run of the new workflow failed on exactly one test, and it was not a bug: `elevationRenderRequest.spec.ts`'s `view 'all' composites all five overlays` needs 9.8s on a 4-core GitHub runner (the whole suite is ~3x slower there - 230s vs 71s locally) against Vitest's 5s default. https://github.com/wormeyman/FactorioMapWebUI/actions/runs/30512820959 Raising the default rather than adding a 25th annotation. 24 individual tests across 10 files already carry an explicit `}, 120000)`, which is the same complaint made 24 times by hand, and elevationRenderRequest.spec.ts has 27 tests with zero annotations - so the annotation approach was already failing in its characteristic way, by depending on the next author remembering. 30s is ~3x the slowest measured case and still fails a genuine hang far inside the job's 15-minute cap; the existing 120000 annotations keep winning where they are set. No retries. These tests compare pixels against captured game output, so nothing here is nondeterministic - a retry could only hide a real regression. Also corrects two things this repo's docs asserted and CI disproved: - `verify` is ~65-90s locally and ~4 minutes on a runner, not the ~9.5s CLAUDE.md claimed. The gap is the point: 63s is where people start skipping a manual gate, which is half the argument for having CI at all. - `.node-version` is machinery now, not documentation - `setup-node` reads it, so it is the version the gate runs on. `engines.node` stays a permissive floor. The README also said "Requires Node 24.18.0 (see `.node-version`)", conflating the floor with the pin; `.node-version` says 26.5.0. Adds a CI section to CLAUDE.md (the conventions the workflow establishes, the Renovate/pnpm minimumReleaseAge interaction, and why branch protection is not configured here) and a verify badge + CI section to the README. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F3a2HLJ4beKARi7SPmaM54
The workflow has actually run, twice, and the first run earned its keepRun 1 - https://github.com/wormeyman/FactorioMapWebUI/actions/runs/30512820959 - FAILURE (4m19s) Every step up to the gate worked first time: checkout, pnpm resolved from It failed on exactly one thing, and it was not a bug in this repo: A 4-core runner is ~3x slower than a dev machine (231s vs 71s for the same suite), which put that test at 9.8s against Vitest's 5s default - and the next one down at 4.0s, i.e. passing by one second. This is the pre-existing too-tight-default problem, surfaced rather than introduced. Run 2 - https://github.com/wormeyman/FactorioMapWebUI/actions/runs/30514284066 - SUCCESS (4m31s), on The 6 skips vs 3 locally are the The failing run is worth as much as the passing one: it is the negative control. A real test failure produced a red job with exit 1, so the check gates rather than merely reporting. How the flaky-timeout risk was handled: raise the default, no retries
Two local findings, both pre-existing, neither caused by this branch
Local Follow-ups deliberately left out of this PR
|
The brace-expansion hold was scoped with `matchDepTypes: ["overrides", "pnpm.overrides"]`, which is a guess: the override lives in pnpm-workspace.yaml rather than package.json, and if the depType Renovate reports for that location is neither string the rule matches nothing and the hold silently does not exist. `renovate-config-validator` cannot catch that - the config is valid, just inert. brace-expansion appears in no manifest in this repo (it is a transitive of js-beautify via @vue/test-utils), so matching by package name alone is both correct and the only form that cannot miss. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F3a2HLJ4beKARi7SPmaM54
#51) Verified locally **and** in CI before merge. ## Local gate `pnpm run verify` exit **0** in 58s on an uncontended box: - format 314 files; lint+type 301 files clean - app **1249 passed / 3 skipped** (142 files, 1 skipped) - worker **12 passed** (4 files) - `wrangler types --check` under 4.115.0: `worker-configuration.d.ts` **up to date**, and the check did not rewrite the file ## CI Rebased onto `672c66e` (the new `.github/` from #56) so the required `verify` check could actually compute; it reports **pass**. Head `90a941b`. ## Independently re-derived, not taken on trust - **Release-age gate clear from the registry:** both published `2026-07-28T19:41Z`, **~32h** old at merge against pnpm 11's 1440-minute default. - **No freshness bypass:** `pnpm-workspace.yaml` is untouched - no `minimumReleaseAgeExclude` block, which was #48's stated acceptance criterion. - Worker still carries **no** `typescript` and **no** `@cloudflare/workers-types` devDependency. ## One note for the record Earlier verify runs on this branch failed with 29-34 **timeout-only** failures. That was CPU starvation, not a defect: two other agents were running full suites on the same 12-core box, and the run got 48s of CPU across 154s of wall time. On a quiet box the same tree reproduces the documented ~63s baseline exactly. #56's `testTimeout: 30_000` removes most of that fragility going forward.
Caught by the CI workflow from #56 on its first real use - `vp check` (no --fix) failed in 27s on two formatting issues. Worth noting the gate earned its keep immediately on a comment-only change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F3a2HLJ4beKARi7SPmaM54
…#52) Verified locally **and** in CI. The guard was proven non-vacuous independently of the author's own plant. ## The `.vue` gap is real, and `check:vue` is what closes it Planted a `TS2322` in **`src/ui/FNumberInput.vue`** (deliberately a different file from the author's `FInfo.vue`, so this is an independent reproduction) inside `<script setup lang="ts">`: | command | result | | --- | --- | | `pnpm vp check` | **exit 0** - "Found no warnings, lint errors, or type errors in 301 files" (blind to it) | | `pnpm run check:vue` | **exit 2** - `src/ui/FNumberInput.vue(16,7): error TS2322: Type 'string' is not assignable to type 'number'.` | | `pnpm run verify` | **exit 2**, and `vp test` never ran - the chain stops at `check:vue` | Reverted, then re-confirmed `check:vue` exit 0. Full red-green-green cycle, so the check can genuinely fail. ## No freshness guard was waived `pnpm-workspace.yaml` is untouched - **no `minimumReleaseAgeExclude` block**, checked in the diff and after a real `pnpm install`. Confirmed from the registry rather than assumed: `vue-tsc@3.3.8` and `@vue/language-core@3.3.8` published `2026-07-22T15:54Z`, so **7.6 days** old, comfortably past pnpm 11's 1440-minute default. The PR's claim that no separate tsconfig is needed also checks out - bare `./node_modules/.bin/tsc --noEmit` now exits **0** with no `Debug Failure`, so the `vue() as Plugin` cast did fix all three tools. ## Local gate `pnpm run verify` exit **0** in 61s: format 316 files, lint+type 301 clean, `vue-tsc` clean (2.2s), app **1249 passed / 3 skipped**, worker **12 passed**. ## CI `verify` check: **pass**, on head `9934a45` rebased onto `5f676ea`. ## Added while merging: three stale phase lists the PR missed `verify` gained a fourth phase, and three places that enumerate the list were left behind (CLAUDE.md was updated, these were not): - `README.md` spelled the gate out as `vp check` + `vp test` + `preview:test` in **two** places and had no `check:vue` entry at all, making the one command that type-checks `.vue` bodies undiscoverable from the README. - `.github/workflows/verify.yml`'s comment said "its **three** phases" - and the count is that comment's whole point, since it explains why CI calls `pnpm run verify` verbatim instead of listing phases as steps. **Comment-only change; no step added and no phase mirrored into CI.** Also resolved the CLAUDE.md conflict with #56, which had rewritten the same `verify` bullet: kept #56's newer measured timings (~65-90s dev / ~4 min CI) and folded in `check:vue`, rather than letting either side's number win by rebase order.
…ps, lockfile hold (#59) * chore(renovate): tune for "Automated PRs" - security bypass, burst caps, lockfile hold The Renovate GitHub App went live with "Automated PRs", "Require config file" and "Create onboarding PRs", so the config now has to hold up unattended rather than describe an intent. - `timezone: America/Los_Angeles`. The schedule was UTC, so "before 6am on monday" actually fired around midnight Sunday local. - `prConcurrentLimit` / `prHourlyLimit`. The first run after enabling sees every pending update at once, and each PR now costs a ~4 minute CI run. - `vulnerabilityAlerts` bypasses the weekly window - a CVE fix could otherwise sit ~10 days (up to 3 for the age floor, then up to 7 for Monday). Its `minimumReleaseAge` is **25 hours, not 0**: pnpm 11 refuses anything younger than 24h without writing a `minimumReleaseAgeExclude:` bypass into pnpm-workspace.yaml, so a same-day security PR would reintroduce exactly the hazard this config exists to prevent. - `osvVulnerabilityAlerts` - GitHub's advisories miss ecosystem-only reports. - `lockFileMaintenance` pinned off. It is automated `pnpm up` for the lockfile, and transitive re-resolution is what triggered the `TS2321: Excessive stack depth ... UserConfig` pathology in vite.config.ts while installing the same target versions directly did not. Off by default; pinned so it cannot get switched on as tidying. - `dependencyDashboardApproval` deliberately NOT set - it would re-impose scan-only behaviour at the config layer and defeat the app's own setting. CLAUDE.md's "Renovate is inert until the GitHub App is enabled" is now false and is corrected, along with the two settings whose reasoning is not guessable from the outside. Validated with `renovate-config-validator` (exit 0). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F3a2HLJ4beKARi7SPmaM54 * style: apply vp check --fix to the renovate config and CLAUDE.md Caught by the CI workflow from #56 on its first real use - `vp check` (no --fix) failed in 27s on two formatting issues. Worth noting the gate earned its keep immediately on a comment-only change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F3a2HLJ4beKARi7SPmaM54 --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes #54.
git log --all -- .githubwas empty, so this is the first.github/directory in the repository's history. Two files..github/workflows/verify.ymlRuns
pnpm run verifyverbatim - not a re-listing of its three phases as separate steps.verifyis the single definition of "this repo is consistent"; splitting it in the workflow would create a second definition free to drift frompackage.json. It isverify, not thecheckscript, so CI never rewrites files.pull_request,pushtomain, plusworkflow_dispatch. Concurrency-cancels superseded PR pushes.permissions: contents: readand nothing else. The job needs no secrets at all - Cloudflare Pages does not build this repo, so CI here is a check, not a deploy path.version:input onpnpm/action-setup. v6+ readsdevEngines.packageManager, so the pnpm pin stays in one place instead of being duplicated into YAML. It runs beforesetup-nodebecausecache: pnpmresolves the store path by invoking pnpm.preview:testneeds no Docker. The worker tests are pool-workers (workerdinstalls from npm) and the container tests arenode --testagainstrender.mjs. The whole gate runs on a plain runner - see the run linked below.Deliberately not included:
pnpm refs:sync(needs a Factorio binary and~/GitHub/factorio-data;verify's no-Factorio property is what makes this workflow possible at all), any deploy step, andpnpm vp build- the build reads git history for the stamp and wants a checkout policy of its own. The build gap is real and left as a follow-up rather than silently dropped..node-versionis now machinerysetup-nodereads it vianode-version-file, which gives the file its first consumer and flips it from documentation to machinery. CLAUDE.md said the opposite and is corrected here.engines.nodestays a permissive floor and is explicitly not what CI runs..github/renovate.json5Renovate rather than Dependabot for the reason #54 gives: the decisions here are holds, and
ignoreentries cannot express them. Encoded:typescriptdisabled outrightpako: 14-day age, note pointing atdeflate.tslegacyHash: trueis a pako extension with no zlib-API contract, from a library that flipped that default inside one majorwrangler+@cloudflare/vitest-pool-workersgroupedbrace-expansionoverride disabledpnpm auditstaying red is not a reason to take 5.xmatchDepTypes: ["engines"]disabledengines.nodeis a permissive floor, not a version to keep currenthelpers:pinGitHubActionDigestsThree rules carry
prBodyNotes, so the reasoning arrives attached to the proposal instead of waiting in CLAUDE.md for someone to remember it.The pnpm interaction, called out explicitly
This workspace's release-age guard is a pnpm 11 default, not a line in
pnpm-workspace.yaml. When pnpm is asked for something fresher than that cooldown it does not refuse - it writes aminimumReleaseAgeExclude:bypass intopnpm-workspace.yaml, which is exactly howvue-tsc@3.3.8once waived the guard silently. A dependency bot is a machine for manufacturing that situation.So
minimumReleaseAge: "3 days"is declared in the Renovate config, above pnpm's default, so Renovate can never open a PR for a release pnpm would want a bypass for. Declaring it also means a future pnpm changing its own default cannot move this repo's floor silently. IfminimumReleaseAgeExclude:ever shows up in a Renovate PR diff, that PR is wrong.Renovate is inert until the GitHub App is enabled on the repo (https://github.com/apps/renovate) - a one-click repo-owner step I can't do. The config was validated with
renovate-config-validator(INFO: Config validated successfully).Verification
pnpm run verifygreen locally before any of this: 142 test files / 1249 tests, 91s on a loaded machine.Not done here, on purpose
Branch protection on
main. #54 raises it and it is the right follow-up, but it is a repository setting, not a file, and "a required check that can be bypassed is most of the cost for none of the benefit" is a decision for the repo owner. Worth doing once this check has a track record.Interaction with the concurrent PRs
verify) - adds a phase, so CI's runtime grows with it. Nothing in this workflow needs to change: it invokespnpm run verify, so whatever chore(types): close the .vue type-check gap - adopt vue-tsc in verify #52 makesverifymean is what CI runs. That is the whole point of not re-listing the phases here.🤖 Generated with Claude Code
https://claude.ai/code/session_01F3a2HLJ4beKARi7SPmaM54