Two failures landed this morning:
- test_gitleaks_ignore.py's historical-fingerprint resolution (added in
d1fa7f38c) needs branch- and tag-reachable history, but the
scripts-and-build job used a depth-1 checkout, so all 37 fingerprints
failed to resolve in CI. Fetch full history like the gitleaks and
changes jobs already do.
- d1f687c0e routed frontend-modern/package.json and package-lock.json to
the new frontend-dependency-security path policy without updating the
manifest expectations pinned in test-hot-dev-bg.sh. Pin the effective
first-match policy per manifest instead of bare membership so the
routing stays asserted and shadowed entries cannot fake coverage.
Contract-Neutral: CI-only fix: full-history checkout for gitleaks fingerprint resolution test and re-pin hot-dev manifest policy expectations to the registry routing d1f687c0e already established; no contract delta
The public repository surface refresh (40d29ca44) hard-wrapped the
Settings → Infrastructure → Install on a host phrase mid-line and
dropped the v5-to-v6 agent upgrades mention from README.md, breaking
RA10's documentation-currentness-tests proof on main.
- documentation_currentness_test.py: match prose fragments against
whitespace-normalized content, the way markdown renders it, so an
editorial rewrap can no longer fail the guard. Structural fragments
(table rows, gate headings) and all assertNotIn checks still match
raw source.
- README.md: restore the v5-to-v6 agent upgrades mention the refresh
dropped; the invariant is still live while v5 installs migrate.
Traefik took the whole operator .env via env_file, so the internet-facing
container's environment carried CP_ADMIN_KEY and the entitlement signing
private key, neither of which it needs. It now receives only ACME/DNS
material: the CF token by explicit passthrough plus an optional
dns-credentials.env that setup.sh creates 0600.
The same wiring removes the silent Cloudflare-only narrowing: the resolver
provider is now ACME_DNS_PROVIDER (default cloudflare) via Traefik's env
override, the CF token is required only for the default provider, and any
other Traefik dnsChallenge provider supplies its credential variables through
dns-credentials.env. Pinned by
TestProviderMSPTraefikEnvIsMinimalAndDNSProviderOverridable; contract delta
in deployment-installability.
The docs link drift guard (test_repo_docs_link_drift.py) fails CI because
render_release_body.py links docs/CODE_SIGNING_POLICY.md at blob/main.
Release bodies must reference the docs snapshot of the release they
describe, so the installation section now links blob/v{version}, matching
how the rest of the body pins to the version being cut. The unit test pin
follows. Also skip .claude/ in the drift guard so local runs on a shared
tree are not polluted by stale agent worktrees under .claude/worktrees
that CI never checks out.
A `make format` sweep re-lays-out already-committed files without
changing a single token, so it cannot change what renders. The browser
verification guard still demanded a fresh receipt for it, which would
mean recording routes, viewports, states and interactions nobody
exercised in order to describe a diff with no visual delta. A guard that
can only be satisfied by an untrue receipt teaches people to write
untrue receipts.
Exempt a path only when its new content is byte-identical to prettier's
output for its committed content. That is provable, not a judgement
call: if the two match, the sole difference from HEAD is layout.
Fails closed everywhere else -- added or deleted files, unreadable
blobs, prettier missing, or any non-identical output all fall through
and still require the receipt. A reformat that also changes a value is
covered by a test and still blocks.
The pre-commit frontend formatter resolved prettier at
REPO_ROOT/frontend-modern/node_modules/.bin/prettier, where REPO_ROOT is
derived from the script's own path. In a linked worktree that is the
worktree root, which never runs npm install, so prettier_bin() returned
None and the formatter silently returned 0. Every frontend commit made
from a Claude or Codex agent worktree skipped formatting entirely, and
the drift accumulated in already-committed files until someone ran
`make format` and picked up 14 files of unrelated churn.
The skip path's comment claimed "CI's prettier check still catches drift
that slips through here." No such check existed -- nothing under
.github/workflows referenced prettier -- so there was no backstop at all.
- Fall back to the primary worktree's node_modules, resolved via
`git rev-parse --git-common-dir`, and say so in the hook output so a
version mismatch between the two checkouts stays visible.
- Add the whole-tree "Check frontend formatting" step to the frontend CI
job that the comment already promised. Staged-only formatting cannot
see drift in untouched files; this can.
- Pin prettier exactly. "^3.3.0" let the tracked package-lock.json land
on 3.9.5 while the gitignored pnpm-lock.yaml resolved 3.9.6, so CI and
the dev machine were free to run different formatters.
- Resolve the test suite's prettier the same way, so its two real
coverage tests stop silently skipping in worktrees.
Contract-Neutral: devDependency prettier version pin plus pre-commit/CI formatter tooling; no runtime, API, or deployment-surface delta
The installer compares the agent binary it downloaded against the server that
served it, stripping a leading "v" so "v6.0.4" and "6.0.4" match. It did not
strip semver build metadata, so a server built from a working tree reporting
"6.2.0-rc.8+git.46.g98a638e00.dirty" never matched the "v6.2.0-rc.8" agent it
had just served, and the mismatch warning fired on every correct development
install.
This is the warning's whole job, so a false positive is expensive. It is the
only client-side signal that a stale agent was downloaded, and because it
always fired it read as background noise. That is exactly how a genuinely
stale v6.0.5 agent was installed on a live host earlier today: the warning
was there, above the install output, and looked like the one that always
appears.
Strip build metadata from both sides before comparing, keeping the prerelease
suffix because 6.2.0-rc.8 and 6.2.0 are genuinely different releases. This is
the same release-identity reduction the server applies when deciding whether a
local agent artifact is fresh enough to serve; the contracts now state that
one definition governs both ends rather than leaving each side to invent its
own.
Guarded by a test that pins both normalisation steps and exercises the
comparison across the cases that matter: the dev-server shape that used to
warn wrongly, the stale-download shape that must still warn, and a prerelease
against its release. Verified to fail when either strip is removed.
Completes the wrapper-teardown rule across the remaining branches. The QNAP
install and both uninstall paths still used a bare pkill -f
"start-pulse-agent.sh" and still stopped the agent before its wrapper.
The bare pattern is narrower than it looks and wider than it should be. It
does NOT match a co-installed agent's supervisor, so the sibling case was
already safe; what it does match is anything where the unescaped dot stands in
for another character and the unbounded tail keeps going, including a .bak
copy of the wrapper and an editor session holding it open. Escaping the dot
and bounding the far end removes both without narrowing the intended match.
Ordering is the more consequential half. A wrapper is a watchdog, so stopping
the agent while its wrapper still loops only races the respawn. QNAP and the
uninstall paths now stop the supervisor first, which is what the contracts
already required of every branch that writes and launches a wrapper.
Uninstall keeps a deliberately broader match than install, with no leading
path separator, so it still reaches a wrapper invoked by a relative path or
stranded at a superseded location. Both contracts now carry that distinction
and the teardown ordering rule, which each had stated only for install.
Guarded by two tests that pin every wrapper kill in the file rather than one
branch: one requires the escaped dot and the bounded tail everywhere, the
other walks each stop block and fails if an agent kill precedes its wrapper
kill. Both were confirmed to fail against the pre-fix QNAP block.
The Unraid install path killed the running agent but never the wrapper
supervising it, then appended a second wrapper at the end of the install. The
survivor and the newcomer both loop trying to own the same agent id, and
because the old wrapper is a watchdog it respawns the agent mid-install with
the previous binary and arguments. Observed on a live Unraid host: a
supervisor from a July install was still running beside the one the reinstall
had just started.
Nothing reports this as a failure. It presents later as an agent that
restarts on its own or reverts to superseded arguments.
Stop the wrapper first, then the agent: killing a supervised agent while its
wrapper still loops only races the respawn. The pattern matches the trailing
path segment so a wrapper left at an older storage location is caught too,
with the dot escaped and the far end bounded so a co-installed agent's
supervisor (start-pulse-agent-prod.sh) is not.
The QNAP branch already stopped its wrapper, which is what made the Unraid
omission visible; the contracts now require every wrapper-writing branch to
own the same teardown.
Guarded by two tests: one pins that the Unraid branch stops the wrapper and
does so before the agent, the other pins that the wrapper pattern spares a
sibling supervisor, with a premise check that the loose pattern really does
match so neither assertion can pass vacuously.
pkill -f matches the whole command line and "^" only anchors the start, so
"^/usr/local/bin/pulse-agent" also matches "/usr/local/bin/pulse-agent-prod".
On a host running a second agent whose binary name shares the prefix, every
install, every upgrade, and every restart of the generated Unraid wrapper
silently killed the other agent too. Confirmed on a live dual-agent Unraid
box: the old pattern matched both the dev agent and the production dogfood
agent, the bounded pattern matches only its own.
The wrapper is the worst of the three because restarting through it is the
documented runbook step, so the collateral kill repeats every time an
operator follows it.
Bound the far end of each binary-anchored pattern with ([[:space:]]|$), and
swap the bare pkill -9 -f "pulse-agent" for -x on the exact process name,
which keeps that site's deliberate path-agnostic intent while excluding the
sibling. The pkill -x sites were already safe and are unchanged.
Guarded by two tests: one pins that no binary-anchored pkill in the installer
is left unbounded, the other exercises POSIX ERE semantics against the two
command lines a dual-agent host presents, including a premise check that the
unbounded pattern really does match the sibling so the assertion cannot pass
vacuously.