build: ratchet the branch coverage threshold
A full run on this branch reports 100% statements (49249/49249), 100%
functions (7333/7333) and 99.16% branches (25479/25696), up from 98.72%
(25434/25765) before the manager coverage work. The threshold moves to
99.14, keeping the same rounding-down margin as before.
Statements, functions and lines are already pinned at 100 and stay
there.
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* test: close remaining line coverage gaps
A full coverage run reported three uncovered lines:
- `rez` versioning: `rez2pep440()` was never called with a plain version,
because `getNewValue()` short-circuits via `matchVersionPrecision()`
whenever the new version is valid for npm. A four-component rez version
is not, so it reaches the pep440 path.
- `nuget` v3: only the inner nuspec 404 was covered, never a 404 escaping
from the `.nupkg` download into the outer catch.
- `github` platform: the statement after `await` in the GitHub App branch
is a v8/source-map false negative - it runs, but is counted zero. The
branch used to carry the `v8 ignore` comment, which moved away with the
`ghHostname` block in #44587.
Co-Authored-By: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* build: ratchet coverage thresholds
A full run now reports 100% statements (49776/49776), 100% lines
(49256/49256), 100% functions and 98.72% branches (25756/26089), so the
thresholds move up accordingly. Branches keep the same rounding-down
margin as before.
Co-Authored-By: Claude Opus 5 <michael.kriese+claude-code@mend.io>
---------
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
Sets each floor to the current measured coverage, rounded down to two
decimals:
statements 99.993944% -> 99.99
branches 98.703754% -> 98.70
functions 100% -> 100
lines 99.993880% -> 99.99
This leaves very little slack by design: one uncovered branch is
0.00386%, so the branches floor tolerates none.
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
lib/config/decrypt/legacy.ts was removed in #39111, so
getCoverageIgnorePatterns() only ever excluded a path that no longer
exists, and the TEST_LEGACY_DECRYPTION workflow env var that gated it
was read by nothing else.
Coverage is unaffected: a full run reports the same numbers before and
after, so the thresholds stay as they are.
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
ci: tighten statements and functions thresholds
Both metrics have headroom against the current measured coverage
(statements 99.9899%, functions 99.9864%), so the floors can move up
without depending on any other pull request landing first.
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
We dropped the "bot" from our terminology long ago, but it looks like
we've got some outdated docs + code that reference it.
We should remove this to keep things consistent.
A `fix`, as we're changing the commit author's name, and log lines.
Co-authored-by: Claude Sonnet 5 <jamie.tanna+claude-code@mend.io>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Due to a long-standing issue with `semantic-release` not checking if
`#...` reference is for a Discussion or Issue, if a contributor says
`Closes #<discussion>`, then our release process fails.
We can extend our existing checks to try and catch this in the PR
itself, as users may not read the big warning that says "don't add a
Discussion here".
Co-authored-by: Claude Sonnet 5 <jamie.tanna+claude-code@mend.io>
As per request in #45462, we can perform a prefactor to ensure that our
scripts for CI-related functionality are part of `tools`, so they're
linted appropriately.
Co-authored-by: Claude Sonnet 5 <jamie.tanna+claude-code@mend.io>
When running the Release job, we restore our `node_modules` cache via
`setup-node`. This leads to `semantic-release` executing - with
`contents: write`, `packages: write`, `id-token: write` and Docker Hub
credentials - the release process.
If we were to have a poisoned cache, this could lead to backdoored npm
packages and Docker images.
Although not currently exploitable, we should further harden this by:
- never using the cache for `release` jobs
- requiring a full `pnpm install --frozen-lockfile` on release jobs
- increasing the build time slightly on release jobs
This ensures that we don't leave ourselves vulnerable to cache poisoning
in the future.
It appears that zizmor's `cache-poisoning` audit covers this pattern,
but doesn't recognise a branch-push `semantic-release` job as a release
workflow, so won't flag regressions here.
Co-authored-by: Claude Fable 5 <jamie.tanna+claude-code@mend.io>
As per the previous commit, we currently don't lint our composite
actions, but should do.
Co-authored-by: Claude Fable 5 <jamie.tanna+claude-code@mend.io>
To handle an ActionLint finding against the repository.
This isn't caught because `lint-actions` is a sparse checkout, which
doesn't include the in-repo Actions themselves, which is fixed in a
follow-up commit.
Co-authored-by: Claude Fable 5 <jamie.tanna+claude-code@mend.io>
The Unified Agent scan passes `secrets.WS_ORG_TOKEN` as a `-apiKey`
CLI argument. Actions masks it in logs, but command-line arguments
are visible in the runner's process table to anything else running on
the machine.
As the Unified Agent reads any configuration parameter from a
`WS_`-prefixed environment variable, we can supply `WS_APIKEY` via
`env:` and drop the argument.
Co-authored-by: Claude Fable 5 <jamie.tanna+claude-code@mend.io>
To avoid potentially expanding a `discussion.created_at` event, we can
make sure we don't interpolate it, by injecting it in via `env`.
Co-authored-by: Claude Fable 5 <jamie.tanna+claude-code@mend.io>
To reduce risk of template injection, we can pass secrets and inputs as
an `env` input, which avoids shell expansion and hardens our usage.
Co-authored-by: Claude Fable 5 <jamie.tanna+claude-code@mend.io>
To make sure that we cover these steps explicitly, we can add a check
for them in `test-success`.
Co-authored-by: Claude Fable 5 <jamie.tanna+claude-code@mend.io>
As we're missing the `${{ }}`, we're passing in the literal string
`github.event_name ...` to `fail_ci_if_error` which may not evaluate as
expected.
Co-authored-by: Claude Fable 5 <jamie.tanna+claude-code@mend.io>
So we can run them both in parallel, speeding up the overall run time.
This may lead to a case where we run `build-docs` in the case a linting
failure is hit, but it also allows running them both in parallel which
could save ~1m of wasted waiting time.
Co-authored-by: Claude Fable 5 <jamie.tanna+claude-code@mend.io>
Like the vitest cache, the tsbuildinfo cache in `lint-other` can only
restore entries whose key contains the current
`hashFiles('pnpm-lock.yaml')` — its existing `restore-keys` prefix
still embeds the hash — so every lockfile change makes `type-check`
run a full, rather than incremental, `tsc`.
We can add a bare `tsbuildinfo-` fallback after the existing
same-hash prefix, restoring the newest cache from before the lockfile
bump. tsc records the version of every input file in the buildinfo
and recompiles whatever changed, so a stale restore is safe.
We intentionally leave the similarly-shaped prettier cache without a
cross-hash fallback: prettier can't detect plugin changes, so the
lockfile hash in its key is the only thing invalidating its cache
when a plugin updates — a cross-hash restore could wrongly skip
reformatting files.
Co-authored-by: Claude Fable 5 <jamie.tanna+claude-code@mend.io>
The vitest cache key in `test` embeds `hashFiles('pnpm-lock.yaml')`
and has no `restore-keys`, so every lockfile change starts each test
shard fully cold, re-transforming the whole codebase. As Renovate
updates its own lockfile near-daily, this is the common case rather
than the exception.
Because `restore-keys` performs prefix matching, it can only wildcard
the end of a key, so we first reorder the key to put
`matrix.cache-key` before the lockfile hash. The fallback then means
"same shard, any lockfile". vitest validates restored entries itself
(transforms are keyed by content hash), so a stale restore degrades
to a rebuild, never a wrong result.
Due to the reorder, the first run after this change won't match any
existing cache entry, so will start cold once.
Co-authored-by: Claude Fable 5 <jamie.tanna+claude-code@mend.io>
As `setup-node` currently uses a hash based on
`hashFiles('pnpm-lock.yaml', 'package.json')`, but we weren't doing the
same here, we would always have a cache miss, and we would need to run
the macOS prefetch, regardless of whether the cache was warm or cold.
Co-authored-by: Claude Fable 5 <jamie.tanna+claude-code@mend.io>
As `lint-actions` doesn't currently need any access to `pnpm` artifacts,
we can run this check earlier than needing to have `setup-build` (and a
cache hit or miss).
Co-authored-by: Claude Fable 5 <jamie.tanna+claude-code@mend.io>
As a way to get better visibility for - predominantly - transient issues
GitHub are having, via [0] we can auto-label PRs, so they can then be
picked up by a maintainer.
We can use the `-slim` runners, as this doesn't need much compute, and
it reduces ours and GitHub's costs.
[0]: https://www.jvt.me/posts/2026/08/11/github-merge-queue-prs/
Co-authored-by: Sonnet 5 <claude-code@jamietanna.co.uk>