Commit Graph

12628 Commits

Author SHA1 Message Date
pulse-triage[bot] 54de7bdca7 Capture fresh broadcast resources and history targets together
Resolve canonical source mappings once per complete projection while preserving detached resource content and target selection after host coalescing. Bind resources and targets to the same locked generation, including absence and continuity overlays, and avoid another owned outer-slice copy. No timestamp cache or native CPU/RSS claim. Retain cold, clean and dirty full-content controls and paired complete-projection measurements.

Contract-Neutral: Internal bulk-read and copy-ownership repair preserves existing public resource/history fields, source precedence, agent identity/lifecycle, tenant/revocation and safety contracts; no wire or permission delta.
Change-source: pulse-maintainer
2026-10-05 11:25:57 +01:00
pulse-triage[bot] d7a305922a Merge reviewed Core guest metadata writer and shutdown safety
Change-source: pulse-maintainer
2026-10-05 10:56:58 +01:00
rcourtman 7387703d23 Retire the same-subsystem test wildcard the guards never honoured (#2505)
## Problem

`check_staged_contracts` (and `canonical_commit_shape_errors`) passed
the impacted entry from `infer_impacted_subsystems` as the `rule`
argument of `staged_verification_files_for_requirement`. The
`allow_same_subsystem_tests` branch matches through
`subsystem_matches_path(rule, path)`, which reads `owned_prefixes` and
`owned_files`, and the entry carried neither. So since the v6 import
(778a2577b6) a same-subsystem test never satisfied a requirement.
Meanwhile `subsystem_lookup.py`, which passes the real registry rule,
and the guard's own BLOCKED text ("same-subsystem test/spec files are
also accepted") both said it would.

## Decision: keep the strictness, don't honour the flag

Honouring the 12 registry flags would have loosened the guard far beyond
what the flags appear to have been meant for:

- All 16 rules set `require_explicit_path_policy_coverage`
(`registry_audit` makes it mandatory). The default policy is never
consulted, so the 5 rule-level flags (alerts, cloud-paid, monitoring,
relay-runtime, unified-resources) are dead either way.
- On the 7 path policies, the flag accepts any test the subsystem owns,
not just tests near the policy's code. Proof files each policy would
newly accept on `main`:

| policy | accepted today | newly accepted |
|---|---|---|
| ai-runtime/canonical-mutation-registry | 4 | 455 (any `internal/ai`,
AI Chat or `internal/agentcapabilities` test) |
| ai-runtime/patrol-qualification | 27 | 433 |
| unified-resources/product-trust-actions-frontend | 16 | 128 (Go tests
in `internal/unifiedresources`) |
| agent-lifecycle/action-runner-runtime | 4 | 119 (mostly
`internal/hostagent`) |
| agent-lifecycle/collector-lifecycle-installer-transport | 7 | 118 |
| agent-lifecycle/agent-privilege-helper-runtime | 8 | 115 |
| security-privacy/audit-log-runtime-store | 10 | 7 (`pkg/audit` tests)
|

Six of the seven already list their own directory in `test_prefixes`, so
the flag only added reach across the subsystem. That reach would also
grow whenever the subsystem gained ownership, without anyone editing the
policy.

## Change

- `registry.json`: sets the 12 flags to `false`. This is what the guards
have actually enforced.
- `registry_audit.py`: rejects `true` at both the rule level and the
path-policy level, and tells the author to list proofs in
`test_prefixes` or `exact_files`.
- `canonical_completion_guard.py`: the impacted entry now carries the
rule's `owned_prefixes` and `owned_files`. The completion guard, the
commit-shape guard, `subsystem_lookup.py` and the BLOCKED message now
read the flag the same way.

**Enforcement does not loosen.** No live requirement accepts a proof it
rejected before. The visible change is in `subsystem_lookup.py`: it
stops reporting matches the guard refused. For example, a new
`internal/hostagent/*_test.go` used to claim three agent-lifecycle
policies and now claims none.

`security-privacy/audit-log-runtime-store` is the one policy where the
flag was the only directory-level proof route. It keeps its 12 exact
files. If `pkg/audit` tests should count, the explicit form is
`"test_prefixes": ["pkg/audit/"]`.

## Verification

- `python3 -m unittest canonical_completion_guard_test
registry_audit_test staged_commit_shape_guard_test subsystem_lookup_test
status_audit_test dev_runtime_governance_test`: 400 tests pass.
- New
`test_check_staged_contracts_reads_same_subsystem_tests_from_ownership`
runs `check_staged_contracts` end to end with a synthetic flagged
policy. Against the unfixed guard it fails (`1 != 0`).
- New
`test_audit_registry_payload_rejects_same_subsystem_tests_wildcard`
covers both rejection sites.
- `registry_audit.py --check` on the live registry: no errors.
2026-10-05 10:48:26 +01:00
rcourtman cb9b3e82bc Retire the same-subsystem test wildcard the guards never honoured
staged_verification_files_for_requirement() matches
allow_same_subsystem_tests against a subsystem's
owned_prefixes/owned_files, but both commit guards passed it the
impacted entry, which carried neither. Since the v6 import (778a2577b6)
a same-subsystem test never satisfied a requirement, while
subsystem_lookup.py (which passes the registry rule) and the guard's own
BLOCKED message ("same-subsystem test/spec files are also accepted")
said it would.

Honouring the 12 flags would have loosened the guard well past their
apparent intent. Every rule requires explicit path-policy coverage, so
the five rule-level flags are never consulted, and on the seven path
policies the flag reaches every test the subsystem owns: a
mutation-registry change would accept any internal/ai or AI Chat test
(~455) instead of its 4 listed proofs, and a frontend Actions change any
Go test under internal/unifiedresources. Six of the seven already list
their own directory in test_prefixes.

So the strictness stays and the tools now agree on it: the registry sets
the 12 flags to false (what has always been enforced), registry_audit
rejects true and points at test_prefixes/exact_files, and the impacted
entry carries the rule's ownership so the completion and commit-shape
guards read the flag the same way subsystem_lookup does.

No requirement accepts a proof it rejected before. subsystem_lookup
stops reporting matches the guard refused: a new internal/hostagent test
no longer claims three agent-lifecycle policies.
2026-10-05 10:27:17 +01:00
rcourtman f416722b64 Stop the SSE connect-snapshot test racing a queued duplicate (#2503)
TestSSEBroadcaster_AddClientWritesCurrentStatusBeforeReturning failed
under -race in CI on unrelated frontend PRs (2479, 2485). The test
broadcast a stage and connected straight away. When the broadcast loop
picked that message up after the client had registered, it queued the
stage for the new client too, so the client's writer goroutine wrote
the httptest recorder while the test read it without a lock.

AddClient itself is correct: it writes the preamble and current status
before returning, and a stage racing the connect can reach the client
twice, which the update modal already ignores. The test now lets the
broadcast reach an earlier client before connecting, so nothing else
writes the recorder and the status in the body can only come from the
synchronous snapshot write.

Reproduced by injecting the CI schedule through -overlay (broadcast
loop held until the client registers, caller paused as AddClient
returns): the old test failed 199/200 runs with DATA RACE, the new one
0/200. A mutant that moves the snapshot write onto the writer goroutine
still fails the new test 100/100.
2026-10-05 10:26:56 +01:00
rcourtman 9bb91352d0 Read the guard's registry and contracts from the evaluated commit (#2504)
Follow-up to #2502. CI now compares contract text against the commit
being checked, but the guard still decided which contracts that commit
owed using the subsystem registry and contract index from `HEAD`. In CI,
HEAD is the branch tip (or `main` after a merge), so a later commit
could still change an earlier commit's verdict:

- **False block.** A later commit adds a `Shared Boundaries` reference
to a file. An earlier commit that changed that file was complete when
written, but is now told it is missing the referencing contract.
- **False pass.** A later commit drops ownership of a file from the
registry. An earlier commit that changed that file without its contract
now passes.

The pre-commit hook reads both from the commit's own index, so CI and
the hook could still disagree.

**Change**
- `repo_file_io.read_repo_text` / `load_repo_json` and
`subsystem_contracts.tracked_contract_paths` / `tracked_contract_files`
/ `load_contract_index` accept a `revision`, alongside the existing
working-tree and index reads.
- A revision read uses only that commit's tree. It fails closed and
never falls back to the disk.
- In diff-base mode, the guard reads the registry and contract index
from the evaluated commit. So does the Prettier-only inference that
`--commit` enables.
- Pre-commit mode still reads the index, unchanged.

**Tests**
- `EvaluatedCommitGovernanceSourceTest` runs the real guard against a
temporary repo with its own registry and contracts, and covers both
cases above. Both fail against current `main`.
- New revision-read tests for `repo_file_io` and `subsystem_contracts`.

**Replay.** I replayed CI's per-commit loop (history registry,
Contract-Neutral trailers, `--diff-base <c>^ --commit <c>`) over the
last 120 non-merge commits on `main`, with the current and the new
guard. Verdicts are identical, and every historical registry loaded. The
PR #2494 commits still pass with HEAD at that PR's tip.

The maintainer preflight and coordinator already pass `--commit`
(rcourtman/pulse-dev-infra#704), so they pick this up without changes.
2026-10-05 10:21:01 +01:00
rcourtman 561609ec3b Correct the SSE connect-snapshot comment about racing broadcasts
The comment in AddClient said a broadcast racing a connect is either in
the snapshot or queued behind it. It can be both, so the client then
receives that status twice, possibly with an older in-flight stage in
between. Trusting the "either" is what led the connect-snapshot test to
assume nothing writes the recorder after AddClient returns, which made
it flaky under -race. Say what actually happens and why it is harmless:
stage progress only rises and the update modal ignores backwards stages.

Contract-Neutral: Comment-only correction in internal/updates/sse.go; no runtime or contract change.
2026-10-05 10:06:46 +01:00
rcourtman 5f5f7ac7fc Read the guard's registry and contracts from the evaluated commit
The completion guard's CI mode now compares contract text against the
evaluated commit (#2502), but it still decided which contracts a commit
owed from HEAD's registry and contract index. CI walks a branch with HEAD
at the tip (or at main after a merge), so a later commit could change an
earlier commit's verdict. If a later commit added a Shared Boundaries
reference to a file, an earlier commit that was complete when written got
blocked. If a later commit dropped ownership of a file, an earlier commit
that changed it without its contract passed. The pre-commit hook read
both from the commit's own index, so CI and the hook could still
disagree.

repo_file_io and subsystem_contracts can now read from a revision as well
as the working tree or index. A revision read uses only that commit's
tree and fails closed rather than falling back to the disk. In diff-base
mode the guard reads the registry and contract index from the evaluated
commit, as does the Prettier-only inference that --commit enables.
Replaying CI's per-commit loop over the last 120 commits on main gives
the same verdicts as before.
2026-10-05 10:04:06 +01:00
rcourtman 58c26e1629 Stop the SSE connect-snapshot test racing a queued duplicate
TestSSEBroadcaster_AddClientWritesCurrentStatusBeforeReturning failed
under -race in CI on unrelated frontend PRs (2479, 2485). The test
broadcast a stage and connected straight away. When the broadcast loop
picked that message up after the client had registered, it queued the
stage for the new client too, so the client's writer goroutine wrote
the httptest recorder while the test read it without a lock.

AddClient itself is correct: it writes the preamble and current status
before returning, and a stage racing the connect can reach the client
twice, which the update modal already ignores. The test now lets the
broadcast reach an earlier client before connecting, so nothing else
writes the recorder and the status in the body can only come from the
synchronous snapshot write.

Reproduced by injecting the CI schedule through -overlay (broadcast
loop held until the client registers, caller paused as AddClient
returns): the old test failed 199/200 runs with DATA RACE, the new one
0/200. A mutant that moves the snapshot write onto the writer goroutine
still fails the new test 100/100.
2026-10-05 09:59:14 +01:00
rcourtman b69258f14f Judge each commit's contract edit against that commit, not HEAD (#2502)
The canonical completion guard gave a different verdict in CI than in
the pre-commit hook for the same commit.

**Cause.** CI (`canonical-governance.yml`) and `scripts/dev-prepush.sh`
walk a branch's commits with HEAD at the tip, and run the guard per
commit with `--diff-base <commit>^`. In that mode the guard compared the
parent's contract text against `HEAD`, not against the commit it was
checking. So each commit was judged by the state of the whole branch.
The pre-commit hook compares HEAD with the staged index, which is the
commit's own change, so the hook and CI disagreed.

This had nothing to do with section headings. The paragraph sits under
`## Current State`, and both paths use the same substantive-section
rule. Only the texts they compared differed.

**Evidence.** In #2494, a99f3804a7 rewrote a Current State paragraph in
`performance-and-scalability.md`, and dfd6b4bd19 reverted it word for
word. Parent-vs-HEAD saw no change, so CI blocked a99f3804a7, which the
hook had correctly passed. With HEAD at dfd6b4bd19:

| guard | a99f3804a7 | dfd6b4bd19 |
| --- | --- | --- |
| before | blocked | passes |
| after | passes | passes |

The same comparison also let a bare runtime commit through when a later
commit on the branch carried its contract update.

**Change.**
- When `--commit` is given, the guard resolves the diff base against
that commit and reads the new contract text from it. CI already passes
`--commit`.
- `dev-prepush.sh` now passes `--commit` as well.
- Without `--commit` the guard still compares against HEAD, which is
right only when the commit being checked is the one checked out.

**Tests.** New tests in `canonical_completion_guard_test.py` build a
real temporary repo and cover both directions: a later revert no longer
blocks the commit that wrote the paragraph, and a later contract update
no longer passes an earlier bare commit. The dev-prepush test now checks
that each commit is passed as `--commit`. Against the old guard, the
false-pass test and the dev-prepush test fail.

The maintainer candidate preflight and coordinator in pulse-dev-infra
call the guard the same way. They get the same flag in a companion
pulse-dev-infra PR.
2026-10-05 09:45:35 +01:00
pulse-triage[bot] 5e51069048 Allow explicit recovery of retained guest metadata after failed close
Keep admission sealed and reuse a still-running close observer. A later explicit shutdown attempt may persist retained state after a completed write failure; successful close remains idempotent. This avoids a permanent offboarding dead end after an ordinary filesystem repair without an automatic spin retry or overlapping writes.

Contract-Neutral: Internal JSON writer recovery behaviour only; public routes, fields, permissions, identity format, private file mode and atomic persistence are unchanged.
Change-source: pulse-maintainer
2026-10-05 09:31:01 +01:00
rcourtman cf785c0ac5 Judge each commit's contract edit against that commit, not HEAD
CI and dev-prepush walk a branch's commits with HEAD at the branch tip and
run the completion guard per commit with --diff-base <commit>^. The guard
then compared the parent's contract text against HEAD rather than against
the commit it was evaluating, so every commit was judged by the whole
branch. The pre-commit hook compares HEAD with the staged index, which is
the commit's own parent-to-commit change, so the two disagreed.

PR 2494 hit this: its second commit reverted a Current State paragraph in
performance-and-scalability.md that its first commit wrote. Parent-vs-HEAD
saw no change, and CI blocked a commit the hook had correctly passed. The
same comparison lets a bare runtime commit through when a later commit in
the branch carries the contract update.

When --commit is given, the guard now resolves the diff base and reads the
new contract text from that commit. CI already passes --commit, and
dev-prepush now does too. Without --commit the guard still compares with
HEAD, which is right when the evaluated commit is checked out.
2026-10-05 09:29:57 +01:00
pulse-triage[bot] 244d322dce Coalesce guest identity writes and retain incomplete tenant shutdown
Admit detached metadata in order, merge identity fields without restoring stale
operator edits, and persist complete snapshots with one owned writer. Seal late
writes and return shutdown failures before organisation data or offboarding is
removed. Preserve existing drain budgets, file format, tenant boundaries and
atomic persistence. Add controlled burst, ordering, write-error, blocked-close
and real deletion controls.

Contract-Neutral: Behavioural repair of existing JSON persistence and organisation deletion; no route, field, permission, identity format, schema or provider authority change. Shutdown failure uses the existing HTTP error envelope. Internal ownership methods and explanatory contract text change, not the public contract shape.
Change-source: pulse-maintainer
2026-10-05 09:21:40 +01:00
pulse-triage[bot] 76c6c6e254 Keep guest readings truthful and diagnostic help safe (#2486)
## Outcome

- Small positive container CPU readings display as fractions rather than
zero, while raw readings, sorting and alert thresholds retain their
existing host-capacity scale.
- Both guest History entry paths distinguish retained/unavailable memory
and paused filesystem readings from current measurements. Original
observation source/time and stored samples survive refresh; unavailable
is not measured zero.
- Agent lookup avoids cloning unrelated Kubernetes metadata, and
registry bulk reads/grouping avoid repeated derived-metadata work while
preserving full inventory, identity, freshness and copy ownership.
- Failed alert sends reconcile delivery health only after releasing
their delivery gate and attempting the audit write. Operator transitions
remain immediate; retry budgets/policy are unchanged.
- PBS/API/History/temperature/VM and NAS instructions keep credentials
and diagnostic results private, bound requests, preserve logs and
unexpected files, and stop on unsafe preparation. NAS guidance now
matches the existing QNAP data-volume default, with private per-attempt
staging rather than destructive cleanup advice.

The demand is misleading container labels (Related to #2447),
false-current guest readings (Related to #2439), unnecessary per-report
work in the #2199 estate evidence, and unsafe existing diagnostic
recipes. Native metric accuracy, CPU/RSS relief, alert delivery, guest
recovery and every-symptom report resolution are not claimed.

Related to #2199

## Reviewed candidates and complete range

Complete proposed range:
`b2b5be27e94839c2561a8df5680cfe45810fd84f..5127e10164f5d743baa901813187d8b54594de1e`.
Starting reviewed tip `b21969de1a2bb17c849a0241384827bd6f4e6fef` is
preserved in final ancestry.

This advances the completed, failed proposal head
`e3652756fceb69d5389ebb932dceb39ced2e5e19`; it does not interrupt a
still-checking batch. Its existing PBS/API/fractional CPU/agent-lookup
candidates remain in ancestry. The added reviewed work includes:

- Passive VM `3a0a7f630205082c9610bb0cb8e5e5c139070b0a`; passive
temperature `4cb1c8a232576778ee6754bb1e5655d125c854db`; token-aware
Proxmox permissions `3c249ecacf4ab747ab822ad972527f7799bc00a0` and
composed webhook proof `899414d8ca2fbc86b2245675bf51bc30d14bfac9`.
- Mutation-bound materialisation
`51ef7edddab15f987fc930e31dbcf897777728f4..bec1d6455f870a877fc66df41a0c5e0ad02525db`;
pass-local owner grouping
`ff4626450343a0e4cb73b2516f52cf76ed4e0891..ab15c4db8a9f84a59d5d2f74ae50da1793f031e3`.
- Private-guide fixtures and phase/runner corrections
`5082c60d27d4946814ac05af512155eb3447238c`,
`8ad902ed1faecbee19da4908be8c23fe451f64f3`,
`eb9cf740bf1da0f4c4d092ffe976b353054b4b2a`,
`4bcca1a100f652db5024da614fa702f6a515e2f4`,
`87790b90428206e2c228748c9df46d3a26ac166d`,
`c0f0474423869e5ccc3d59db2d3013669ac37215` and
`c4207580689166eb04233a050706416da4a8c362`.
- Clone-boundary allocation controls
`476805821693146a79b3fcceca36613bfbe691b3` and alert callback ordering
`d500b4f211526bda3509adef6a7c4fb3e0337506`.
- Canonical History `ca255a0e3f81a8d1588f3e783b7084fc863b37c8`, composed
Docker evidence `d358adf7d0becc1deeca0bcb1a135fea024b4702`, NAS guide
`34a568ef656bf59a73f67cffaa23cdcf7910febf`, integrated at
`b21969de1a2bb17c849a0241384827bd6f4e6fef`.

All reviewed identities and additive upstream merges are retained.
Batch-start upstream `93652f453a3a30299b8159c52cd92ae1840d5cbc` was
already an ancestor. During proof admission current main advanced to
`b2b5be27e94839c2561a8df5680cfe45810fd84f` (TrueNAS storage
explanations, PR2497). Bounded refresh
`a0323a355e22fb6f0c707b6462e9cd49ad262f346d435812483e8437b1d563dd`
completed with exit 0 and unchanged source; additive full-SHA merge
`5127e10164f5d743baa901813187d8b54594de1e` then incorporated it without
conflict or rewriting accepted work. Independent drawer, security, Audit
Webhooks and storage presentation are preserved. No product selection
was performed.

## Validation and retained limitations

PR2486 run 37255054627 fails old whole-registry allocation measurement
(983→1,000), PBS bootstrap literals and copied API/PBS recipe
assertions. Governance run 37255054792 actually fails the same
bootstrap-doc assertion; its API-contract BLOCKED text is a logged,
successfully suppressed Contract-Neutral requirement, not the workflow
failure. These deterministic failures do not warrant unchanged-head
retries. The newer source corrects those checks without relaxing their
safety limits. The allocation test now measures the clone boundary with
unchanged three samples/+10 and a full-publication positive control; it
does not establish the old whole-admission envelope.

Retained reviewed evidence includes agent lookup `source-bg4zd66_`,
materialisation `source-u0i3z9yq`, grouping
`source-176ctacf`/`source-wv738zr1`, callback `source-xx7omeec` and
complete Core races/vet/build `source-liltmi2o`. Materialisation cold
List-only regressions (+4.05% time/+4.49% bytes/+13.33% allocations),
grouping time increases (+0.82% demo/+1.48% empty), earlier writer/drain
failures and unknown historical results remain; these synthetic controls
prove no estate CPU/RSS improvement.

History parent `source-t7yab3c2` reproduces false-current; final
`source-etqcidkl` passes 22,695 frontend tests,
types/lint/embed/budgets, with complete recorded raw-output hash match
and own guest stop. Twelve History/Docker browser cases have 44
owner-inspected captures; earlier selector/console failures and three
skipped tests remain. Final range browser guard confirms unchanged
covered bytes, so those sufficient visual checks were not replayed. NAS
`source-9rx_y8hp` passes 25 copied-command/mirror tests and public-doc
checks with parent failure controls; wrong-directory attempt remains
failed.

Combined-batch `source-w_c7gm41` passes full models, unified-resources,
notifications and installer suites at starting tip `b21969de`; its
overall exit is 1 because two repoctl cross-repository existence checks
lack private sibling checkouts in this single-module VM. The subsequent
chained checks did not run. That result remains failed, with complete
1,685-byte hash-matched output and completed stop; no private-evidence
acceptance is inferred. Backend code/tests/manifests and copied-guide
inputs are byte-identical across the upstream merge, so those passing
suites were not repeated. Starting-tip frontend `source-7sfiykc1` passes
type-check, 353 tests across nineteen connected suites, and embed build.

Exact final head `5127e101` is checked by:
- `source-o6i0fysr`: offline npm graph installation, types, 289 tests
across eight TrueNAS storage/History/Docker/docs/shared-primitives
suites and production embed build.
- `source-vdatp7x7`: repoctl using the repository's existing public-only
Actions profile, focused agent lookup controls, scoped vet, all 80
selected copied-guide Python tests, 241 public Markdown files and 62
shipped mirrors. This profile deliberately leaves private sibling
existence to the separate private governance job; it is not private
evidence or actual hosted execution.
Both final receipts have exit 0, independently hash-matched complete raw
output (60,374 / 1,633 bytes) and completed guest stops. Frontend
long-line observer flags are retained without truncation or overflow.
Earlier missing-context failure is not relabelled green.

Publication provenance, whitespace, every in-range non-merge completion
guard and final-range browser guard pass. No release, installed
safety/metric accuracy, native thaw/every-covered-filesystem
writes/liveness, or permission for uncertain monitoring resumption is
established. Required exact-head CI and protected landing remain
separate, with native/reporter follow-through still owned.


[How Pulse handles
triage](https://github.com/rcourtman/Pulse/blob/main/docs/AI_TRANSPARENCY.md)
2026-10-05 08:11:35 +00:00
pulse-triage[bot] 5127e10164 Preserve reviewed monitoring work alongside upstream TrueNAS storage explanations
Incorporate exact current upstream additively without rewriting accepted candidates. Retain truthful History, private guidance and source ownership controls.

Change-source: pulse-maintainer
2026-10-05 08:27:33 +01:00
rcourtman b2b5be27e9 Say why TrueNAS storage is unhealthy, drop the healthy pills (#2497)
The TrueNAS Storage tab put a green Healthy pill on every pool, dataset
and disk, repeating the status dot, while the few amber rows gave no
reason.

- Healthy rows leave the Health column empty.
- A non-healthy row says why in the Health column, on the row's single
line, coloured by bucket. The order is: TrueNAS's own alert text, then
Pulse's risk summary when TrueNAS raised no alert, then the dataset
state tag (read-only datasets raise no incident), then a plain fallback,
so no amber row is silent. Stale TrueNAS data leads when it is the
cause. A count stands in for further reasons, and the full list shows on
hover and for screen readers.
- Health sorts by severity rank and stays desktop-only in every scope.
The old pill clipped past the phone edge in the disk view. Phones keep
the status dot and the row drawer.

Verified at 1440, 820 and 390 wide, light and dark, including the disk
scope and the phone drawers.
2026-10-05 08:11:11 +01:00
pulse-triage[bot] b21969de1a Integrate reviewed private and non-destructive NAS installation guidance
Change-source: pulse-maintainer
2026-10-05 08:06:40 +01:00
pulse-triage[bot] f3c315a1fd Incorporate batch-start published Audit Webhooks guidance
Change-source: pulse-maintainer
2026-10-05 08:06:22 +01:00
rcourtman 615c03de55 Merge remote-tracking branch 'origin/main' into feat/truenas-storage-health-reasons 2026-10-05 07:53:47 +01:00
rcourtman d504f4f9bd Read TrueNAS dataset state through the shared helper
The storage table and the resource drawer both explain a read-only
dataset from its provider state tag. The drawer change added a shared
helper for that mapping, so the storage model now uses it too instead of
keeping a second copy, and reads it only for datasets, as the drawer does.
2026-10-05 07:53:21 +01:00
rcourtman 93652f453a Point Audit Webhooks visitors to alert notifications (#2500)
Audit Webhooks is the only webhook page in Settings, so people who want
alerts in Discord or Slack land there. In #1344 a user tried to add a
Discord webhook from it and filed a bug when that didn't work. Alert
delivery lives in Alerts, Notifications, which Settings never mentions.

The panel now opens, gated or enabled, with one line saying these
webhooks carry security events only, linking to alert notifications.

Verified at 1440 and 390. The link lands on /alerts/notifications
without a page reload.
2026-10-05 07:51:04 +01:00
pulse-triage[bot] e32304902b Incorporate batch-start published security guidance
Preserve the exact published frontier alongside reviewed canonical work. No publication or deployment is performed by this merge.

Change-source: pulse-maintainer
2026-10-05 07:51:04 +01:00
rcourtman 8c2083ad65 Call plain HTTP critical only when Pulse is publicly reachable (#2499)
The Security Overview tagged the HTTPS tile red and Critical on every
install, directly above a hardening card that calls HTTPS Recommended on
a private network and says local HTTP is acceptable. The same flag
counted plain HTTP as a missing critical control in the posture score,
so a typical LAN install read 55 percent.

The tile now uses the hardening card's rule: critical once the instance
is publicly reachable, recommended otherwise. A private install with a
password and protected exports reads 79 percent (still Moderate). A
public one still shows HTTPS as Critical.

Verified at 1440 and 390 on a private-network instance.
2026-10-05 07:41:05 +01:00
rcourtman 146595b848 Merge remote-tracking branch 'origin/main' into feat/truenas-storage-health-reasons 2026-10-05 07:39:46 +01:00
pulse-triage[bot] 5fbc773563 Integrate reviewed alert reconciliation and precise lookup controls
Retain exact Core commits 4768058216 and d500b4f211. Reviewed callback release ordering, metadata-specific measurement boundary and retained race, vet and build proof; unrelated JSON drain failure remains Core-owned.

Change-source: pulse-maintainer
2026-10-05 07:37:52 +01:00
pulse-triage[bot] acdd8b7a65 Incorporate the batch-start published drawer frontier
Preserve existing reviewed integrations and published attention-reason presentation without re-parenting source commits.

Change-source: pulse-maintainer
2026-10-05 07:37:50 +01:00
rcourtman 09f07d8514 Drop the storage table from the title-case helper consumers
The shared-template rule required every TrueNAS table to use the shared
title-case helper. The storage table used it only for the risk-level
pill, which the Health reason replaced, so it no longer formats any raw
provider value and the required-consumer check failed. The scope-wide
guard against a local titleCase helper still covers the file.
2026-10-05 07:37:46 +01:00
rcourtman d8b8541f37 Merge remote-tracking branch 'origin/main' into fix/audit-webhooks-point-to-notifications 2026-10-05 07:25:28 +01:00
pulse-triage[bot] 34a568ef65 Keep constrained NAS install guidance private and non-destructive
Match QNAP guidance to existing data-volume relocation, create private per-attempt staging and preserve diagnostic logs and identity. Exercise copied commands for preparation and installer failures without an appliance operation.

Contract-Neutral: Clarifies existing installer storage and private staging guidance; no runtime, permission or data-contract change.
Change-source: pulse-maintainer
2026-10-05 07:25:14 +01:00
rcourtman e37a692086 Point Audit Webhooks visitors to alert notifications
Audit Webhooks is the only webhook page in Settings, so people looking to
send alerts to Discord or Slack land there. Issue 1344 tried to add a
Discord webhook from it and filed a bug when that did not work. Alert
delivery lives in Alerts, Notifications, which Settings never mentions.

The panel now opens, gated or enabled, with one line saying these webhooks
carry security events only and linking to alert notifications.
2026-10-05 07:23:30 +01:00
rcourtman d2e2243dcc List every reason a resource needs attention in its drawer (#2498)
On a phone the TrueNAS Storage Health column is hidden, so the row
drawer is where a user learns why a row is amber. It fell short in three
ways:

- A read-only dataset said only "Observed state Degraded", because a
read-only dataset raises no incident.
- A pool showed one of its two incidents, because the health presenter
read only the rollup, plus a generic "Resource Health Issue" line.
- When an open alert matched the first reason, the drawer dropped every
other reason.

The fix:

- The shared health presenter keeps every incident summary.
- It adds a TrueNAS dataset's state, through a small shared util the
storage table will also use.
- It uses the rollup's category label only when nothing more specific
exists.
- The Overview's Needs attention list skips only the reasons an open
alert already states, ignoring a trailing full stop.

Verified on the phone and desktop drawers for the read-only dataset and
the degraded pool, and on the Machines table, which is unchanged.
2026-10-05 07:20:45 +01:00
pulse-triage[bot] 3c95617bd1 Integrate reviewed truthful guest History presentation
Preserve the exact reviewed Web commits and their own-parent browser receipts alongside the supplied main frontier.

Change-source: pulse-maintainer
2026-10-05 07:20:38 +01:00
pulse-triage[bot] 757ae22ece Integrate reviewed private Metrics History guidance
Preserve the exact reviewed documentation candidate and its retained privacy/query proof; no runtime or storage changes.

Change-source: pulse-maintainer
2026-10-05 07:14:39 +01:00
rcourtman 731cd84b11 Merge remote-tracking branch 'origin/main' into fix/security-https-severity 2026-10-05 07:05:07 +01:00
pulse-triage[bot] c635e0dda0 Preserve reviewed maintenance alongside current upstream presentation
Additively incorporate the exact observed main frontier without advancing PR2486 or rewriting accepted work. The newer canonical range remains deferred pending its existing source-check and composition-proof routes.

Change-source: pulse-maintainer
2026-10-05 07:04:09 +01:00
rcourtman 85bf454ba3 Call plain HTTP critical only when Pulse is publicly reachable
The Security Overview tagged the HTTPS tile red and Critical on every
install, directly above a hardening card that calls HTTPS Recommended on
a private network and says local HTTP is acceptable. The same flag also
counted plain HTTP as a missing critical control in the posture score, so
a typical LAN install read 55 percent.

The tile now uses the hardening card's rule: critical once the instance
is publicly reachable, recommended otherwise. A private install with a
password and protected exports reads 79 percent, still Moderate, and a
public one still shows HTTPS as Critical.
2026-10-05 07:03:03 +01:00
rcourtman a28b105645 Mark the alerting Mail Gateway number, keep rows single-line (#2496)
An earlier change (#2490) named the alert type on a second line under
the gateway name. The shared platform-table contract keeps summary rows
on one line, with context in an operational column, a tooltip or the
drawer.

Each gateway alert is about one number in the row, so that number now
carries it. The message shows on hover and for screen readers.

- Queue, hold and message-age alerts colour Queue.
- Deferred alerts colour Deferred.
- Quarantine alerts colour Quarantine.
- Inbound spam and virus anomalies colour the Spam and Virus columns.

Alerts with no column of their own (offline, outbound anomalies) stay on
the status dot, a screen-reader summary, the row tint and the drawer.

Verified at 1440 and 390, light and dark, including the phone drawer.
2026-10-05 07:00:59 +01:00
rcourtman 206c352bef Pick the sdc disk row by kind in the TrueNAS history spec
The spec took the first table row containing "sdc". The archive pool's
Health cell now carries the copied SMART alert for /dev/sdc, so that row
matched first and the spec opened the pool drawer instead of the disk's,
then timed out waiting for the sdc drawer. Select the disk row by its
storage kind, which is what the test means.
2026-10-05 06:58:37 +01:00
rcourtman b7e8eb8beb List every reason a resource needs attention in its drawer
On a phone the TrueNAS Storage Health column is hidden, so the row drawer
is where a user learns why a row is amber, and it fell short. A read-only
dataset said only Observed state Degraded, because it raises no incident.
A pool showed one of its two incidents, because the health presenter read
only the rollup, plus a generic Resource Health Issue line. And when an
open alert matched the first reason, the drawer dropped every other one.

The presenter now keeps every incident summary, adds a TrueNAS dataset's
state (the provider's only reason for a read-only dataset, read through a
shared util), and uses the rollup's category label only when nothing more
specific exists. The Overview's Needs attention list skips only the
reasons an open alert already states, ignoring a trailing full stop.
2026-10-05 06:54:42 +01:00
pulse-triage[bot] d358adf7d0 Verify composed container labels and host context
Record inspected desktop and touch-WebKit phone light/dark evidence for the final fractional CPU, silent-host and shared-column content. Exercise the actual mobile Filters and View controls before flat-row drawer expansion; production container source is unchanged.

Change-source: pulse-maintainer
2026-10-05 06:51:02 +01:00
pulse-triage[bot] c420758068 Keep history diagnostics private and match current query types
Reuse the tested API helper, explain point/source and access distinctions, and preserve data before storage tuning. Copied examples now exercise private success/error responses, redirects, incomplete transport and encoded IDs.

Contract-Neutral: Documentation and recipe checks align with existing history API behaviour; no runtime, storage, entitlement or authentication contract changes.
Change-source: pulse-maintainer
2026-10-05 06:47:09 +01:00
pulse-triage[bot] ca255a0e3f Keep retained guest readings truthful in canonical History
Share the original memory observation policy with the dedicated guest drawer and carry filesystem deferrals through the existing History presentation. Preserve measured zero, stored samples, source and access isolation, and same-ID read budgets. Record fresh eight-state browser proof; native thaw and workload recovery remain independent.

Change-source: pulse-maintainer
2026-10-05 06:42:55 +01:00
pulse-triage[bot] d500b4f211 Reconcile delivery failures after releasing alert gates
Commit the normal retry/dead-letter attempt audit before invoking monitoring health reconciliation outside the firing-alert delivery gate. Keep explicit operator actions immediate, transient retry budgets, configuration refusal, policy cancellation and operational links unchanged.

Change-source: pulse-maintainer
2026-10-05 06:40:53 +01:00
pulse-triage[bot] cadb669610 Integrate reviewed private-help checks without replacing stronger fixtures
Preserve the reviewed candidate commits as the second parent and retain canonical one-shot, redirect, partial-response and disclosure controls in the overlapping API/PBS smoke fixtures. Keep the candidate's canonical PBS guard, scoped private temperature-file assertions and unchanged-verdict audit diagnostics.

Contract-Neutral: Integration of test-only private-help adapters; no runtime, guides, dependencies or frontend source changed.
Change-source: pulse-maintainer
2026-10-05 06:39:53 +01:00
pulse-triage[bot] 2ed68c9ebe Incorporate supplied upstream frontier for private-help test review
Change-source: pulse-maintainer
2026-10-05 06:39:20 +01:00
rcourtman d6ccdd6943 Say why TrueNAS storage is unhealthy, drop the healthy pills
The Storage tab put a green Healthy pill on every pool, dataset and disk,
repeating the status dot, while the few amber rows gave no reason. The
pool said Warning where the dataset said Attention for the same bucket,
and in the disk scope the pill clipped past the phone edge.

Healthy rows now leave Health empty. A non-healthy row says why in the
Health column, on the row's single line as the shared platform-table
rhythm requires: TrueNAS's own alert text first, Pulse's risk summary when
TrueNAS raised no alert, then the dataset state tag (read-only datasets
raise no incident, so the tag is the provider's only reason), and a plain
fallback so no amber row is silent. Stale TrueNAS data leads when it is
the cause. A count stands in for further reasons, with every reason on
hover and for screen readers. Health sorts by severity and is
desktop-only in every scope, so phones keep the status dot and the drawer.
2026-10-05 06:37:24 +01:00
rcourtman b7b26bdfdf Merge remote-tracking branch 'origin/main' into fix/mail-gateway-single-line 2026-10-05 06:34:23 +01:00
rcourtman dbbf74646b Mark the alerting Mail Gateway number, keep rows single-line
The earlier change named the alert type on a second line under the
gateway name. The shared platform-table contract keeps summary rows on one
line, with context in an operational column, a tooltip or the drawer.

Each gateway alert is about one number in the row, so that number now
carries it: queue, hold and message-age alerts colour Queue, deferred
alerts colour Deferred, quarantine alerts Quarantine, and inbound spam and
virus anomalies their columns, with the message on hover and for screen
readers. Alerts with no column of their own, such as offline or outbound
anomalies, stay on the status dot, a screen-reader summary, the row tint
and the drawer.
2026-10-05 06:31:34 +01:00
rcourtman fdcffc6411 Say which TrueNAS services and VMs should be running (#2495)
The Services tab gave SMART a red dot with no reason, beside the same
Stopped pill as SSH, and spent a column on process IDs. The VMs tab
spent two columns on the bootloader and device counts. None of those
columns answers whether something is wrong.

- A service stopped while set to start at boot, and a VM set to
autostart that is stopped, say "should be running" beside the state,
with the full sentence on hover.
- The State column is hidden on phones, so the service drawer gains a
Condition row and the VM drawer gains the same row and summary note
(skipped while TrueNAS data is stale).
- A VM stopped without autostart gets no note: someone stopped it on
purpose.
- Process IDs, bootloader and device counts stay in the drawers.
- Rows stay single-line, as the shared platform-table rhythm requires.

Verified at 1440 and 390, including the phone drawer for SMART. The mock
has no stopped autostart VM, so that case is covered by component and
drawer-model tests.
2026-10-05 06:31:10 +01:00
rcourtman f30be8b48f Say which TrueNAS services and VMs should be running
The Services tab gave a red dot to SMART with no reason, beside the same
Stopped pill as SSH, and spent a column on process IDs. The VMs tab spent
two columns on the bootloader and device counts. None of those columns
answers whether something is wrong.

A service stopped while set to start at boot, and a VM set to autostart
that is stopped, now say "should be running" beside the state, with the
full sentence on hover. Services also gain a drawer Condition row, since
the State column is hidden on phones. A VM stopped without autostart gets
no note, because someone stopped it on purpose. Process IDs, bootloader
and device counts stay in the drawers. Rows stay single-line, as the
shared platform-table rhythm requires.
2026-10-05 06:13:52 +01:00