Commit Graph

697 Commits

Author SHA1 Message Date
pulse-triage[bot] 9085539f03 Gate ordinary E2E on auth renderer controls
Run the existing credential-free diagnostic regressions in tier admission before dependency installation or image/shard work. Check the actual workflow wiring alongside the read-only collector and unchanged strict goldens.

Contract-Neutral: Invoke existing auth-renderer regression controls in CI; no public contract, dependency, golden or threshold changes.
Change-source: pulse-maintainer
2026-10-14 07:18:13 +01:00
pulse-triage[bot] bd6de2bed3 Retain actual auth renderer facts beside strict goldens
Current hosted E2E stays green with an adverse probation auth comparison, while local auth pixels use a different observed font. Attach actual heading font selection, browser and viewport identity, and complete loaded stylesheet hashes before each existing strict comparison. Keep diagnostic gaps explicit, omit page/account/URL/CSS contents, and never change styles, baselines, thresholds or assertions.

Contract-Neutral: Add read-only test diagnostics for existing auth goldens; application, permission, deployment, screenshot and security contracts are unchanged.
Change-source: pulse-maintainer
2026-10-14 05:57:02 +01:00
pulse-triage[bot] f90a3da1c7 Keep the full Pulse Mobile retirement notice readable on phones
Current 390px consuming pixels showed the free-tier notice still inside a two-line sales gate, clipping the phone-alert alternatives. Use the shared labelled InlineNotice for that existing guidance, retain the entitled configuration and pairing controls, and target the actual notice rather than the hidden mobile page subtitle. No offer, entitlement, billing, configuration or service change is introduced.

Change-source: pulse-maintainer
2026-10-14 03:21:16 +01:00
pulse-triage[bot] 5384298c17 Use the current scoped phone review navigation label
The current source renders Next issue inside Review queue navigation; the phone proof still requested the old Next decision label. Preserve the scoped navigation, disabled previous control, focus, containment and reload checks while selecting the current button.

Contract-Neutral: Test-only selector update preserves the existing phone navigation and Patrol contracts.
Change-source: pulse-maintainer
2026-10-14 03:15:30 +01:00
pulse-triage[bot] e19d17af53 Exercise Patrol evidence through its current keyboard disclosure
Current consuming proof found the retained observation text hidden inside the deliberate Evidence and history disclosure. Check that boundary, open it by keyboard, keep every evidence, protection and lifecycle assertion, and attach the actual expanded detail. The synthetic shell also supplies the existing empty active-alert array rather than an invalid catch-all object.

Contract-Neutral: Test-only keyboard disclosure and typed fixture repair preserves Patrol and alert runtime contracts.
Change-source: pulse-maintainer
2026-10-14 03:13:40 +01:00
pulse-triage[bot] a1824f157a Supply the real digest shape in the Patrol attention fixture
The isolated current-source consumer entered its error boundary when Activity parsed the fixture catch-all empty object as a successful weekly digest. Supply the existing empty history fields, with no fabricated verified work, so Activity navigation remains tested alongside the unchanged acknowledgement, evidence and failure controls.

Contract-Neutral: Test-only fixture supplies the existing Patrol digest schema; runtime and operational contracts are unchanged.
Change-source: pulse-maintainer
2026-10-14 03:12:00 +01:00
pulse-triage[bot] 5e2bd40c87 Keep phone reload proof aligned with the resource-qualified Patrol title
The original detail assertion now matches the current resource-first title, but the same phone journey still required its obsolete title after reload. Assert the same canonical identity across both navigations without changing the fixture title or reducing detail, overflow, focus or adverse-state controls.

Contract-Neutral: Test-only selector repair preserves the existing Patrol title and operational contracts.
Change-source: pulse-maintainer
2026-10-14 03:06:29 +01:00
pulse-triage[bot] 30bce19ac5 Retain existing paid-phone controls in the navigation journey
After following the real entitled sidebar, read the existing Mobile controls and retirement date without pairing a phone or changing configuration. Keep new-sale prompts absent.

Contract-Neutral: Existing-flow browser assertions only; no licence, mobile service or offer changes.
Change-source: pulse-maintainer
2026-10-14 02:18:36 +01:00
pulse-triage[bot] 9e81bd661e Align browser guards with retirement and current navigation
Preserve auth pixels, hidden-form and entitlement controls while pinning the visual runtime/org inventory. Exercise the existing Mobile retirement notice separately from sales gates, and retain scoped Coverage links and resource-qualified Patrol decisions. Supply the real runtime providers to the existing PBS snapshot fixture.

Contract-Neutral: Test and browser-fixture repair only; no runtime, entitlement, retirement or monitoring contract changes.
Change-source: pulse-maintainer
2026-10-14 01:38:16 +01:00
pulse-triage[bot] 1a27471600 Reconcile published Overview charts with reviewed recovery guidance
Preserve accepted 0efe3f7a12 and exact upstream ebfdc9d669 additively. Keep quiet unavailable captions, keyboard and touch reading explanations, stopped-agent provenance and primary CPU readings. Preserve stable denied-update help outside Details and all regression assertions.

Change-source: pulse-maintainer
2026-10-14 00:08:44 +01:00
rcourtman 5168ae6460 Chart history on Overview in Docker host and resource drawers
PR 2913 moved the Proxmox guest and node drawers' charts onto Overview
and removed their History tab. The Docker host drawer and the shared
resource-detail drawer (agent machines, containers, PBS, TrueNAS,
Kubernetes and other platform rows) still kept the charts a tab away,
so the same kind of drawer behaved two ways depending on the page.

Both now render the history charts on Overview under the attention
list, opening on 24 hours with the range picker beside them, and drop
the History tab. Charts stop polling while another tab shows, and an
active alert on a charted metric draws its trigger the same way as in
the Proxmox drawers. A resource whose metrics target drops for one
snapshot simply loses its charts until the target returns; the
selected tab is kept.

Storage page disk and pool details keep their own History tab; they
are separate components and their Overview has no charts to repeat.

E2E specs 27 and 63 no longer click History; the drawer read starts
when it opens. The manual tab-retention proof now selects Manage. Its
fixture page fails before the drawer renders (no WebSocket provider)
on main as well, so it was not run.
2026-10-13 23:30:46 +01:00
rcourtman 77131c19b1 Show each bucket's min-max range behind drawer chart lines
Drawer charts plot one average per time bucket, so a short CPU burst or
I/O spike inside a 6-minute bucket (24h) or an hourly rollup (7d) shrinks into
the average and disappears. The trend cards this Overview replaced
stated a "24h low-high" range; the charts had nothing in its place.

Each stored point already carries its bucket's min and max. A tinted,
outlined band now spans those extremes behind every line, so a spike
stands as a spire above the average. Series whose buckets never spread
draw no band. While a time is inspected, the legend names that bucket's
peak when it rises at least a tenth of the plot above the average, and
the accessible value states each reading's average and range.

Spec 86 also allows sub-pixel clipping when it checks that the drawer's
Memory rows are fully in view. With the charts on Overview, CI placed
that table on a fractional offset and the rounded table border clipped
0.4% of it (ratio 0.9956), failing an assertion of exactly 1.
2026-10-13 22:59:35 +01:00
rcourtman 070d0a36fb Chart drawer history on Overview and drop the History tab
Opening a Proxmox guest or node drawer showed 24-hour trend cards for
CPU, memory and disk on Overview, then a History tab with the same
metrics again, in a different chart style and opening on 7 days, plus
network, disk I/O and thermals. One tab was a superset of the other,
so the reader had to reconcile two views of the same data.

The guest and node drawers now chart history on Overview itself, under
the alerts line and Worth checking, opening on 24 hours with the range
picker beside the charts. The History tab and the trend cards are gone.
The charts stop polling while Manage is shown, show the live reading
(qualified current or last known) until history is stored, and an
offline or stale node says so in one line instead of offering live
values.

What the cards carried and the charts lacked moves with them:
- An active alert on a charted metric tones its reading and, when it has
  a numeric trigger, draws it as a dashed line. I/O triggers are
  converted from the alert engine's MiB/s to the charts' bytes per
  second, and networkIn/networkOut map to the netin/netout series.
- The facts strip states the amounts the percentages are of: guest
  vCPUs and used-of-total RAM and disk, node used-of-total RAM and root
  disk (capacity only while a node's readings are withheld).

The range picker now mints its id once per instance, so a guest and a
node drawer open together no longer share a label target. Legend
readings are a step larger and the timestamp quieter, and the charts
use two columns at medium widths. Docker host and resource-detail
drawers keep their History tab for now.
2026-10-13 22:15:36 +01:00
rcourtman 160701bb42 Drop guest row captions that repeat an empty cell
The Workloads table put a second line under memory and disk cells on
every stopped guest ("N/A / Unavailable", "- / Unavailable"), on every
agentless VM's disk, and "May include cache" under every agentless VM's
memory bar, even at 20%. None of them said why or what to do, and they
broke the row rhythm.

Memory that cannot be read now renders the same dash as an unreadable
disk, with its reason in the cell title, the sparkline's hover title
and assistive text. Disk shows a visible label only for a retained
reading (Last known) or a read the user can fix (No permission, Agent
error). The cache caption appears only when the guest's own memory is
at its warning or critical threshold, the figure that colours the bar
in host-share mode too, which is the case #2762 and #2639 were about;
below that the caveat stays in the title and assistive text.

An unavailable memory explanation names what is known instead of
"Unknown source": a powered-off guest reads "Unavailable while the
guest is powered off." MetricMiniSparkline gains an optional tooltip
so the chart covering the cell can carry that reason without changing
its accessible name.
2026-10-13 21:08:59 +01:00
pulse-triage[bot] ff1e59ff33 Make the table-edge geometry control demonstrate its intended clipping
The first native pass verifies the complete canonical memory journey but exposes a faulty positive geometry control: collapsed borders alone did not reduce its target intersection. Make the synthetic table wider than its clipping container while keeping every text line safely inset. Preserve all negative clipping/hidden/empty controls and use no ratio tolerance; this diagnoses the control, not the product layout.

Contract-Neutral: Integration geometry control only; production frontend, memory semantics, safety behaviour and dependency graphs are unchanged.
Change-source: pulse-maintainer
2026-10-13 16:54:02 +01:00
pulse-triage[bot] 94cdf4d1da Check clipping on the actual memory text ancestors
Include nested value spans in the unclipped-text proof, not just their enclosing cells. Add controls for an internally clipped value and a hidden child whose line boxes still exist; retain the viewport and navigation requirements.

Contract-Neutral: Integration geometry proof only; production frontend, memory semantics, safety behaviour and dependency graphs are unchanged.
Change-source: pulse-maintainer
2026-10-13 16:46:23 +01:00
pulse-triage[bot] b5e8d41b7c Check readable memory evidence instead of collapsed table borders
The PR 2910 Chromium shard fails twice on a 0.991228 tbody intersection despite fully readable phone Memory, Total and Free evidence in the retained captures. Require every non-empty text line to fit the viewport, navigation clearance and clipping ancestors instead of a decorative table border. Keep all canonical-memory withdrawal, stale, zero and other-cluster assertions; add desktop/phone positive border and seven genuine unreadability controls without loosening a ratio or timeout.

Contract-Neutral: Integration geometry assertions and adverse controls only; production frontend, memory semantics, safety behaviour and dependency graphs are unchanged.
Change-source: pulse-maintainer
2026-10-13 16:26:06 +01:00
pulse-triage[bot] db2615f4da Reconcile published contracts and alert-first drawers with reviewed maintenance
Preserve exact reviewed d636bfdaea and published 0a44d276db ancestry. Retain both contract additions, readable System evidence and stable accessible update help alongside upstream alert-first trends, facts and Details.

Change-source: pulse-maintainer
2026-10-13 14:09:00 +01:00
rcourtman a59f6eb97a Open drawer Details in the hybrid memory browser spec
The guest drawer now keeps its Memory section inside a Details
disclosure that is closed by default, so the spec scrolled to hidden
rows and its in-viewport check failed. It opens Details once, as a
reader would, before judging the recovered rows.
2026-10-13 10:58:00 +01:00
pulse-triage[bot] a10fcd81a2 Verify Patrol setup through a real local model and first run
Exercise the public router, persisted settings, streaming readiness evaluator and Watch-only tool execution from the production desktop and touch UI. Use a credential-free local Ollama protocol double, with a rejected context control and an actual persisted run opened through Activity and History.

Contract-Neutral: Test-only journey fixtures verify the existing Patrol setup, provider transport, settings ownership and read-only first-run contract; no runtime, route, model policy, feature gate, notification or collector behaviour changes.
Change-source: pulse-maintainer
2026-10-13 01:26:27 +01:00
rcourtman 8cff7de026 Restore Patrol's Safe auto-fix and Autopilot modes
Reverts #2863 before any release ships it. The live Pro pricing and
Patrol pages, in English, German and Spanish, sell both modes ("applies
safe fixes in the mode you choose: Ask first, Safe auto-fix, or
Autopilot"), and paying installs chose them. Shipping the retirement
would have silently downgraded those installs while the sales page still
promised the modes.

If the modes are retired, that needs a dated notice: stop selling them
first, warn installs on those modes in-app, and then re-ship the change.
It should not arrive through an engineering cleanup.

The receipt #2863 landed stays in place; this commit adds the receipt
for the restored content.
2026-10-12 14:13:47 +01:00
rcourtman afc4c9275d Retire Patrol's Safe auto-fix and Autopilot modes
Patrol could run some fixes on its own in two paid modes: assisted (Safe
auto-fix) and full (Autopilot, behind a server-owned acknowledgement).
Schema-v17 telemetry for the week to 2026-10-11 counts 1 assisted and 2
full installs out of 612 reporting Patrol, 4 executed Patrol actions in
30 days, all on one install, and no GitHub request for more autonomy,
while the automatic path carried its own policy leases, acknowledgement
store, per-resource allowlist, release gate and Colima lab. Investigation
is what paid installs use, so it stays.

Patrol now has two modes. Watch only reports findings. Ask first
investigates and proposes fixes, and every fix waits in Actions for an
operator to approve and run it.

- A saved assisted, full or autonomous level reads as approval, and the
  update routes refuse those levels with 400 invalid_autonomy_level.
- The core Patrol action broker only plans: Plan and Submit persist the
  plan and return. ExecuteUnderPolicy, the policy authorization lease,
  the policy admission store methods, the Autopilot acknowledgement
  persistence, routes and dialog, and the per-resource automatic-action
  allowlist (store column now written as NULL) are gone.
  GetPatrolFullModeUnlocked stays on the extension contract and reports
  false.
- Historical action records keep their policy lease and tenant/resource
  reason codes decodable, labelled as retired modes.
- The Patrol inbox no longer splits issues into "continuing without a
  decision"; every current issue is an operator decision.
- Release gate RG-06 now proves Patrol never executes without an operator
  decision (integration tier) instead of limited unattended autonomy,
  and RG-02/RG-04 point at the surviving human-admission proofs.
- Pro plan copy, AI docs, privacy telemetry wording and the subsystem
  contracts describe the two modes.
2026-10-12 11:52:11 +01:00
rcourtman 3ec94790a9 Merge remote-tracking branch 'origin/main' into refactor/remove-ai-discovery 2026-10-12 09:15:24 +01:00
rcourtman 592a1e52f6 Advance the Go toolchain to 1.26.9 and x/net to v0.60.0
govulncheck on the pinned 1.26.8 toolchain reports twelve reachable
standard library vulnerabilities in Pulse (net/http, crypto/tls,
html/template, os, net/textproto), published on 2026-10-08 and fixed in
Go 1.26.9. golang.org/x/net v0.58.0 carries further advisories fixed in
v0.60.0. With 1.26.9 and x/net v0.60.0 the scan reports no affecting
vulnerabilities under both govulncheck v1.1.4 and v1.7.0.

Pulse's own govulncheck job last ran on 2026-10-05, so nothing flagged
this. The private Enterprise build and test job notices it: once the scan
finds reachable advisories it needs more than the 4 GiB CI VM holds, the
guest kernel kills govulncheck, and the job is cancelled. That has failed
every Enterprise pair check since 2026-10-08, including the first
beta's.

Move every release builder and local toolchain guard together, as 1.26.8
did, so no candidate artifact keeps an older compiler: go.mod, the
builder image digests (index digest for 1.26.9-alpine), the release and
dev scripts, the devcontainer, and the root pair diagnostic. Only x/net
is raised in the module graph; go mod tidy is deliberately not run, so
unrelated indirect requirements stay as they were.

Contract-Neutral: Toolchain and indirect dependency patch update that clears reported advisories; no product or runtime contract changed.
2026-10-11 22:37:46 +01:00
rcourtman 39f988b220 Remove AI Discovery
AI Discovery ran a separate model-backed service scanner beside Patrol
and the Assistant: its own store, scheduler, model route, API, tool and
drawer UI. No telemetry counter ever measured its use (the telemetry
discovery_enabled flag OR'd it with network subnet discovery), the
maintainer judged it a flop, and the context it produced is already
available to the models through pulse_query, pulse_read and the
unified-resource context sections.

This deletes internal/servicediscovery, internal/ai/infradiscovery, the
pulse_discovery tool, the /api/discovery routes, the mock Discovery
fixtures, the Discovery tabs in the resource, guest, node and Docker host
drawers, the identified-service cards, readiness badges, provenance
markers, suggested web-interface URLs, the Workloads AI Context column,
the chat "Discovery is off" hint and the AI Service Context settings page.
The old settings route redirects to Assistant settings. The model-bound
sanitizer allow-list existed only to pass Discovery context through, so it
goes too, along with the CopyableCodeRow primitive that only the
Discovery tab used.

Network subnet discovery (/api/discover, the Infrastructure discovery
settings) and the discoveryTarget agent coordinates are separate features
and stay. Stored discovery_enabled, discovery_interval_hours and
discovery_model AI settings are ignored on load; the settings API decoder
is lenient, so older frontends saving those fields still succeed.
2026-10-11 22:20:25 +01:00
pulse-triage[bot] 0cb31936f0 Wait for completed browser bootstrap in reused E2E sessions
Enter the existing infrastructure route without manufacturing local authentication hints. Await the rendered protected workspace, distinguish the login form at the unchanged URL from admission, and propagate pending or rejected bootstrap without a password fallback. Faithful delayed and rejection controls execute the actual predicate while preserving cookie-only probing, token and tenant isolation and failure cleanup.

Contract-Neutral: E2E fixture admission and regression controls only; product authentication, routes, subsystem contracts, workflow gates and dependency graphs remain unchanged.
Change-source: pulse-maintainer
2026-10-10 20:08:15 +01:00
pulse-triage[bot] 06c2fad483 Withdraw premature E2E cookie-bootstrap integration
Independent combined-tree review found that /infrastructure matches AUTHENTICATED_URL before protected bootstrap completes. The app renders Login at that same URL after a summary 401; the candidate double incorrectly redirected rejected bootstrap to root. A bounded control retaining the requested URL yields Missing expected rejection. Restore the pre-integration fixture/helper bytes and return d7ec05f07f to Delivery for a completed bootstrap assertion and faithful adverse control. This additive withdrawal preserves imported history.

Contract-Neutral: Withdraw E2E fixture navigation and regression controls only; product authentication, routes, contracts and dependency graphs remain unchanged.
Change-source: pulse-maintainer
2026-10-10 19:59:36 +01:00
pulse-triage[bot] d7ec05f07f Keep reused E2E cookie sessions on the protected workspace entry
The login entry deliberately skips protected bootstrap without a local login hint. Cookie-only reuse lacks that hint and leaves stable journeys at the login form despite a successful saved-session API probe. Enter the existing infrastructure route so normal protected bootstrap validates the cookies, without importing token or tenant storage. Preserve the authenticated URL assertion and cover actual browser rejection alongside the shipped pre-auth entrypoint policy.

Contract-Neutral: E2E fixture navigation and regression controls only; existing authentication, tenant isolation, routes, test tiers and dependency graphs are unchanged.
Change-source: pulse-maintainer
2026-10-10 19:40:19 +01:00
pulse-triage[bot] ee07be5a53 Reuse live E2E sessions and keep removal verification authenticated
Reuse the existing admitted cookie session only after a cookie-only request validates it against the current backend. Keep explicit session and token tests independent, reject revoked state, and propagate unexpected probe failures. The registration journey uses its documented cookie/CSRF authentication so removing a dedicated host token cannot invalidate its removal observer. Retain both the HTTP-success and removed-resource assertions, add behavioral adverse controls and run them in ordinary E2E CI.

Contract-Neutral: Test fixtures reuse validated cookie sessions and select documented cookie auth; production authentication budgets, token revocation, API and tenant contracts are unchanged.
Change-source: pulse-maintainer
2026-10-10 17:51:53 +01:00
rcourtman b2d69b7569 Port the inline selection scroll E2E spec to the platform pages
The 47-inline-selection-scroll-stability spec sat in QUARANTINED_SPECS
with no reason, so it never ran. Its five tests opened routes the
platform-first IA retired: /infrastructure (now an entry route that
redirects to the first visible platform page, with settings and alerts
as fallbacks), and /recovery, /workloads and /storage (no route
matches, so NotFoundPage renders).

Inline detail drawers and local row selection are still live on the
platform pages, so each test was judged on what it describes:

- Infrastructure inline detail: deleted. The Proxmox host rows run
  showGuests on click and only the chevron opens the node drawer, which
  has no reveal behaviour to pin.
- Recovery protected-item selection: deleted. There is no Recovery page,
  rollupId URL or history item filter left in the frontend.
- Workload reveal, workload keyboard open/close, storage pool reveal:
  ported to /proxmox/overview and /proxmox/storage with the current
  selectors (tr[data-guest-id], tr[data-summary-series-id],
  [data-inline-detail-for], the disclosure button's aria-controls).

The old assertions waited 350 ms and measured once, which cannot tell a
reveal that never ran from one that landed and then moved. The port
observes the row, its detail and the scroll shell on every animation
frame from before the click until the layout has settled and held, and
fails on a late second scroll, a close that moves the row or scrolls
the shell, any history write (even to the same URL), or a keyboard
toggle that does nothing. The storage test opens ?sort=host&order=asc
because the default Priority sort reorders pools on live utilisation.

Moves the spec from QUARANTINED_SPECS to PROBATION_SPECS. Probation
runs in the Core E2E workflow on pushes (not on pull requests) without
gating the verdict, and the spec earns promotion under the existing
10-run rule.

Checked on pulse-dev against a managed local backend with the CI-size
mock estate (50 nodes, 929 guests):
- unmodified spec, default harness timeouts: Chromium x6 repeats 18 of
  18 passed; all three CI projects passed with the mobile tests skipped
- negative controls on one build with the mutations switched by a
  user-agent marker: no reveal, hard-centred row, row pinned to the top,
  late jump, jump and return, selection written to the URL, close that
  jumps or blips, and a dead keyboard handler each fail the intended
  assertion, and the inert runs on the same build pass
- check-e2e-tiering.mjs: stable tier unchanged (57 files, 366
  instances), probation 42 -> 43 files and 555 -> 564 instances,
  quarantined 2 -> 1; a tree that leaves the spec in both tiers is
  rejected with the overlap error
2026-10-10 13:12:51 +01:00
pulse-triage[bot] 7e7a46244a Preserve notification edits and truthful monitoring recovery (#2768)
## What changed and why

- Preserve saved SMTP rate limits through unrelated edits and allow
settings edits while email remains switched off. Failed webhook saves
now retain the draft until the save owner acknowledges success; pending
saves prevent conflicting local edits. An uncertain create response
prompts checking saved destinations before a deliberate retry, not
automatic replay.
- Reject incomplete guest filesystem inventories as evidence of healthy
recovery or volume removal. Malformed replies could previously turn a
genuine 98% disk breach into 0%. Both collectors retain only the
previous complete observation within its original age bound; complete
ordinary readings can still report genuine zero and recovery.
- Replace root-password/implicit-TLS node API examples with scoped
platform tokens in a private file. Keep ICMP permission guidance and
diagnostics scoped to the selected observation host without reinstalling
or replacing privilege restrictions. Fresh-agent guidance distinguishes
configuration lookup authentication from host reporting and bootstrap
enrolment without rotating credentials or widening grants.
- Ensure failed E2E setup reaches owned teardown; admit shared fixtures
before either browser tier and retain completed stable-tier failure
reports before probation can time out.

These repairs respond to demonstrated save, filesystem-completeness,
copied-guidance and CI lifecycle failures in the existing product and
its tests. Related to #2439, #2619 and Discussion #2769. Source controls
and guidance do not establish those reports' causes, native recovery or
ordinary notification delivery. PBS password-onboarding trust is
separate.

## Included reviewed source

Complete unpublished range:
`9a32396fe2c806c6ed1856b74a8fa7a03f050984..fbbd353d6660400b80b7d9356b1ea5700702c38b`
(22 original non-merge commits and 15 additive merges).

- SMTP: `9734adad`, `27576b90`, `d25c56ad`, `73f87729`.
- Webhook acknowledgement: `84c815d7`, `fcaaabe2`, `c4fa4669`,
`731dff12`, containing composition `0c6c087a` and qualified test
correction `76244218`.
- Guest filesystem completeness: `615fa299`.
- E2E cleanup/admission: `bf1dbbf1`, `7027e7f7`, `5385cb09`, `f2a80606`.
- Node API/ICMP/authentication guidance: `299bddee`, `b65ff95f`,
`f8a33376`, `341de164`; matching ICMP diagnostic repair `5d8ea82d`.
- Integration qualification: private fixture creation `b4976947`;
original recipe-boundary correction `83adaf86` is retained in history,
with final test bytes taken from independently reviewed nine-request
catalogue repair `13206d2f` at integration `ec01ecf0`.

PR2768's original head `a33e5449` cannot land independently: Build and
Test run `38026567559` rejects the added ninth API recipe in four tests,
and CodeQL check `114138740587` flags the node test fixture's creation.
This update includes their additive repairs, without weakening either
gate or requesting an unchanged-head retry. All accepted identities and
ancestry, including original failure-bearing commits, remain intact.
Published upstream through `9a32396f` is incorporated additively by
`fbbd353d`, preserving the starting reviewed tip `ec01ecf0`. This
includes the Patrol contract, full drawer-identity improvements,
recovery-query trimming and alert-test cleanup. The bounded refresh
receipt records the exact upstream and unchanged pre-merge source.

## Validation and limits

Retained reviewed candidate evidence includes:
- Qualified webhook-containing frontend: 23,189 passing tests,
type/lint/build/budget checks, and parent-bound desktop/phone browser
proof. SMTP's earlier 23,183-test qualification and browser controls
remain attributed; three Windows runtime skips remain.
- Guest filesystem current/parent discrimination, affected normal/race
suites, vet and genuine embedded build. Recorded latency overruns, skips
and original incomplete reporting remain adverse or unexecuted, not
passed budgets.
- ICMP diagnostic: 32 focused passing identities, 11 parent failures
with 21 compatibility controls, full probe/agent normal/race suites, vet
and agent build; six native/systemd skips remain.
- Nine-request API catalogue: parent four failures; repaired whole
installtests package (501 methods, 14 skips), vet and focused
race/private-umask controls. Documentation and E2E candidates retain
their copied-command and real-process controls.

Publication provenance, whitespace, CI-equivalent per-commit canonical
completion checks and final browser-receipt coverage pass. Existing
browser-proved runtime bytes remain unchanged; eight final frontend
paths have in-range coverage.

Additional containing-source checks:
- `source-gfjfoexn` at starting reviewed head `ec01ecf0`: selected
API-guidance Go checks, eight node-recipe tests, sixteen
API-authentication documentation tests and API mirror comparison all
exit zero. Complete 2,803-byte output hash matches; guest ready and
stopped.
- Lightweight final guidance/workflow fixtures pass 4
agent-authentication, 6 ICMP and 19 E2E-image tests. The confined-host
missing-command diagnostic assertion failed (9/10 lifecycle controls);
both same-input disposable-VM executions pass all 22 lifecycle/readiness
controls. The host failure is retained, not declared passed or assigned
a speculative cause.
- After additive upstream composition, `source-lj_mx166` at exact final
head `fbbd353d`: all 22 lifecycle/readiness controls and all three
changed guide mirrors pass. Complete 7,977-byte output hash matches;
guest ready and stopped. The API, notification, E2E, ICMP and dependency
inputs proved at `ec01ecf0` are byte-identical at this composed head.

All three new VM receipts retain `line_bound_exceeded=true`; raw output
is nevertheless complete and independently hash-matched. This is not a
missing-output waiver or a claim to have rerun the full product graph.

Required hosted checks, including backend, CodeQL, frontend, governance
and E2E, must pass on this exact updated proposal before landing.
Previous failing checks are not cleared by local proof. Original
failed/partial proofs, skipped native controls and unavailable raw
evidence remain explicit. This proposal is source delivery, not release
availability, installed recovery or permission to change workloads or
privileges.


[How Pulse handles
triage](https://github.com/rcourtman/Pulse/blob/main/docs/AI_TRANSPARENCY.md)
2026-10-10 08:13:51 +00:00
rcourtman 8226d38836 Merge remote-tracking branch 'origin/main' into fix/assistant-protected-guests 2026-10-10 07:49:19 +01:00
rcourtman 1d6d2bfcae Remove the inert Assistant Protected guests setting
Settings > Assistant offered a Protected guests list (protected_guests)
that was saved and pushed into the Assistant tool executor, but its only
reader was executeControlGuest, which nothing calls. pulse_control plans
through the shared action lifecycle and never consulted the list, so a
guest named in it (by VMID and by name) was planned like any other. The
"legacy" label added in #2765 states that limit; this removes the
setting instead of keeping a field that cannot do its job.

The canonical way to keep Pulse off a resource is its operator lock
(Never auto-remediate, or a Retired lifecycle). It is keyed by canonical
resource id, applies to every platform, and the action lifecycle refuses
dispatch for it even for approved plans. The one gap was planning: the
lifecycle plans without reading operator state, so the Assistant could
leave a plan in Actions that can never run. assistantTypedActionPlanner
now reads the state through the tenant-scoped lifecycle store (the same
read Patrol's broker uses) and declines before Plan, and pulse_control
reports the block with how to clear it. A state that cannot be read does
not refuse planning, because dispatch re-checks.

Removed: AIConfig.ProtectedGuests and its getter, the settings API
field, the executor field/setter/config, the chat service plumbing, the
Settings field and form state, and the legacy copy, pins and prose from
#2765. A stored value still loads (tolerant decode) and is dropped the
next time the settings are written; older PUT bodies that send the key
are ignored. The Settings panel points at the guest's Manage tab.
2026-10-10 07:46:45 +01:00
pulse-triage[bot] 2cfcc73190 Integrate supplied upstream frontier without rewriting reviewed source
Change-source: pulse-maintainer
2026-10-10 07:40:49 +01:00
rcourtman 2a8a91646d Show drawer identity values whole instead of cutting them
The inline drawer is where a truncated table cell sends the reader for the
full value, but its own identity rows rendered as single-line truncating
spans. On a 390px phone the Primary ID, Parent, Hostname, Discovery and
Machine ID rows ended in an ellipsis, and the distinguishing part of these
values sits at the end: two pods of one ReplicaSet both read
"checkout-api-6c746d5bcf-..." and a Kubernetes parent read
"k8s-cluster-afcb623d93ff2...". The full value lived only in a title, and
phones cannot hover. Container image references lost their tag the same
way.

Docker label chips were hand-rolled inline-flex spans with truncate. An
inline-flex box draws no ellipsis, so long labels were cut with no sign at
all: 161px shown of up to 343px on a phone, and two chips were still cut
at 1440px.

Identity rows (the primary identity rows, Platform ID, Primary IP, image,
Podman pod and ID, Compose project and service) now use the detail row's
existing wrap mode, and label chips use MetadataBadge's wrap mode, so
operators can read, compare and copy the whole value. Descriptive rows
keep the single-line default. Measured across 83 drawers on a mock
estate, cut values in Docker and Kubernetes drawers went from 148 to 0 at
390px, 28 to 0 at 768px and 7 to 0 at 1440px; at 1440px only the rows
that were cut before changed height.

The mobile drawer spec now checks a Kubernetes pod drawer as well as a
container drawer, and fails any drawer value cell that ends in an
ellipsis, not only content cut past a clipping edge.
2026-10-10 07:04:16 +01:00
pulse-triage[bot] 5385cb0956 Fail shared E2E fixture admission before repeating browser setup
Main E2E 38020587365 exhausted twenty shared-readiness/login failures over 47 minutes, then probation hit the job limit before stable report uploads. Admit the same authenticated inventory and pool/disk history once before either tier, without retries or ignored failures, and upload completed stable failures before observational work. Share the existing readiness predicates and cover every identity/history requirement, actual spec cleanup and the workflow fail-fast command. Preserve phase thresholds, test tiers, pins and the stable verdict. This does not diagnose the hosted runtime failure.

Change-source: pulse-maintainer
2026-10-10 05:48:35 +01:00
pulse-triage[bot] 7027e7f754 Observe teardown even when E2E lock acquisition fails
Keep the actual parent control observable when its posttest is launched against a directory instead of a lock file. The fixture must record that unowned teardown rather than failing to log it before the assertion.

Change-source: pulse-maintainer
2026-10-10 05:09:00 +01:00
pulse-triage[bot] bf1dbbf1a3 Tear down failed E2E setup without clearing another run's handoff
The npm Playwright entrypoint exited inside setup and bypassed its finally block, leaving a partial managed runtime and verification lock behind. Keep the setup failure, skip tests, attempt existing teardown once, and release only the handoff this invocation wrote when its contents still match. Preserve primary failures through teardown errors and do not stop a runtime after lock preparation fails. Add real child-process controls to exhaustive script smoke; test tiers, acquisition deadlines and browser coverage are unchanged.

Change-source: pulse-maintainer
2026-10-10 05:08:02 +01:00
pulse-triage[bot] 440137890e Build Core E2E test images once per exact workflow run
Replace nine identical image build pairs with one same-run artifact producer. Admit the producer's manifest, checkout, run, attempt, archive and loaded image identities before starting each isolated stack. Keep fixed registry pins, local seed defaults, every journey gate and existing browser budgets. Provider refusals remain terminal without mirrors, login, pull retries or rebuild fallbacks. Record the contract and narrowly scoped regression proof.

Change-source: pulse-maintainer
2026-10-09 22:25:34 +01:00
rcourtman 1289728ded Point dev:verify at the Proxmox backups layout spec
The managed proof pack behind `npm run dev:verify` still named
tests/17-recovery-layout.spec.ts, which 1c38d79937 replaced with
tests/17-proxmox-backups-layout.spec.ts in July. The registered proof for
scripts/hot-dev-bg.sh pinned the stale path, so nothing flagged it.

The stale path did not fail the pack. Playwright treats each spec path as
a filter, so `test --list` with main's arguments reports 10 tests in 2
files and exits 0: the Proxmox backups layout guard had silently dropped
out of dev:verify.

The replacement spec reads the mock-mode estate, while dev:verify attaches
to the managed runtime in whatever mode it was left in. Under the managed
runtime it now skips with a `npm run mock:on` hint when mock mode is off,
instead of flipping modes in place: Monitor.SetMockMode clears the
runtime's active alerts and in-memory history on every switch. The Core
E2E harness runs mock mode and is unaffected.

test-hot-dev-bg.sh now pins the new spec and checks that every spec the
default verify command names exists under tests/integration; against
main's hot-dev-bg.sh that check fails on the retired path. The integration
README and its pin, the deployment-installability contract clause, and two
unused RECOVERY_PRODUCT_SURFACE_EXACT_FILES lists naming deleted files are
brought in line.
2026-10-08 09:56:53 +01:00
rcourtman b9c98a4a16 Stop Patrol fetching the circuit-breaker status nobody reads (#2704)
The Patrol page fetched `GET /api/ai/circuit/status` on every load,
every 60 s poll, every return to the tab, and again in the background
after Retry. The store's breaker state had one production reader: the
`circuitBreakerStatus` memo in `usePatrolIntelligenceState`, which
nothing read and which #2695 removes. An open breaker already reaches
the page through `/api/ai/patrol/status` as the `circuit_open` blocked
cause, and the paused banner shows its reason.

## Change

- `stores/aiIntelligence.ts`: remove the breaker signal, getter,
`circuitBreakerStatusSignal`, `loadCircuitBreakerStatus()`, its
org-switch reset, and its `loadDashboardData()` entry.
- `features/patrol/usePatrolIntelligenceState.ts`: drop the read from
the Retry path's background supporting reads.
- `api/ai.ts`: remove `AIAPI.getCircuitBreakerStatus` and the
`CircuitBreakerStatus` type; nothing else used them. The backend route
stays.
- Tests: the client's unit tests and the store and page test mocks go.
The store bundle test still pins the exact AIAPI read set. The breaker
route stubs are removed from seven E2E specs.
- Contracts: patrol-intelligence, ai-runtime, api-contracts and
frontend-primitives say the browser reads no breaker status and how an
open breaker still reaches Patrol. Each note sits next to #2677's
correlation note.

## Evidence

- Normalized production-build compare: only the removed store loader,
signal, getter and client differ in `index.js`, plus the removed
supporting read in `AIIntelligence.js`. `check-bundle-size.mjs` passes.
- Private demo-mode stacks for the parent and this tree, driven by the
same Playwright script at 1440x900 and 390x844. Across Patrol load, tab
hide/show and stale-data Retry, the breaker read drops from 1-2 requests
per phase to none. Every other endpoint matches, the Open in Assistant
handoff is unchanged, and neither build logs a console error.
- A forced `circuit_open` Patrol status still shows "Patrol paused /
circuit breaker is open" on both builds.
- `tsc --noEmit`, eslint, and vitest for `src/stores`, `src/api`,
`src/features/patrol`, `src/components/AI`, the Patrol page test and the
boundaries test pass, as does the full pre-commit battery.

Stacked on #2695 (which stacks on #2687) with #2677 merged in, so until
those land this PR shows their commits too. The 2677 owner agreed this
read belongs in a follow-up. `git merge-tree` is clean against main,
#2677, #2690, #2691 and #2693.
2026-10-08 03:05:15 +01:00
rcourtman 8d6f489d3e Wrap long identity chips inside phone drawers (#2731)
Below the lg breakpoint, the inline drawer's Identity section rendered
alias and IP chips as hand-rolled nowrap spans inside a fixed-layout
table cell. Generated aliases such as `proxmox-lxc-docker:Production
West:pve1:113` or `k8s-production-1:deployment:redis-ffcd-b17c78f012`
are wider than that value cell on a phone, so the cell's hidden overflow
and the drawer's clipping content shell cut them off with no ellipsis.
The part that distinguishes two aliases is at the end, and a phone has
no hover to show the title.

- `MetadataBadge` (the contract owner of read-only chip whitespace
handling) gains an opt-in `wrap` mode: the chip is capped at its
container and breaks inside a token only where no space or hyphen fits.
Default stays nowrap; existing consumers get byte-identical classes.
- The drawer's IP, alias and alias-count chips in
`ResourceDetailSummary.tsx` compose `MetadataBadge`; identity values
wrap in full. Desktop drawers are unchanged (one line per alias).
- The shared template registry requires the consumer and forbids the old
hand-rolled chip classes there; the guardrail and unit tests cover the
wrap classes.
- `04-mobile.spec.ts` opens a Docker container drawer at phone width and
fails if any Identity-section element or text crosses the detail cell,
table wrapper or drawer shell (values that end in their own ellipsis
excepted).

Measured on a private mock estate at 390px: 53 cut chips across 92
Docker/Kubernetes/Proxmox/Machines drawers before, 0 identity chips past
the shell across 140 drawers at 390 and 768px after. Rebased on main
after #2703 (wider phone drawers): the new spec passes on the Pixel 5
and iPhone 12 projects and fails with the two runtime files reverted
(`proxmox-lxc-docker:...:113` still 71px past the cell).

Correlation-summary and redaction-label chips were checked and left
alone: redaction labels are a fixed five-word vocabulary, and the
correlation chips sit outside the nowrap table and break at spaces and
hyphens.
2026-10-08 02:37:34 +01:00
rcourtman 59cc3ce10b Wrap long identity chips inside phone drawers
Below the lg breakpoint, the inline drawer's Identity section rendered
alias and IP chips as hand-rolled nowrap spans inside a fixed-layout
table cell. A generated alias such as
proxmox-lxc-docker:Production West:pve1:113 or
k8s-production-1:deployment:redis-ffcd-b17c78f012 is wider than that
value cell on a phone, so the cell's hidden overflow and the drawer's
clipping content shell cut it off with no ellipsis. On a 390px mock
estate 53 chips across Docker and Kubernetes drawers were cut. The part
that tells two aliases apart sits at the end, and a phone has no hover
to show the title.

MetadataBadge owns read-only chip whitespace handling, so it gains an
opt-in wrap mode that caps the chip at its container and breaks inside
a token only where no space or hyphen fits. The drawer's IP, alias and
alias-count chips now compose MetadataBadge, and the identity values
wrap in full. The default stays nowrap, so existing badge consumers are
unchanged, and desktop drawers still show each alias on one line.

The shared template registry now requires ResourceDetailSummary to
compose MetadataBadge and forbids the old hand-rolled chip classes
there. A mobile integration check opens a Docker container drawer and
fails if any Identity-section element or text crosses the detail cell,
table wrapper or drawer shell; it fails on the old code and passes with
this change.
2026-10-08 00:17:01 +01:00
rcourtman 4292c180b2 Retire the summary hover E2E spec pinned to removed surfaces
The quarantined 48-summary-hover-selection spec never ran, and none of
its nine tests could pass against the current app. Each one opens
/workloads or /storage, which no route matches since the platform-first
IA, or /infrastructure, which is now only an entry route that redirects
to the first platform page, and then waits for the workloads,
infrastructure or storage summary cards that were deleted with the
standalone surfaces.

The storage pool-group pin test was the only candidate for a port to
/proxmox/storage, but it asserts behaviour that page does not have. A
group-header click there collapses the group as it pins it, so the
member rows the test expects to stay visible and pinned are hidden, and
there are no summary charts left for the pin to rescope. The live pin
(hover preview, the summaryGroup URL param, pinned member emphasis
after expanding, Clear selection) is already covered in Storage.test.tsx.

Deleting the file takes its QUARANTINED_SPECS entry with it, because
the tier check rejects ledger entries for specs that do not exist.
2026-10-07 23:47:38 +01:00
rcourtman 8b18ec2efb Stop Patrol fetching the circuit-breaker status nobody reads
The Patrol page fetched GET /api/ai/circuit/status on every load, every
60 s poll, every return to the tab, and again in the background after
Retry. Its only reader was the circuitBreakerStatus memo in
usePatrolIntelligenceState, which nothing read and which the previous
commit removed. Nothing else renders breaker state: an open breaker
already reaches the page through /api/ai/patrol/status as the
circuit_open blocked cause, and the paused banner shows its reason.

Remove the store's breaker signal, getter, signal export, loader,
org-switch reset and dashboard-bundle entry, the hook's background read,
and AIAPI.getCircuitBreakerStatus with its CircuitBreakerStatus type.
The client's unit tests, the store and page test mocks, and seven E2E
route stubs go with them. The store bundle test still pins the exact
AIAPI read set. The backend route stays for API users and diagnostics.

Production builds before and after differ only in the removed code
(check-bundle-size passes). A Playwright pass on private demo stacks at
1440 and 390 px shows the breaker read going from 1-2 requests per
phase to none across Patrol load, tab hide/show and stale-data Retry,
with every other endpoint and the Assistant handoff unchanged. A forced
circuit_open Patrol status still shows "Patrol paused / circuit breaker
is open" on both builds.
2026-10-07 17:50:51 +01:00
rcourtman 6d8e2c9e28 Stop Patrol fetching the correlation list nobody reads
The Patrol page fetched GET /api/ai/intelligence/correlations on every
mount, every 60 s poll, every return to the tab and on Retry, and the
store's dashboard bundle fetched it again for FindingsPanel's refresh.
Nothing has read that list since the Patrol supporting-context block was
removed in 27ba85ddc6; the resource drawer reads per-resource
correlations from the resource-intelligence payload instead.

Remove the store's correlation signal, getter and loader, drop the read
from the Patrol background refresh and the dashboard bundle, and remove
AIAPI.getCorrelations, which had no other caller. The backend route
stays as an API surface. Dead route stubs go from six E2E specs, and
contract prose that said the Patrol page loads or renders correlations
or recent changes is corrected.

Measured on a demo-mode stack: correlation requests fall from one to
zero per Patrol load, reload, 60 s poll and tab return at 1440x900 and
390x844, with every other AI read unchanged. Production bundle JS gzip
drops 0.18 kB with no chunk renamed.
2026-10-07 15:17:11 +01:00
rcourtman 2265d28242 Remove the never-mounted unified resource table
PlatformResourceTable was the only renderer of UnifiedResourceTable, and
nothing imports it. Every platform page (Proxmox, Docker, Kubernetes,
TrueNAS, vSphere, Machines) renders its own native tables, and
/infrastructure only shows the opening-workspace placeholder. A
production build of main already tree-shook the whole cluster (none of
its strings ship), yet it still carried 11 source files, 7 test files,
registry ownership in two subsystems and contract rules for a table
nobody sees.

Removed: PlatformResourceTable and its PLATFORM_STATUS_FILTER_OPTIONS;
UnifiedResourceTable with its host, service, PBS and PMG cards, source
badge cell, state hook, viewport sync and models; and
ClusterDeployBanner, which only the host card rendered (the live cluster
deploy flow is the Settings ClusterAgentDeployDialog). Their tests go
with them.

Coverage for live code those tests also exercised is kept: the detail
mapper no-fetch boundary and the online boolean sensor case move to
resourceDetailMappers.test.ts, the ResourceFacetSummary render cases
move to their own test file, and both files become registered proofs for
the policies that govern their subjects. Guardrail tests, the shared
template registry, registry.json, status.json evidence and the
performance, unified-resources, frontend-primitives and cloud-paid
contracts drop the deleted paths and the rules that only governed them.

Rollup used to name the large shared chunk after sharedPlatformPage; it
now names it MetricBar, so the bundle baseline entry is re-keyed at its
measured size.
2026-10-06 20:34:35 +01:00
rcourtman 7f83d73cfd Tighten the drawer span tests
The unit test only showed that a width write happened, so a doubled
nudge or a missing forced layout read would still pass. It now spies on
offsetWidth and requires exactly one forced read of the detail cell, at
width 0px with the new colspan, when the requested span and the summary
cells grow in the same update, and none when they shrink.

The browser spec read every `thead th` under the table, which would also
match headers of tables inside the drawer. It now reads only the outer
table's own header row, and uses the frontend Prettier settings (single
quotes, 100 columns) instead of Prettier's defaults.
2026-10-06 15:13:08 +01:00
rcourtman 0bd9064513 Add a browser check for drawer span after column toggles
The unit tests cannot see layout, so a regression in the Chromium width
workaround would only show in a real browser. The new spec opens a
Machines drawer, hides and restores System through View > Columns, and
checks after each step that the drawer spans the visible headers, that
the drawer, last header and table share a right edge, and that the
drawer row, focus and any inner scroll position survive.

It fails on main before the fix (colspan 8 against 7 after hiding) and
with only the width workaround removed (drawer 79px short of the table
edge after showing), and passed five runs in a row with the fix.
2026-10-06 14:39:09 +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