Commit Graph

751 Commits

Author SHA1 Message Date
pulse-triage[bot] 8f3f67db92 Refresh governed Node and Alpine container digests together
Recompose the two Dependabot digest-only refreshes on current main, update the stale Node proof pin, and bind all six Alpine consumers to the same selected runtime image. Keep governed tag lines, release keys and exact-candidate consumption unchanged; source contracts do not claim a hosted image build or installed recovery.

Change-source: pulse-maintainer
2026-10-27 04:46:31 +00:00
pulse-triage[bot] f56b7e2b38 Preserve operator boot jobs during Unraid agent upgrades
Replace only exact top-level generated launches, retaining ambiguous boot content and recovery on failure. Stage and validate replacement before rename, preserve unrelated jobs and existing modes without assuming FAT supports chmod, and stop installation before claiming unconfirmed boot persistence. Cover compound commands, launch-like data, write faults, FAT permission limits and caller failure in the normal installer inventory.

Change-source: pulse-maintainer
2026-10-27 02:25:03 +00:00
pulse-triage[bot] 89969b6418 Preserve unrelated QNAP startup jobs during agent upgrades
Replace substring and unterminated-range boot deletion with exact owned-entry admission and staged same-filesystem replacement. Preserve ambiguous content and failed staging for recovery, and check append plus flash unmount before claiming persistence. Exercise production writer/caller failures and keep the existing NAS removal fixtures composed. Native QNAP acceptance remains separate.

Change-source: pulse-maintainer
2026-10-27 01:15:55 +00:00
pulse-triage[bot] 81575dd1f9 Fix cleanup fixture confinement through directory aliases
Both macOS native jobs on 2d226d4f fail the key-read-failure check because its staging guard compares the logical temp path with the writer's physical target. Canonicalise the private fixture root before building the guard, without widening the allowed cleanup path, and exercise both direct and aliased roots. All key-preservation, nonzero-result, no-user-removal and staging-cleanup assertions remain.

Contract-Neutral: Test-fixture path normalisation only; no runtime behaviour or subsystem contract change.
Change-source: pulse-maintainer
2026-10-26 22:16:29 +00:00
pulse-triage[bot] 44b0500abc Preserve recovery keys when local legacy cleanup cannot complete
Separate staging-file admission from grep no-match in both shipped local cleanup paths. Resolve shared targets, require intact metadata and return incomplete results without replacing unrelated keys or caller resources. Add actual filesystem failure and caller-path controls.

Change-source: pulse-maintainer
2026-10-26 20:52:58 +00:00
pulse-triage[bot] 07e630d34e Match the final legacy-cleanup success message exactly
The remote failure proof correctly reached main's nonzero incomplete result. Its broad success substring also matched the truthful local-file-cleanup warning, so narrow the assertion without changing the runtime or cleanup contract.

Contract-Neutral: Test-only assertion precision; no runtime or contract change.
Change-source: pulse-maintainer
2026-10-26 20:00:25 +00:00
pulse-triage[bot] 5bc140aa7e Preserve shared SSH key targets during legacy remote cleanup
Resolve the fixed remote authorized_keys symlink before staging an atomic target replacement. Fail closed on unresolved, non-file, filtering, metadata and replacement errors, and leave already-clean shared keys untouched.

Execute the actual transmitted POSIX payload against closed regular-file and shared-link fixtures, with unrelated recovery-key and failure-preservation checks. Exercise its failure through validated cluster discovery into main without live SSH or customer operations.

Change-source: pulse-maintainer
2026-10-26 19:53:01 +00:00
pulse-triage[bot] 7508fc5edf Require complete inventory for legacy credential cleanup
Keep Proxmox API JSON on the parser's stdin and validate every identity before key cleanup. Failed inventory and key replacement must not become empty successful cleanup; opted-in API removal propagates failures and preserves the user when token removal is incomplete.

Exercise cluster discovery, local key preservation and API removal with closed fixtures, alongside the existing SSH trust and stopped-container controls.

Change-source: pulse-maintainer
2026-10-26 19:29:50 +00:00
pulse-triage[bot] aef2f5be9c Keep non-root agent preflight outside saved installer trust
Fix the manual-recovery failure reported in Pulse #3331. Compare directory identity without entering the protected saved lifecycle directory, so a freshly authenticated temporary installer can finish preflight as the invoking user. Keep the saved installer's ownership, mode and checksum enforcement, including parent-directory aliases.

Contract-Neutral: Restores the existing non-root preflight and saved-installer integrity contracts; no privilege, credential, update-authorisation or rendered-interface contract changes.
Change-source: pulse-maintainer
2026-10-26 12:37:01 +00:00
pulse-triage[bot] 1aa9974ed2 Preserve existing Docker deployments during setup
The first-install generator replaced customised Compose files and overwrote its one recovery backup on every rerun. Refuse existing Compose paths before Docker or output writes, retain regular environment files, and prevent output clobbering. Report configuration preparation rather than an installation that was never run.

Contract-Neutral: Restore intended setup idempotency and saved-deployment preservation; no generated Compose schema, runtime policy, image default, upgrade API or lifecycle contract changes.
Change-source: pulse-maintainer
2026-10-26 09:44:50 +00:00
pulse-triage[bot] 665b18acad Stop container continuity reads at explicit access refusals
The scheduled scan at 41715cf2 retried a refused Docker Hub control-plane read four times, then attempted aliases through a second script. Stop explicit registry or attestation access refusals on the first response, retain the exact target and exit, and run aliases only after exact-container verification succeeds. Keep both registries, every digest and source-bound attestation mandatory; unrelated Helm evidence remains independent.

Contract-Neutral: Fail-closed refusal handling and diagnostic ordering only; public registry, digest, provenance and release activation requirements are unchanged.
Change-source: pulse-maintainer
2026-10-26 06:00:47 +00:00
pulse-triage[bot] aa4f3bbf8d Preserve updater assets when repository configuration fails
Return actual allocation, syntax, mode and rename failures before publishing a configured updater. Prepare the rewrite beside its input and leave the installed helper and units unchanged when configuration cannot complete. Exercise the real rewriter and its containing asset installer under the conditional callers that suppress errexit.

Contract-Neutral: Repairs the existing configured-helper and failure-preservation guarantees without changing update consent, source selection, service policy or deployment contracts.
Change-source: pulse-maintainer
2026-10-25 21:47:30 +00:00
pulse-triage[bot] c84e5143da Keep manual channel proof on the HTTPS transport envelope
Returned CI rejected the new curl protocol flags in the existing channel fixture. Admit only the exact HTTPS options and require both. Make the invalid-signature control prove it reached the real verifier instead of passing on a fixture error; retain all channel and no-download checks.

Change-source: pulse-maintainer
2026-10-25 16:56:36 +00:00
pulse-triage[bot] 89c61b233c Keep signed manual update bootstraps on HTTPS
The generated manual helper currently permits an HTTPS asset redirect to plaintext HTTP, unlike the unattended downloader. Restrict initial and redirect protocols for both signed inputs, without weakening deadlines, signature verification or cleanup. Exercise the actual helper with real curl and authenticated local TLS, including downgrade refusals and permitted HTTPS redirects; keep the native systemd fixture on the exact envelope.

Change-source: pulse-maintainer
2026-10-25 15:47:43 +00:00
pulse-triage[bot] 70a0c54e84 Replace manual update helpers without truncating running readers
The published v6.5.0 to v6.6.0 lifecycle run 40197749411 reported an unmatched quote after the installer rewrote /bin/update in place. Stage, validate and atomically replace the helper so an executing Bash reader keeps its original inode. Preserve the optional non-fatal setup guarantee on every preparation or replacement failure.

Change-source: pulse-maintainer
2026-10-25 13:42:11 +00:00
rcourtman e138bdf7d6 Point the MSP guide at v6.6.0 and self-serve buying
Every /msp evaluation link opens docs/MSP.md, which still installed the
v6.4.1 bundle and said paid licences come through an assisted upgrade
path. Plan in the provider portal has sold every MSP plan since v6.5.0,
so a provider following the site could never reach it.

The guide now installs the signed v6.6.0 bundle, explains buying,
renewing and lapsing from Plan as shipped (paid period plus 14 days,
then 7 days of grace), and adds an upgrade section. Walking it on a lab
VM found three more gaps, now covered: day-2 commands need sudo because
setup writes a root-only .env; client runtimes move only with
--rollout-tenants; and a v6.4.x control plane keeps stopping clients,
which blocks the upgrade gate until it is stopped and the current
runtimes are started. No plan exists above 40 clients, so the guide no
longer offers a custom licence for that.
2026-10-25 12:59:11 +00:00
rcourtman 5ad599fc98 Keep the system hostname when migrating to the safe profile
Safe-profile migration pinned the output of hostname -f as the agent's
reported name, so every migrated host switched to its FQDN while
root-profile hosts kept the short system hostname the agent reads. Pin
the system hostname instead. An explicit --hostname still wins, and the
server's authority check already accepts short and FQDN forms of the
bound name. Hosts migrated earlier keep their stored name.

Contract-Neutral: Safe-profile migration now pins the system hostname the agent already reports; no agent-lifecycle or deployment-installability clause names the hostname -f choice, and the new install_sh_test.go case proves the behaviour.
2026-10-25 09:43:39 +00:00
pulse-triage[bot] b6618c19a3 Preserve each configuration-backup safety control's deadline
The containing installer suite exhausted a single 30-second Python-class budget after eleven controls and killed the last. Use the existing serial discovery runner with all twelve required methods, retaining each control's 30-second bound and the existing inner shell bounds rather than treating cumulative class time as a safety deadline.

Change-source: pulse-maintainer
2026-10-25 06:49:45 +00:00
pulse-triage[bot] 5d748986b3 Keep native recovery proof aligned with bounded manual downloads
Admit only the manual helper's exact new curl envelope in the existing closed transport fixture. Require a signed emitted-helper round trip and reject altered deadlines, retries and TLS flags through the registered installer suite; do not widen the adapter or execute a native journey.

Change-source: pulse-maintainer
2026-10-25 06:38:18 +00:00
pulse-triage[bot] b239891b75 Bound signed manual-update bootstrap downloads
Keep both bootstrap requests within curl's connection and whole-request deadlines, without ambient retry settings. Arm cleanup before allocation so a second mktemp failure cannot strand the first input. Exercise the emitted helper's signed success, partial download failures, allocation failures and signature rejection without native or production execution.

Change-source: pulse-maintainer
2026-10-25 06:33:50 +00:00
pulse-triage[bot] 7b64d9b66d Keep real NAS process-inventory controls serial
The first exact-source parallel attempt failed the generated Unraid and QNAP watchdog control. Its real machine-wide process observation is not isolated by private fixture files. Preserve the entire NAS class's serial execution and make concurrency explicit only for the seven modelled-inventory classes. The regression now proves that the serial helper still preserves ordering, as well as bounded parallelism and complete failure/skip/cleanup evidence.

Contract-Neutral: Test-only fixture scheduling; real NAS observations, production manager deadlines, process ownership checks and required test coverage are unchanged.
Change-source: pulse-maintainer
2026-10-25 01:15:42 +00:00
pulse-triage[bot] d291080a8f Run isolated installer safety controls within Go's parallel budget
Current successful public CI puts the installer package on the backend critical path for 683-764 seconds. Each registered Python method already owns a separate process and private fixtures; run those methods through the existing Go parallel scheduler, starting their unchanged deadlines only after admission. Keep exhaustive discovery and exact non-skipped method verdicts, with connected parallel, serial, failure, skip and cleanup controls.

Contract-Neutral: Test-only scheduling of already isolated fixture processes; no installer, runtime, service deadline, source selection, required check or native acceptance contract changes.
Change-source: pulse-maintainer
2026-10-25 01:51:34 +01:00
courtmanr@gmail.com c4486a177a Keep a real change out of the Highlights example
The Highlights guidance used "Resource Overview shows alerts, recent
trends and longer history together" as its example. That is a real
v6.6.0 change, and a test draft of the v6.6.0 notes copied it almost
word for word, so a later release could repeat it as if it were new.

The generator and the release-notes template now describe the shape
instead, the page or feature and then what it now does, and say to
describe only changes in this release. The slogan counterexamples stay,
and the release-assets test pins the new sentence.
2026-10-24 20:43:52 +01:00
rcourtman 97f55638c9 Prove demo auto sign-in in the public browser smoke (#3204)
Since v6.5.0 demo mode signs a fresh visitor in with the published
demo credentials, so the login form never stays on screen for a first
visit. The browser smoke still waited two minutes for that form and
failed, which blocked every stable demo convergence once the Tailscale
step worked again. It went unnoticed because the demo update had not
got past Tailscale since v6.5.0 shipped.

The smoke now proves the visitor's real path, a fresh tab reaching the
connected workspace, when the server reports demo mode. A second
context sets the frontend's own pulse-demo-auto-login marker to keep
proving the login form and a manual demo sign-in. Against the live
demo the old script times out and the new one passes both phases. The
policy test pins the marker to localStorage.ts so a rename fails in
source checks rather than on the live demo.
2026-10-24 15:06:41 +01:00
rcourtman 572930fded Make release Highlights name concrete changes (#3203)
Make release Highlights name concrete changes

The generator only asked for "one to three plain-text outcomes", so it
produced slogans such as "See problems, recent trends and longer
history together" that tell a user nothing about what changed. These
lines are what the update banner shows before someone installs.

The generator prompt and the release-notes template now ask for the
release's biggest specific changes and where users find them, with an
example and named anti-examples, and the release-assets test pins that
wording. Their description of the post-update modal also follows the
new plain changelog, which no longer collapses upgrade notes or puts
Highlights above the changes.
2026-10-24 14:45:36 +01:00
rcourtman df79b7716a Prove demo auto sign-in in the public browser smoke
Since v6.5.0 demo mode signs a fresh visitor in with the published
demo credentials, so the login form never stays on screen for a first
visit. The browser smoke still waited two minutes for that form and
failed, which blocked every stable demo convergence once the Tailscale
step worked again. It went unnoticed because the demo update had not
got past Tailscale since v6.5.0 shipped.

The smoke now proves the visitor's real path, a fresh tab reaching the
connected workspace, when the server reports demo mode. A second
context sets the frontend's own pulse-demo-auto-login marker to keep
proving the login form and a manual demo sign-in. Against the live
demo the old script times out and the new one passes both phases. The
policy test pins the marker to localStorage.ts so a rename fails in
source checks rather than on the live demo.
2026-10-24 14:31:21 +01:00
courtmanr@gmail.com 842dbda6b8 Make release Highlights name concrete changes
The generator only asked for "one to three plain-text outcomes", so it
produced slogans such as "See problems, recent trends and longer
history together" that tell a user nothing about what changed. These
lines are what the update banner shows before someone installs.

The generator prompt and the release-notes template now ask for the
release's biggest specific changes and where users find them, with an
example and named anti-examples, and the release-assets test pins that
wording. Their description of the post-update modal also follows the
new plain changelog, which no longer collapses upgrade notes or puts
Highlights above the changes.
2026-10-24 14:06:27 +01:00
rcourtman 1ffa17a10c Merge remote-tracking branch 'origin/main' into fix/demo-tailnet-ci-deploy-client 2026-10-24 13:28:42 +01:00
pulse-triage[bot] 06475a9d37 Keep bounded hosted provisioning overhead in chart SLO verdicts (#3199)
Retained hosted samples include both an over-budget failure and an
under-budget pass with one provisioning agent occupying about one
logical CPU. Attribute only that bounded Actions overhead; retain raw
CPU and absolute 90 ms latency, and keep other or unattributed CPU,
guest, steal and swap invalidating. Exercise the real CLI and workflow
shells with adverse controls; no timed workload or release acceptance is
claimed.

---
Fleet candidate `20261024T104012Z-delivery-trust` from the
`delivery-trust` lane. Reviewed tip
`fbe410e244f3d44b44de258c6f3db68dabd257da` on `main`.

[How Pulse handles
triage](https://github.com/rcourtman/Pulse/blob/main/docs/AI_TRANSPARENCY.md)
2026-10-24 12:27:42 +00:00
rcourtman bc39aa5908 Move demo deploys to the tag:ci-deploy Tailscale client
The demo update and recovery workflows still joined the tailnet with
the July 2026 tag:infra OAuth client. That client was revoked on 30
September, when the private repositories moved to narrow clients, and
the public repository was missed. Since then every demo convergence
step has failed with an OAuth 401, so stable releases cannot converge
and the hourly reconcile keeps retrying v6.5.0.

Both workflows now use the TS_CI_DEPLOY_OAUTH_* binding that the
browser update qualification already expects, restricted to
tag:ci-deploy. The tailnet policy already grants that tag SSH to
pulse-relay, the demo host, so no policy change is needed. One narrow
client now serves all three workflows, and the broad tag:infra
identity is gone from this repository.
2026-10-24 13:25:23 +01:00
pulse-triage[bot] fbe410e244 Compose chart SLO repair without dropping current administrative guarantees
Resolve adjacent contract append conflicts by keeping bounded hosted-agent classification in its chart section and preserving all newer SSO, RBAC and uninstall guarantees. Runtime, workflow and synthetic chart controls merge without conflict; no latency budget or customer journey changes.

Change-source: pulse-maintainer
2026-10-24 11:42:23 +01:00
rcourtman c7ac6ac86a Pin the Pro promotion terminal annotation where it now lives
The promotion caller now owns the terminal failure annotation, so a
re-dispatched refusal leaves none behind. The release-asset contract
test still pinned the old annotation inside wait_for_workflow. It now
pins the caller's annotation and the source-check refusal gate.
2026-10-24 11:42:17 +01:00
pulse-triage[bot] b25c22c00f Keep bounded hosted provisioning overhead in chart SLO verdicts
Retained hosted samples include both an over-budget failure and an under-budget pass with one provisioning agent occupying about one logical CPU. Attribute only that bounded Actions overhead; retain raw CPU and absolute 90 ms latency, and keep other or unattributed CPU, guest, steal and swap invalidating. Exercise the real CLI and workflow shells with adverse controls; no timed workload or release acceptance is claimed.

Change-source: pulse-maintainer
2026-10-24 10:48:40 +01:00
rcourtman 27f7e11e9b Refuse Unraid uninstall while the rc.local go shell is live
Uninstall treats a live Unraid go shell as an unknown boot owner and
refuses, because that shell may still hold later boot lines in memory.
Signalling the watchdog would release it to run them. The check only
looked for /boot/config/go in a process whose name is a shell. Unraid's
rc.local never runs that path: it writes a fromdos copy to /var/tmp/go
(go.safemode in safe mode) and executes that copy directly. The kernel
then names the process "go", with argv "/bin/bash /var/tmp/go", so the
check never matched it.

Hosts installed before the detached go entry still have this shell
blocked on the old foreground wrapper line. On those hosts uninstall
classified the shell as unrelated, stopped the watchdog, and let go run
the rest of its in-memory script. That is the case the live-shell
refusal was added to prevent.

The role check now covers a go or go.safemode process, or a shell, that
has /boot/config/go, /var/tmp/go or a .safemode form as a whole
argument. The old substring match is gone, so near-miss paths such as
/var/tmp/gopher.sh no longer block removal, and a Go toolchain process
named go still reads as unrelated. The NAS teardown control covers the
rc.local spellings, and a new role-table test pins Unraid and QNAP.
Both fail against the previous install.sh.

A go file without a #! line is still not recognised. Bash runs it in a
forked copy of rc.local, and the contracts now say so.
2026-10-24 10:26:45 +01:00
pulse-triage[bot] f19424f51a Complete credited chart evidence reporting on current main (#3169)
Isolate the retained PR3095 completion (7f4ec192 and 93d87118) without
importing unrelated history. Keep the exact proposed Python classifier
and maintained chart shells, real CLI/workflow controls and dependent
contracts. Inconclusive capacity never establishes the 90 ms budget;
child, cleanup and malformed-proof failures remain errors.

Source-PR: https://github.com/rcourtman/Pulse/pull/3095
Source-Commit: b31fabd778
Co-authored-by: rcourtman <8825017+rcourtman@users.noreply.github.com>

---
Fleet candidate `20261024T010002Z-delivery-trust` from the
`delivery-trust` lane. Reviewed tip
`7e7cea87a6bce2e7a2e00e888a676c571e141d92` on `main`.

[How Pulse handles
triage](https://github.com/rcourtman/Pulse/blob/main/docs/AI_TRANSPARENCY.md)
2026-10-24 02:26:12 +00:00
rcourtman 4f3e170612 Detach the Unraid boot entry so later go lines run
The Unraid installer appended `bash <wrapper>` to /boot/config/go. The
wrapper is an endless watchdog loop, so that line never returned, and
rc.local never reached anything the user had placed after the Pulse
block. On a maintainer host a reboot skipped swap, a memory logger and a
hosts entry the agent needed to reach its server, and Pulse showed the
host as down for two days while it was healthy.

The entry now backgrounds the wrapper with its output redirected, so go
returns and holds no boot output pipe open. Install-time cleanup used to
delete every go line containing "pulse-agent", including log directories,
hosts entries and a co-installed agent. It now removes only the
installer's marker and command lines that name a start-pulse-agent.sh
wrapper or this runtime binary outside a comment, so upgrades from the
foreground or hand-edited entry still leave exactly one launch. Kept
lines lose a trailing CR, as Unraid's fromdos does before running go, and
a removal that would leave go unparseable is refused with a warning.
Uninstall admits the detached entry as well as the earlier foreground
form.

The new tests execute the go file the installer writes and require it to
return, release its output pipe and run the next line, with a planted
foreground entry as the control, and pin the narrowed cleanup against
the removal filter. The NAS teardown controls now render their Unraid
entry through the same emitter.
2026-10-24 00:34:31 +01:00
pulse-triage[bot] 8478eac3ef Complete credited chart evidence reporting on current main
Isolate the retained PR3095 completion (7f4ec192 and 93d87118) without importing unrelated history. Keep the exact proposed Python classifier and maintained chart shells, real CLI/workflow controls and dependent contracts. Inconclusive capacity never establishes the 90 ms budget; child, cleanup and malformed-proof failures remain errors.

Source-PR: https://github.com/rcourtman/Pulse/pull/3095
Source-Commit: b31fabd778
Co-authored-by: rcourtman <8825017+rcourtman@users.noreply.github.com>
Change-source: pulse-maintainer
2026-10-24 00:26:11 +01:00
pulse-triage[bot] b79496219f Preserve prior installer snapshots on retry
Reconcile the retained snapshot publication repair with current main without importing unrelated containing history. Unique private staging, no-clobber publication and failure-before-mutation protect earlier recovery copies; retain zero-retention and hardened-unit fallback semantics.

Retained-source: 854ea3c81a94ffe88145d663d25d5040d4637a47, 91bd7393fc4413b6629ad851f90c2c7f6c2c0991
Change-source: pulse-maintainer
2026-10-23 22:39:08 +01:00
rcourtman c909b4db79 Bound Unix teardown fixture controls independently
The macOS Intel native job killed the DSM Upstart suite at its single
one-minute alarm after sixteen controls had passed (main 39883120153,
PR 39952586718). Passing runs take 41s on Intel, 23s on ARM and 14s on
Linux, so the budget measured runner speed, not a stuck teardown. The
Legacy suite sits at 35s against the same alarm.

Run each discovered systemd, OpenRC, legacy and Upstart control through
the shared helper under its existing bound, as the NAS and TrueNAS suites
already do. Required controls stay mandatory, and skipped, absent or
failed controls still fail. Installer deadlines are unchanged.
2026-10-23 15:45:44 +01:00
rcourtman 02e53bc777 build: move to Go 1.26.10 and Vite 6.4.5 for October advisories
Go 1.26.10 (20 October) distrusts the compromised eMudhra intermediate
CAs in crypto/x509 (CVE-2026-111943). Binaries built with 1.26.9 still
accept chains through them, so every verified outbound TLS connection
Pulse makes (licence, updates, webhooks, AI providers) stays open to
interception by a holder of those keys. govulncheck does not flag a
trust-store change as a called symbol, so the scheduled scan stayed
green while the gap existed.

Advance the governed toolchain floor and every pin together: go.mod,
the toolchain installer and release-script guards, the source-built
container stages (manifest-list digests for golang:1.26.10-alpine and
the Debian dev container image), the root-pair diagnostic, and their
tests.

Vite 6.4.3 bundles a launch-editor with three advisories
(GHSA-587h-7wmj-h5pm, GHSA-r825-w8mm-5pxw, GHSA-698v-4r3x-3vcv) that
failed the daily advisory watch. 6.4.4 bundles the fixed launch-editor
and 6.4.5 adds the dev-server server.fs query check. Both reach only
the local dev server, not the built product. The lockfile changes only
the vite entry, and dependencySecurity.test.ts now pins the floor.
2026-10-21 15:19:02 +01:00
rcourtman 266144de1f Share native installer execution controls with script smoke tests
Script smoke still asserted the removed grouped runner while native
verification used a separate suite. Share the canonical execution controls
so both entrypoints reject lost checks, false passes and changed deadlines.
2026-10-18 17:55:06 +01:00
rcourtman 79a35fbe2e Give every native installer check its own process budget
Compile and discover one exact test binary per native workflow phase, then
execute every discovered root check independently. This removes inventory
growth from another check's ten-minute Go alarm while preserving fixture,
installer and overall CI job deadlines.

Retain exhaustive phase coverage and reject missing completion, failed checks,
unexpected exit zero and timeouts using real compiled Go execution controls.
Register the runner with deployment installability and retain the failed Intel
and advisory benchmark evidence in the existing reliability record.
2026-10-18 17:17:52 +01:00
rcourtman 0b3261e1aa Bound native fixture controls independently
The NAS suite exhausts its aggregate alarm after fifteen successful
controls on macOS Intel. Run each discovered NAS and updater control
under its existing bound, retaining the native phase's ten-minute alarm
and all installer operation deadlines.

Keep required controls mandatory and reject skipped, absent or failed
execution. Exercise discovery and false-pass rejection with real Python
controls, including a newly added method.
2026-10-18 15:40:55 +01:00
rcourtman 2fc54a5a1f Merge pending maintenance repairs with current main
Preserve selected memory freshness and the VM/LXC interpretation.
Retain both native tool contracts and current API concurrency controls.
Qualify this source separately from release and installed recovery.

Contract-Neutral: Integrate already-governed parents without a new contract change. Incoming verification files already match main. Fresh integration tests and source-bound browser proof cover the combined memory and installer surfaces.
2026-10-18 15:11:28 +01:00
courtmanr@gmail.com 92b784bef2 Keep reliability checks tied to workflows and selected tools
Require the existing troubleshooting jobs and their destinations without
making additional help entries break unrelated safety checks. Keep the
opening backup warning scoped to its actual entry point.

Run closed service and middleware models through the containing fixture's
selected Python interpreter without site startup. Retain every safety
scenario and deadline while qualifying the pending maintenance source.

Refs #2888

Contract-Neutral: Test-only navigation assertions and confined model interpreter selection. Production behavior and safety deadlines are unchanged.
2026-10-18 14:28:37 +01:00
rcourtman cce5ca2a7e Supply GNU ln to macOS Linux installer fixtures (#3020)
The non-required **Unified Agent Native Verification** workflow has
failed on `main` (and most PRs) in both macOS jobs since 12 Oct (first
failing main run 38285211868 on c3524d373a; last green 38273335159 on
552e838749). The single cause is
`TestInstallSHUpstartTeardownRequiresObservedQuiescence`: 13 Python
subtests in `agent_upstart_teardown.py` fail with `ln: illegal option --
T`.

`scripts/install.sh` uses `ln -T -- <pending> <override>` and `ln -T --
<retired> <conf>` in the DSM Upstart teardown (added in the DSM
retirement series, a7271f5840 / b7b0eb0b01). That path only runs on
Synology DSM (Linux), so the installer is correct. The harness runs
those functions with the host `ln`, which on macOS runners is BSD `ln`
without `-T`.

The workflow already supplies GNU `stat/chmod/chown/mv/tar` (plus Bash
5) for Linux-only installer fixtures on macOS. This adds `ln` to that
set and extends `TestNativeInstallerFixtureToolsAreRealAndStepLocal` to
require it (missing/non-GNU scenarios, PATH binding, and real `ln -T`
no-replacement checks against file and directory targets).

Verification:
- Reproduced locally on macOS: the harness fails 13/15 with BSD `ln` and
passes 15/15 when `ln -T` is available.
- `TestNativeInstallerFixtureToolsAreRealAndStepLocal`: fails against
the old workflow (real tools, missing ln, non-GNU ln) and passes with
this change (Linux).
- The macOS jobs themselves are only exercised by this PR's CI run.
2026-10-17 10:16:53 +01:00
rcourtman 5c3c7b8baa Make the Upstart teardown harness independent of umask (#3026)
`scripts/installtests/agent_upstart_teardown.py` created its fixture job
directory with a bare `mkdir`. Under a caller umask of 002 (the default
on Ubuntu hosts with user-private groups, pulse-dev included) that
directory came out 775. The installer's `trusted_lifecycle_regular_file`
check then correctly refused the fixture config, and running the harness
directly failed 23 subtests. The Go driver
`TestInstallSHUpstartTeardownRequiresObservedQuiescence` still passed,
because the installtests package already pins umask 022 for its children
(`umask_unix_test.go`). So the bug only shows up when the harness runs
on its own.

The fix gives the job directory an explicit 0755 mode. The sibling
TrueNAS and uninstall-state harnesses already create their trust-checked
directories with explicit modes. The production functions under test
still run with the caller's umask. They `chmod 644` what they write, so
nothing in production depends on umask.

Checked on pulse-dev (python3 3.12, go 1.26.5):

| run | umask 0002 | umask 0022 | umask 0077 |
| --- | --- | --- | --- |
| harness before | FAILED (failures=23, errors=1) | OK (15) | OK (15) |
| harness after | OK (15) | OK (15) | OK (15) |
| Go driver after | ok | ok | — |

The other Python harnesses in `scripts/installtests/` (legacy, OpenRC,
systemd, TrueNAS, uninstall-state) already pass under umask 0002, so
they are unchanged.

Test-only change.
2026-10-17 03:18:22 +01:00
rcourtman 0a7a9490d5 Fix action-runner setup after collector TLS trust recovery (#3010)
An existing collector can retain its legacy TLS bypass after the host
trusts the Pulse certificate. Updating it with action-runner enrollment
then incorrectly rejects normal system-root trust and enters recovery
before creating the runner service.

Keep collector/download settings separate from verified runner TLS.
HTTPS runner configuration continues to use system roots, a configured
CA or an exact certificate pin and never inherits the collector bypass.
Activation, cancellation, revocation and service recovery retain their
existing boundaries.

Validation: the new executable regression fails against the previous
installer and passes with the fix. It covers recovered insecure state,
private configuration persistence and the real runner credential
lifecycle binary over TLS. System roots, custom CA and exact pin
succeed. Untrusted certificates and incorrect pins receive no
credential-bearing request. The installer Action Runner suite, runner
binary tests and hostagent Action Runner suite pass.

Agent Doctor enrollment also passes in the current built app at 1440,
820 and 390 pixels wide, covering details disclosure, issue
failure/pending/success, private command copying, one-time reveal,
clearing and reload. Browser endpoints use synthetic fixtures. No native
installed-host or published-release acceptance is claimed.

The additional native workflow passes on Linux x64 and ARM64. Its macOS
Intel and ARM64 jobs fail the unchanged Upstart retirement contract
because the host `ln` rejects `-T`. The same failure occurs in [the
earlier main
run](https://github.com/rcourtman/Pulse/actions/runs/38899584939). This
change does not touch that fixture or the exercised retirement
functions.
2026-10-17 02:33:07 +01:00
rcourtman 327ef6a100 Make the Upstart teardown harness independent of umask
agent_upstart_teardown.py created its fixture job directory with a bare
mkdir, so under a caller umask of 002 (the default on pulse-dev and other
Ubuntu user-private-group hosts) the directory came out 775. The
installer's trusted_lifecycle_regular_file check correctly refuses a
config whose parent is group-writable, and 23 subtests failed when the
harness ran directly. The Go driver only passed because the
installtests package pins umask 022 for its children.

Give the job directory an explicit 0755 mode, matching the sibling
TrueNAS and uninstall-state harnesses, which already create their
trust-checked directories with explicit modes. The production code
under test still runs with the caller's umask; it sets 644 on the files
it writes, so the harness now passes under 002, 022 and 077.
2026-10-17 01:10:19 +01:00
rcourtman 1591af6c1e Supply GNU ln to macOS Linux installer fixtures
The DSM Upstart removal path links its manual override and restores a
retired job config with GNU `ln -T --`, a hard link that never replaces
or descends into an existing target. Its Python harness executes those
production functions with the host's ln. On the macOS runners that is
BSD ln, which rejects -T ("illegal option -- T"), so 13 subtests of
TestInstallSHUpstartTeardownRequiresObservedQuiescence have failed on
both macOS jobs since the DSM retirement series landed on 12 Oct.

The production code is right: that path only runs on Synology DSM
(Linux), never on a macOS agent host. The gap is the macOS fixture
toolset, which already supplies GNU stat/chmod/chown/mv/tar for these
Linux-only installer contracts but not ln. Add ln to that step and make
the fixture-tool contract test bind it and exercise real `ln -T`
no-replacement semantics (file and directory targets).

Contract-Neutral: CI fixture tooling only; macOS runners gain the GNU ln the existing DSM Upstart harness already assumes, no installer or lifecycle contract change
2026-10-17 00:36:19 +01:00