Commit Graph

7665 Commits

Author SHA1 Message Date
pulse-triage[bot] 76da9cfbba Prepare v6.6.0
Promote v6.6.0-rc.6 to stable after its 24-hour soak. Only release metadata changes.

Contract-Neutral: Release-only v6.6.0 version, release notes and distribution metadata; no public install or runtime contract changes.
Change-source: pulse-maintainer
2026-10-25 08:39:03 +00:00
pulse-triage[bot] f54647d7e9 Prepare v6.6.0-rc.6
Release candidate cut from main at 8f111cd43f. It carries every change merged since stable v6.5.0.

Contract-Neutral: Release-only v6.6.0-rc.6 version, release notes and distribution metadata; no public install or runtime contract changes.
Change-source: pulse-maintainer
2026-10-24 05:19:48 +01:00
pulse-triage[bot] 8f111cd43f Keep SSO recovery feedback in the active editor (#3172)
**Keep SSO recovery feedback in the active editor**

Keep refused saves, clipboard outcomes and metadata failures beside
reachable editor actions. Preserve exact spaced Allowed Groups on no-op
and mapping-only saves for OIDC and SAML, without changing scope,
domain, email or secret-omission semantics. Keep retained notifications
below blocking dialogs while preserving inertness. Cover
correction/reopen, pending submit, clipboard refusal and modal
restoration through normal suites and source-bound desktop/phone browser
evidence.

**Align the SSO ownership guard with inline connection results**

Retain canonical result presentation and failure formatting without
requiring the background connection-test toast that the editor
intentionally no longer emits. The full frontend run exposed this stale
architecture assertion; normal UI tests already cover active-dialog
feedback and no duplicate toast.

**Keep modal failure notifications above their dialogs**

Lower only success-confirmation-only stacks; existing delete, role and
assignment dialogs still rely on foreground error, warning and
information feedback. Preserve SSO inline recovery, retained-success
reopening and exact spaced access groups. Keep the SSO header as a
non-scrolling flex sibling instead of redundant sticky positioning.

Add real provider save/reopen/delete consumption and severity
regressions, with desktop and 320px native pixel proof bound to this
parent. The earlier heading discrepancy remains undiagnosed; all
selected candidate frames are readable.

---
Fleet candidate `20261024T021747Z-web-product` from the `web-product`
lane. Reviewed tip `4b74dbd5f9da47557958baa762bda89f6c48ac52` on `main`.

[How Pulse handles
triage](https://github.com/rcourtman/Pulse/blob/main/docs/AI_TRANSPARENCY.md)
2026-10-24 03:12:15 +00:00
pulse-triage[bot] 3f5dde6218 Keep mock memory personas below physical capacity (#3170)
Bound the shared role-adjusted memory range before point and series
sampling, leaving cache/free headroom rather than clipping only one
generated host. Preserve ordinary ranges, Docker CPU semantics and the
original fixture assertions. Register fixed-week sampler and host
lifecycle accounting regressions.

---
Fleet candidate `20261024T015631Z-core-runtime` from the `core-runtime`
lane. Reviewed tip `10dd31b3dc2686e8b64645a83be2088d1a4ef059` on `main`.

[How Pulse handles
triage](https://github.com/rcourtman/Pulse/blob/main/docs/AI_TRANSPARENCY.md)
2026-10-24 02:56:50 +00:00
pulse-triage[bot] 4b74dbd5f9 Keep modal failure notifications above their dialogs
Lower only success-confirmation-only stacks; existing delete, role and assignment dialogs still rely on foreground error, warning and information feedback. Preserve SSO inline recovery, retained-success reopening and exact spaced access groups. Keep the SSO header as a non-scrolling flex sibling instead of redundant sticky positioning.

Add real provider save/reopen/delete consumption and severity regressions, with desktop and 320px native pixel proof bound to this parent. The earlier heading discrepancy remains undiagnosed; all selected candidate frames are readable.

Change-source: pulse-maintainer
2026-10-24 03:26:45 +01:00
pulse-triage[bot] f19424f51a Complete credited chart evidence reporting on current main (#3169)
Isolate the retained PR3095 completion (7f4ec192 and 93d87118) without
importing unrelated history. Keep the exact proposed Python classifier
and maintained chart shells, real CLI/workflow controls and dependent
contracts. Inconclusive capacity never establishes the 90 ms budget;
child, cleanup and malformed-proof failures remain errors.

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

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

[How Pulse handles
triage](https://github.com/rcourtman/Pulse/blob/main/docs/AI_TRANSPARENCY.md)
2026-10-24 02:26:12 +00:00
pulse-triage[bot] 518fda75b3 Retain the SSO recovery candidate on current main
Compose the original implementation and test correction without reparenting; an additive follow-up will preserve modal-only error notifications.

Change-source: pulse-maintainer
2026-10-24 03:18:33 +01:00
pulse-triage[bot] 10dd31b3dc Keep mock memory personas below physical capacity
Bound the shared role-adjusted memory range before point and series sampling, leaving cache/free headroom rather than clipping only one generated host. Preserve ordinary ranges, Docker CPU semantics and the original fixture assertions. Register fixed-week sampler and host lifecycle accounting regressions.

Change-source: pulse-maintainer
2026-10-24 03:01:45 +01:00
pulse-triage[bot] f1567d6e97 Isolate guest fixtures and safely observe pinned loser closure (#3168)
**Isolate guest fixtures and safely observe pinned loser closure**

Keep typed guest-agent cases on the existing namespaced fixture identity
so recycled loopback ports cannot inherit a previous synthetic guest
cooldown. Add a forced shared-listener regression without clearing the
production admission registry.

Set the pinned loser peer deadline before cancellation can close the
pipe, exercise the already-closed ordering deterministically, and verify
that the winning connection stays usable. No runtime, transport policy,
cooldown or admission behaviour changes.

Contract-Neutral: Repository-run test fixture isolation and closure
observation only; production guest and pinned-dial contracts are
unchanged.

**Read native TrueNAS SCALE aggregate CPU usage, including idle zero**

The reporting.realtime payload in #3158 places the actual aggregate at
fields.cpu.cpu.usage alongside per-core entries. Accept that reading
without treating temperature, absent/null usage or a core average as a
sample. Existing flat and nested usage/total shapes keep their
precedence.

Cover parser presence, legacy compatibility and repeated real TLS
JSON-RPC subscription through provider/registry/wire projection, with
unavailable and recovery controls. Update the monitoring contract in the
same commit.

---
Fleet candidate `20261024T011100Z-core-runtime` from the `core-runtime`
lane. Reviewed tip `5a852abe6695992d72f7600f44bd870e4de7f710` on `main`.

[How Pulse handles
triage](https://github.com/rcourtman/Pulse/blob/main/docs/AI_TRANSPARENCY.md)
2026-10-24 01:54:37 +00:00
pulse-triage[bot] 21db610f6f Detach the Unraid boot entry so later go lines run (#3162)
## Problem

On Unraid, `scripts/install.sh` appended this to `/boot/config/go`:

```
# Pulse Agent
bash /boot/config/plugins/pulse-agent/start-pulse-agent.sh
```

`start-pulse-agent.sh` is an endless watchdog loop, so that line never
returns. Unraid's `rc.local` runs `go` (as `/var/tmp/go`, after
`fromdos`) to completion, so every line after the Pulse block never
runs. On a maintainer host, a reboot on 21 Oct skipped swap, a memory
logger and a hosts entry the agent needed to reach its Pulse server.
Pulse showed the host as down for two days while it was fine.

The same block's cleanup, `sed -i '/pulse-agent/d'`, deleted every `go`
line that mentioned `pulse-agent`, including log directories, hosts
entries and a co-installed agent's launch.

## Change

- `render_unraid_go_launch` writes the entry detached: `bash <wrapper>
>> /var/log/pulse-agent.log 2>&1 &`, with both paths `%q`-quoted. Plain
`&` rather than `setsid -f`, which needs util-linux 2.32+ and so may be
missing on older Unraid releases. Redirecting the output means `go`
holds no boot output pipe open.
- `write_unraid_go_entry` removes only the `# Pulse Agent` marker and
command lines that name a `start-pulse-agent.sh` wrapper or this runtime
binary outside a comment. That covers the earlier foreground entry,
hand-edited detached copies (including ones glued to a redirect) and
old-location wrappers, so an upgrade leaves exactly one launch. Other
lines that mention pulse-agent, comments and co-installed agents stay. A
trailing comment is only set aside when the scan is certain it is one. A
backslash, any `$` expansion, a backquote or an unterminated quote
before it makes the line doubtful, and a doubtful line is still checked
for a launch, because leaving a launch behind is the worse failure.
"Names" means the file name appears as a whole word: only a neighbouring
file-name character (letter, digit, `.`, `_`, `-`) rules a match out, so
`w=start-pulse-agent.sh` and `…start-pulse-agent.sh${x:+}` count. A
launch reached only indirectly, such as a relative path after `cd`, is
not detected. A name continued by another character (for example an
editor backup, `pulse-agent~`) still counts as the file, as it did under
the old sed.
- Kept lines lose a trailing CR, as `fromdos` does at boot anyway, so
the removal filter can parse the file afterwards. If removing a line
would leave `go` unparseable (for example, the line opened an `if`), the
writer leaves `go` untouched and the installer prints the line to use.
- `filter_nas_agent_boot_file` (uninstall) admits the detached entry as
well as the earlier foreground form, so removal keeps working for new
and existing installs.
- No other non-init platform had this problem: QNAP's autorun block is
already backgrounded, and TrueNAS, Synology, OpenRC, rc.d and SysV hand
off to a service manager or background the process.

## Tests

- `TestInstallSHUnraidGoEntryDoesNotBlockLaterBootLines` executes the
`go` file the installer writes, with output on a pipe. It requires `go`
to return, release the pipe, run the line after the Pulse entry and
start the wrapper. A planted foreground entry has to hang the same
harness. This replaces `TestUnraidGoScriptCleanup`, which re-typed the
sed commands inline and so could not catch this.
- `TestInstallSHUnraidGoEntryReplacesOnlyPulseLaunches` runs the real
writer against legacy, hand-edited, CRLF, old-location and direct-binary
entries, a command whose comment names the wrapper, and a removal that
would break an `if`. It checks the kept lines, that a second run changes
nothing, and that the removal filter admits what was written.
- `agent_nas_teardown.py` renders its Unraid fixture through the real
emitter, as it already does for QNAP. It keeps an earlier-installer
entry in the duplicate case. All 23 controls pass.
- Mutation checks on pulse-dev: each removal fails its test.
  - The foreground line fails with "go did not return".
- Backgrounding without a redirect fails with "kept the boot output pipe
open".
- The blanket sed, the syntax guard and its CRLF normalization, the `<>`
boundary, CR stripping, comment stripping, and quote- and escape-aware
comment detection each fail their own case.
- The writer gives byte-identical output under gawk, mawk and busybox
awk. The full `scripts/installtests` package passes on pulse-dev.

These are private bash fixtures. This is not native Unraid boot
acceptance.

## Follow-ups, not in this PR

- `read_nas_agent_removal_processes` treats a live `/boot/config/go`
shell as an unknown boot owner. Unraid actually runs `/var/tmp/go`, so
on hosts installed before this fix, uninstall does not recognise the
stuck go shell.
- The removal filter refuses any command line that names the wrapper,
even in a trailing comment. The installer now keeps such lines.
- `remove_qnap_autorun_block` still deletes every `autorun.sh` line
containing `start-pulse-agent.sh`.
2026-10-24 02:51:42 +01:00
pulse-triage[bot] ac36a9a94c Keep SSO recovery feedback in the active editor
Keep refused saves, clipboard outcomes and metadata failures beside reachable editor actions. Preserve exact spaced Allowed Groups on no-op and mapping-only saves for OIDC and SAML, without changing scope, domain, email or secret-omission semantics. Keep retained notifications below blocking dialogs while preserving inertness. Cover correction/reopen, pending submit, clipboard refusal and modal restoration through normal suites and source-bound desktop/phone browser evidence.

Change-source: pulse-maintainer
2026-10-24 02:37:38 +01:00
pulse-triage[bot] 5a852abe66 Compose native SCALE CPU repair with current main
Retain main's guest-agent fixture isolation in the two overlapping hunks, the deterministic recycled-listener regression, and main's additional per-field fixture fix. Import the unchanged native aggregate CPU repair and pinned loser observation correction. No production guest safeguards change.

Change-source: pulse-maintainer
2026-10-24 02:12:05 +01:00
pulse-triage[bot] 7e7cea87a6 Reconcile chart reporting with current Patrol contracts
Compose retained 8478eac3 additively on current main. Preserve both independently appended contract sections at each of the three documentation conflicts; workflow, registered executable controls and classifier stay unchanged.

Change-source: pulse-maintainer
2026-10-24 02:02:24 +01:00
pulse-triage[bot] 6ab763c2e6 Keep revised secondary issue topics in the review queue
The existing synchronizer loses appended, replaced or revised topics after an earlier disposition because both field classifications remain true. Compare the current declared text while preserving quiet label deltas, Community ownership and stale-event/pre-write checks. Add registered regression controls, keep the served guide aligned, and record desktop/320px proof through the real documentation consumer.

Contract-Neutral: Repair the existing issue-topic queue and its guidance; no Pulse runtime, API, label ownership, navigation or subsystem contract change.
Change-source: pulse-maintainer
2026-10-24 01:59:27 +01:00
rcourtman 871d94f53d Serialize Patrol action projection and finalize hung runs (#3151)
Three gaps remained in how Pulse projects a Patrol investigation's
action and closes its runs, after rcourtman/Pulse#3147.

- **Projections are serialized per investigation.** The lifecycle's
transition callback, an investigation read and a run's close each read
the action audit and apply it under the investigation's lock. A run's
close also finalizes before releasing it. A projection read from an
older state can no longer land after a newer one, and an approval can no
longer land between a run's reconciliation and its finalization.
- **The projection writes only what changed.** It records the changed
action and outcome through `SetAction` and `SetOutcome` when the store
offers `aicontracts.InvestigationActionStore` (a new optional interface;
a store without it keeps the old `Update`). It therefore cannot undo run
progress the orchestrator saved in between. The reference it writes
carries the broker's canonical result and verification status
(`dispositionFromRecord`) and keeps the model's causal attribution,
which the audit does not hold.
- **Runs that hang past their limit are finalized.** The stuck-run sweep
finalizes such a run with a copy of its session marked failed, leaving
the orchestrator's own session as it is. Once the orchestrator closes
the run itself, its stored session is finalized instead of the copy, and
a later refresh no longer reopens a record that finalization closed.
- **A recovered run keeps its own status** (second commit). The sweep
used to mark a run that a restart or its limit cut short after it
planned a fix as `completed`, because the finding already held the
action's outcome, while its session and record said `failed`. The
orchestrator records the same run as `failed` when it closes itself, and
every gate against a second plan keys on the `fix_queued` outcome, so
the sweep now keeps the outcome and the run's own status.

The matching pulse-enterprise change makes the orchestrator's saves keep
the action Pulse recorded, and gives its store `SetAction`. Each side
works without the other.

Testing
- New tests cover the per-investigation lock, field-only writes,
hydration returning the stored action after a refresh replaced the plan,
a close preferring the orchestrator's own close over the sweep's copy
and finalizing under the lock, a run past its limit finalized as given
up, a refresh not reopening a finalized record, the full action
reference, and a recovered run keeping the status it closed with.
Removing any one of the fixes fails its test.
- On pulse-dev: gofmt, vet, race runs of the targeted tests, and full
runs of `internal/ai/...`, `internal/api`, `internal/actionlifecycle`,
`internal/unifiedresources/...` and `pkg/aicontracts/...`. The registry,
contract, completion and browser guards pass on both commits.
- Live, on a private Pro stack built from this branch and
pulse-enterprise main, with a real model (Claude Sonnet through the
subscription CLI) investigating a Docker container whose web server had
died. In each run the model planned a container restart through
`patrol_propose_action`, and the next model turn was made to fail:
- **Provider error after the plan.** The run closed failed with the
error, `fix_queued` and the pending restart on the session, the finding
and its record. Re-investigation returned 409 `action_pending`, the
restart waited in Actions, and the finding's review showed the queued
fix with its plan. When the plan expired, the sweep published it, and
the session, finding and record moved to `needs_attention` with the
action expired.
- **Deadline after the plan.** With a 300-second limit and the next turn
hung, the run failed at the deadline with `fix_queued`, not `timed_out`,
and the next Patrol pass did not retry it.
- **Restart after the plan.** Pulse was killed mid-run. On boot the
stored session closed as interrupted with the plan kept,
re-investigation still returned 409, and once the grace passed a Patrol
pass recovered and finalized the run. With the second commit the finding
reads `failed`, matching its session and record; before it, it read
`completed`.
- Not observable on a local stack: the approval push, which needs a
relay client, and a run hanging past its limit without closing, since
the provider call is cancelled at the deadline. Unit tests cover both.
2026-10-24 01:39:10 +01:00
pulse-triage[bot] 72c5579c55 Deliver the credited VMware refresh budget on current main (#3160)
Isolate Core's completed 7b633c75 repair from its obsolete containing
history. Preserve the contributor's twenty-second default and positive
whole-refresh override without logging raw invalid environment input,
with registered lifecycle, cancellation, cached-inventory and
serial-dispatch controls plus configuration guidance. Current VMware
runtime and test files still match the original parent exactly; preserve
all later unrelated contracts and documentation.

Original-contribution: https://github.com/rcourtman/Pulse/pull/3042

---
Fleet candidate `20261023T224436Z-delivery-trust` from the
`delivery-trust` lane. Reviewed tip
`00c1f53272243d1ddb6911a898212524c82d2495` on `main`.

[How Pulse handles
triage](https://github.com/rcourtman/Pulse/blob/main/docs/AI_TRANSPARENCY.md)
2026-10-24 00:13:49 +00:00
pulse-triage[bot] e14229c4d6 Read native TrueNAS SCALE aggregate CPU usage, including idle zero
The reporting.realtime payload in #3158 places the actual aggregate at fields.cpu.cpu.usage alongside per-core entries. Accept that reading without treating temperature, absent/null usage or a core average as a sample. Existing flat and nested usage/total shapes keep their precedence.

Cover parser presence, legacy compatibility and repeated real TLS JSON-RPC subscription through provider/registry/wire projection, with unavailable and recovery controls. Update the monitoring contract in the same commit.

Change-source: pulse-maintainer
2026-10-24 00:59:18 +01:00
rcourtman 0ff9b26fb1 Keep a recovered Patrol run's own status
The stuck-run sweep closed a run that a restart or its limit cut short
after it planned a fix as completed, because the finding already held
the action's outcome. That run's session and record say failed, and
the orchestrator records the same run as failed when it closes itself,
so the sweep path disagreed with every other surface about one event.

The coercion kept a queued fix blocking another investigation. That no
longer needs the status: Patrol's own gate, the orchestrator's re-run
check and the API's re-investigate check all key on the fix_queued
outcome or a running status. Recovery now keeps the action's outcome
and the status the run closed with.

Found in a live run on a private Pro stack, where a real model planned
a container restart and Pulse was killed before the run finished.
2026-10-24 00:41:23 +01:00
rcourtman 4f3e170612 Detach the Unraid boot entry so later go lines run
The Unraid installer appended `bash <wrapper>` to /boot/config/go. The
wrapper is an endless watchdog loop, so that line never returned, and
rc.local never reached anything the user had placed after the Pulse
block. On a maintainer host a reboot skipped swap, a memory logger and a
hosts entry the agent needed to reach its server, and Pulse showed the
host as down for two days while it was healthy.

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

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

Source-PR: https://github.com/rcourtman/Pulse/pull/3095
Source-Commit: b31fabd778
Co-authored-by: rcourtman <8825017+rcourtman@users.noreply.github.com>
Change-source: pulse-maintainer
2026-10-24 00:26:11 +01:00
pulse-triage[bot] fe06bf7f83 Keep failed PBS namespace discovery out of complete backup evidence (#3157)
**Keep failed PBS namespace discovery out of complete backup evidence**

Carry namespace discovery errors with the immutable datastore snapshot
into background backup polling. Readable root history still updates, but
unknown child namespaces cannot delete durable facts or claim complete
protection. Cover real HTTP discovery through ingest, replacement state,
repeated failures, scoped removal, recovery and legacy root-only
support.

**Initialize the connection outcome map in namespace poll regressions**

The fixture drives the real connection poll and therefore needs its
ordinary poll-status map, in addition to backup scheduling and recovery
state. Keep the new lifecycle proof independent of fixture setup panics.

**Account for accepted new archives beside retained partial PBS
history**

The namespace lifecycle test advances the root archive timestamp. A
partial instance-wide scan must retain the two historical points and
accept that new root point, not replace all root history. Assert every
historical point remains byte-identical and exactly one independently
accepted new point is present.

**Distinguish failed PBS discovery from an empty backup scope**

Carry failed and accepted-empty discovery through ordinary serialized
backup polling. Preserve unreadable history, reconcile only confirmed
absence, and reject malformed datastore envelopes before destructive
reconciliation.

Contract-Neutral: Enforces existing PBS scope-evidence semantics; no
API, model, policy or permission change.

**Model an existing PBS interval in the skipped-scope regression**

The live interval setter deliberately schedules a catch-up poll. Test
not-due admission with an already-configured interval and its retained
completed-poll timestamp instead.

---
Fleet candidate `20261023T220242Z-core-runtime` from the `core-runtime`
lane. Reviewed tip `d1ecdcb93916f236f966897230541a66850fe3a5` on `main`.

[How Pulse handles
triage](https://github.com/rcourtman/Pulse/blob/main/docs/AI_TRANSPARENCY.md)
2026-10-23 23:18:11 +00:00
pulse-triage[bot] 7e2dcbbe36 Deliver the credited MCP boolean-filter repair on current main (#3156)
Reconstitute the exact runtime and executable proof from Core 4651beb4
without importing its broad unlanded history. The four affected source
files still match that fix parent byte-for-byte. Preserve current
subsystem contracts while inserting the existing typed-filter
obligations.
Boolean true and false survive MCP HTTP projection; path and private
metadata, supported value types, write bodies and authentication stay
bounded.

Original-contribution: https://github.com/rcourtman/Pulse/pull/3097
Reported-by: X1pheR (Pulse PR #3097)
Source-Commit: 4651beb4c8

---
Fleet candidate `20261023T215543Z-delivery-trust` from the
`delivery-trust` lane. Reviewed tip
`4081635115b8db4ae249977d8ef3e78ab44a1073` on `main`.

[How Pulse handles
triage](https://github.com/rcourtman/Pulse/blob/main/docs/AI_TRANSPARENCY.md)
2026-10-23 23:10:01 +00:00
pulse-triage[bot] 00c1f53272 Deliver the credited VMware refresh budget on current main
Isolate Core's completed 7b633c75 repair from its obsolete containing history. Preserve the contributor's twenty-second default and positive whole-refresh override without logging raw invalid environment input, with registered lifecycle, cancellation, cached-inventory and serial-dispatch controls plus configuration guidance. Current VMware runtime and test files still match the original parent exactly; preserve all later unrelated contracts and documentation.

Original-contribution: https://github.com/rcourtman/Pulse/pull/3042
Change-source: pulse-maintainer
2026-10-23 23:48:47 +01:00
pulse-triage[bot] 4081635115 Deliver the credited MCP boolean-filter repair on current main
Reconstitute the exact runtime and executable proof from Core 4651beb4
without importing its broad unlanded history. The four affected source
files still match that fix parent byte-for-byte. Preserve current
subsystem contracts while inserting the existing typed-filter obligations.
Boolean true and false survive MCP HTTP projection; path and private
metadata, supported value types, write bodies and authentication stay bounded.

Original-contribution: https://github.com/rcourtman/Pulse/pull/3097
Reported-by: X1pheR (Pulse PR #3097)
Source-Commit: 4651beb4c8
Change-source: pulse-maintainer
2026-10-23 23:37:42 +01:00
rcourtman acd353f30f Keep Patrol finding reviews in place across refreshes (#3154)
## What was wrong

On the Patrol page's Activity tab (Finding options and history), an open
finding review threw the reader back to the top of the page:

- on opening the review (1389px to 0 at 1024x768),
- on opening its investigation thread,
- on Re-investigate and on each 5-second poll of a running
investigation,
- on every 30s app-shell and 60s Patrol-page findings reload (a reader
at 2715px dropped to 0 at about 25s and again at 55s).

Each reload parsed new finding objects, so `FindingsPanel` rebuilt every
row and remounted the open review. The review read its investigation
with `createResource`, and the nearest `<Suspense>` above it is the
route boundary in `AppLayout.tsx`. Its "Loading view..." fallback
replaced the whole page, so the scroll shell's content collapsed and its
position clamped to 0. The rebuild also closed the review's thread and
reset its inner scroll. Separately, the API returns tied findings in no
fixed order, so rows swapped places between polls.

## What changed

- **Findings store:** a read that returns a finding unchanged keeps its
object, and a read that changes nothing keeps the list.
- **FindingsPanel:**
- keeps display groups whose findings did not change
(`reuseIdenticalPatrolFindingDisplayGroups`);
  - breaks sort ties by finding id;
- passes the open review its related findings through a getter, so it
redraws only when its own finding changes;
- reads row and review ages from the shared relative-time clock (they
had only moved because rows were rebuilt).
- **InvestigationSection, ApprovalSection, InvestigationMessages:** read
through `createNonSuspendingQuery` instead of `createResource`.
Refreshes no longer remount them, so they re-read on their own:
- the investigation and its governed action every 30s, and the
investigation every 5s while it runs;
- the thread every 5s while the run is live, once more when it finishes,
and 30s after a failed read.

A failed read keeps the last answer; only a 404 means "no
investigation". Re-investigate waits for the first read. The thread is
keyed by the run's session and keeps unchanged message rows, along with
any expanded tool output.
- **`createNonSuspendingQuery`:** `pollMs` also accepts an accessor, so
a cadence can follow state through the hook's protected poll path, which
never replaces a slow read still in flight. The numeric path is
unchanged.
- **RunToolCallTrace:** moves to the same query. It did not suspend
before (its reads checked `loading` first), so this is for consistency,
not a fix.
- **Contracts:** notes in `patrol-intelligence.md` (Forbidden Paths 6
and a Current State note) and `frontend-primitives.md`.

## Verification

- **Browser.** Private Pulse Pro stack (enterprise binary built from
main, seeded Patrol findings and investigation sessions), headless
Chromium at 1024x768, 1440x900 and 390x844:
  - Opening a review keeps the page where it was.
- Over 70s with the review open (two 30s reloads, one 60s reload and a
30s re-read of the investigation) the page stays put: no fallback, no
row or review content rebuilt, rows in the same order, the thread still
open and the review's inner scroll unchanged.
  - A 150-second watch with unchanged findings moved no row.
- Main at 1024x768: 1389px to 0 on open, 2715px to 0 at about 25s and
55s, and the row and review content rebuilt three times in 70s.
- Receipt: `frontend-modern/browser-verification/14b0b05ccd7e4c6f.json`.
- **Tests.**
- New `FindingsPanel.refresh.test.tsx` drives the real store, panel and
review sections under a Suspense boundary.
- Also adds to the store, display-group, hook and four component tests.
- Negative controls: every new test fails against main's runtime files.
Reverting any one layer (store, panel or sections) fails the refresh
test. Main's hook fails the cadence, retry and live-thread tests.
Removing the message reuse or the final thread read fails their tests.
- **Checks.**
- Targeted suites (60 files, 1267 tests), type-check, the full `npm run
lint` chain and Prettier pass.
- The full frontend suite passed except one 5-second timeout in
`dependencyTooling.test.ts` under heavy load, which passes on its own.
  - Committed with the pre-commit hooks.

## Follow-ups, not changed here

- The API still returns tied findings in no fixed order:
`GetAllFindings` and `GetAllFindingsIncludingResolved` use `sort.Slice`
over map iteration. The panel no longer depends on that order.
- A change to the open finding itself still redraws its review, which
closes the thread, but the page no longer moves.
- Below 1280px the review renders under the whole findings list, so
opening one from a row far down leaves it out of view until the reader
scrolls.
2026-10-23 23:31:59 +01:00
pulse-triage[bot] aaa6aaa807 Distinguish failed PBS discovery from an empty backup scope
Carry failed and accepted-empty discovery through ordinary serialized backup polling. Preserve unreadable history, reconcile only confirmed absence, and reject malformed datastore envelopes before destructive reconciliation.

Contract-Neutral: Enforces existing PBS scope-evidence semantics; no API, model, policy or permission change.
Change-source: pulse-maintainer
2026-10-23 23:11:44 +01:00
rcourtman c46dd92777 Keep Patrol finding reviews in place across refreshes
On the Patrol Activity tab, an open finding review threw the reader back
to the top of the page: on opening it, opening its thread, on
Re-investigate and on every 30s and 60s findings reload. Each reload
parsed new finding objects, so FindingsPanel rebuilt every row and
remounted the open review. The review read its investigation with
createResource, and the nearest Suspense boundary is the app's route
boundary: its "Loading view..." fallback replaced the whole page, the
scroll shell's content collapsed and its position clamped to 0.

The findings store now keeps unchanged findings, and the list, across
reads. FindingsPanel keeps display groups whose findings did not change,
settles sort ties by id because the API returns tied findings in no
fixed order, and gives the open review its related findings through a
getter. The review's investigation, governed action and thread, and a
run's tool calls, read through createNonSuspendingQuery.

Refreshes no longer remount the review, which they had also used to
re-read it and keep its ages current. The review re-reads on its own
(every 30s, every 5s while a run is active), so createNonSuspendingQuery
accepts a pollMs accessor. A failed read keeps the last answer,
Re-investigate waits for the first read, the thread follows its run's
session, and mounted ages read the shared relative-time clock.

Contract-Neutral: api-contracts: the findings store keeps its payloads, normalization and routes; it only reuses unchanged finding objects across reads
2026-10-23 23:08:34 +01:00
pulse-triage[bot] b79496219f Preserve prior installer snapshots on retry
Reconcile the retained snapshot publication repair with current main without importing unrelated containing history. Unique private staging, no-clobber publication and failure-before-mutation protect earlier recovery copies; retain zero-retention and hardened-unit fallback semantics.

Retained-source: 854ea3c81a94ffe88145d663d25d5040d4637a47, 91bd7393fc4413b6629ad851f90c2c7f6c2c0991
Change-source: pulse-maintainer
2026-10-23 22:39:08 +01:00
rcourtman a4bfca4d32 Serialize Patrol action projection and finalize hung runs
Three gaps remained in how Pulse projects a Patrol investigation's
action and closes its runs.

Projections of one investigation could interleave: the transition
callback, an investigation read and a run's close each read the action
audit and applied it, so one that read an older state could land after
one that read a newer state, and an approval could land between a
run's reconciliation and its finalization. All of them now read and
apply under the investigation's lock, and a close finalizes before it
releases it.

The projection replaced the whole session through Update, so it could
undo run progress the orchestrator saved in between. It now writes
only the changed action and outcome when the store offers SetAction
(aicontracts.InvestigationActionStore), and the reference it writes
carries the broker's canonical result and verification status and the
model's causal attribution, which the audit does not hold.

A run that hung past its limit without closing was recovered by the
stuck-run sweep but never finalized. The sweep now finalizes it with a
copy of its session marked failed, leaving the orchestrator's session
as it is. A later real close is finalized instead, and a refresh no
longer reopens a record that finalization closed.
2026-10-23 22:26:09 +01:00
pulse-triage[bot] 7b39194ae2 Refresh PBS inventory when its newest backup is removed (#3150)
A capped snapshot cache can keep the same retained count after pruning
its newest archive. Require the group's newest timestamp to match
exactly before reusing it, so the deleted recent archive cannot keep
stale backups looking protected. Exercise VM and container polling
through successful, unavailable and denied refreshes, unchanged-cache
reuse, recovery and confirmed removal.

---
Fleet candidate `20261023T202016Z-core-runtime` from the `core-runtime`
lane. Reviewed tip `1d1bf942e63f8cdd8c9fe050822ef96cff75a516` on `main`.

[How Pulse handles
triage](https://github.com/rcourtman/Pulse/blob/main/docs/AI_TRANSPARENCY.md)
2026-10-23 21:17:29 +00:00
pulse-triage[bot] 7152d2b7ff Keep failed PBS namespace discovery out of complete backup evidence
Carry namespace discovery errors with the immutable datastore snapshot into background backup polling. Readable root history still updates, but unknown child namespaces cannot delete durable facts or claim complete protection. Cover real HTTP discovery through ingest, replacement state, repeated failures, scoped removal, recovery and legacy root-only support.

Change-source: pulse-maintainer
2026-10-23 21:57:43 +01:00
pulse-triage[bot] 4b3373f077 Compose retained PBS cache repair with current PVE main
Change-source: pulse-maintainer
2026-10-23 21:55:57 +01:00
pulse-triage[bot] f5c2c3a198 Reproduce PVE partial-scan recovery history loss (#3148)
**Reproduce PVE partial-scan recovery history loss**

Drive ordinary backup polling through durable recovery ingest after live
inventory replacement, with partial, complete, isolation and
cancellation controls.

**Retain PVE recovery history across incomplete backup scans**

Use the poll's existing completeness evidence to admit destructive
reconciliation. Failed or skipped reads may update accepted rows without
deleting unseen durable facts, and cancellation cannot publish an empty
success. Complete deletions remain scoped and tested.

Contract-Neutral: Restore collection truth without changing APIs,
schemas, configuration or protection policy.

**Keep carried PVE rows from renewing recovery observations**

Retain their existing durable point evidence under the partial provider
observation instead of presenting a failed refresh as a fresh artifact
observation. Cover warm inventory alongside the cold-start
reconciliation regression.

Contract-Neutral: No API, schema, configuration or protection-policy
delta.

**Match the full workload identity in the PVE browser journey**

Use the production disclosure's guest name and VM identity, and bring
its provider evidence into the captured viewport. The initial selector
timeout is retained as a harness failure, not a product result.

**Avoid the published monitoring contract insertion conflict**

Relocate the PVE partial-backup collection contract without changing its
text so it composes with the published snapshot-listing contract at
15ea29fe2b. Runtime, regression tests and
browser-proof inputs remain unchanged.

---
Fleet candidate `20261023T194015Z-core-runtime` from the `core-runtime`
lane. Reviewed tip `d39d65fede2a6afc535c0387b0c0e6683868b22e` on `main`.

[How Pulse handles
triage](https://github.com/rcourtman/Pulse/blob/main/docs/AI_TRANSPARENCY.md)
2026-10-23 20:48:34 +00:00
pulse-triage[bot] 1d1bf942e6 Refresh PBS inventory when its newest backup is removed
A capped snapshot cache can keep the same retained count after pruning its newest archive. Require the group's newest timestamp to match exactly before reusing it, so the deleted recent archive cannot keep stale backups looking protected. Exercise VM and container polling through successful, unavailable and denied refreshes, unchanged-cache reuse, recovery and confirmed removal.

Change-source: pulse-maintainer
2026-10-23 21:27:34 +01:00
rcourtman 45b5a8cde8 Finalize Patrol runs when they close, failed runs included
A Patrol run that planned a fix and then failed, normally on its final
model call, was never finalized. Core's run goroutine returned on the
error before it rebuilt the investigation record, published the
unified finding and sent the approval push, so the record kept showing
the run as running and nobody was asked to approve the queued fix.
Manual re-investigations never reached that goroutine, even when they
succeeded, and a run a restart interrupted was only marked closed.

Core now finalizes a run when its close lands on the finding or when
the stuck-run sweep recovers it with a closed session. It first
reconciles the run's action from the action audit, because the
orchestrator writes the action back as planned and an approval or
expiry during the final model call may have overtaken it. For the same
reason a close no longer moves a finding back to fix_queued from an
outcome the action reached during the run. Finalization skips a run
the finding no longer names and asks for approval once per action. An
action's outcome keeps its single push from transition reconciliation,
so a close no longer sends a second fix-completed notification.

The action inbox and decision-queue expiry sweeps also publish the
plans they expire, as a single action's expiry already was, so a
Patrol finding's queued fix moves to needs_attention when its plan
expires. Reconciliation follows refresh lineage to ignore a transition
for a plan that the investigation's current action replaced, so the
expiry of a refreshed plan cannot take the investigation back.
2026-10-23 21:15:25 +01:00
pulse-triage[bot] d39d65fede Avoid the published monitoring contract insertion conflict
Relocate the PVE partial-backup collection contract without changing its text so it composes with the published snapshot-listing contract at 15ea29fe2b. Runtime, regression tests and browser-proof inputs remain unchanged.

Change-source: pulse-maintainer
2026-10-23 21:14:13 +01:00
pulse-triage[bot] 15ea29fe2b Cover failed snapshot listings without treating errors as deletion (#3146)
**Cover failed snapshot listings without treating errors as deletion**

Exercise both guest kinds, partial error results, same-VMID peer
isolation, warning continuity, accepted removal and explicit disablement
through the actual monitor. Include opt-in synthetic frames for the
production alert consumer.

**Use the served alert model in snapshot collection proofs**

The monitor exposes models.Alert, not the internal manager alert type.
Correct the fixture-only type mismatch before establishing the
unchanged-runtime control.

**Keep known LXC snapshots when their listing is unavailable**

Treat every failed snapshot read as incomplete rather than interpreting
596 or unavailable text as an empty successful listing. Preserve known
membership and warnings while independent accepted listings and
confirmed removal continue normally. Correct the isolation fixture to
use cluster-unique VMIDs, keeping the same-VMID cross-instance control.

Contract-Neutral: Restores existing failed-listing semantics for LXC; no
public API, schema, configuration, identity or alert policy delta.
Dedicated collection and producer/browser regressions are outside the
registry's fixed verification filenames.

---
Fleet candidate `20261023T190018Z-core-runtime` from the `core-runtime`
lane. Reviewed tip `850a6310446030354d29787b84e794e0b37852db` on `main`.

[How Pulse handles
triage](https://github.com/rcourtman/Pulse/blob/main/docs/AI_TRANSPARENCY.md)
2026-10-23 20:04:35 +00:00
pulse-triage[bot] 10f570a698 Keep carried PVE rows from renewing recovery observations
Retain their existing durable point evidence under the partial provider observation instead of presenting a failed refresh as a fresh artifact observation. Cover warm inventory alongside the cold-start reconciliation regression.

Contract-Neutral: No API, schema, configuration or protection-policy delta.
Change-source: pulse-maintainer
2026-10-23 20:50:06 +01:00
pulse-triage[bot] 50abcf51c4 Retain PVE recovery history across incomplete backup scans
Use the poll's existing completeness evidence to admit destructive reconciliation. Failed or skipped reads may update accepted rows without deleting unseen durable facts, and cancellation cannot publish an empty success. Complete deletions remain scoped and tested.

Contract-Neutral: Restore collection truth without changing APIs, schemas, configuration or protection policy.
Change-source: pulse-maintainer
2026-10-23 20:46:39 +01:00
pulse-triage[bot] ece2532710 Keep snapshot warnings when creation dates are unknown (#3145)
**Keep snapshot warnings when creation dates are unknown**

Distinguish a listed snapshot with an unusable date from removal. Retain
the last trusted canonical occurrence and evidence after current policy
resolution, including Proxmox's missing-snaptime Unix epoch
representation. Add lifecycle, exclusion, identity and real-consumer
browser proof fixtures.

Contract-Neutral: Repair existing snapshot warning preservation on
incomplete evidence; no API, configuration, schema or canonical identity
change. Dedicated regression and browser-producer tests accompany the
internal contract clarification.

**Keep the global alert pause contract in snapshot regression**

The focused source proof shows that global alerting disablement pauses
evaluation without resolving existing incidents. Assert exact trusted
occurrence preservation for that case; retain clearing assertions for
snapshot/guest disablement, exclusions, threshold changes and removal.
No production source changes.

---
Fleet candidate `20261023T184023Z-core-runtime` from the `core-runtime`
lane. Reviewed tip `9b607668b95bcd843c94aa8271bcd5d356abb985` on `main`.

[How Pulse handles
triage](https://github.com/rcourtman/Pulse/blob/main/docs/AI_TRANSPARENCY.md)
2026-10-23 19:45:42 +00:00
rcourtman f0ae6240d1 Hide Re-investigate while a planned fix is queued (#3141)
A Patrol investigation can plan a fix and then fail, normally on the
final summary call. The run then shows a queued fix with a failed
status, and the Investigation section offered Re-investigate because it
only looked at the status. Another run would plan a second action for
the same problem while the first still waits for approval.

- Re-investigate is hidden while the outcome is `fix_queued`, as it
already was for a completed run with a queued fix.
- The failed run's note uses the muted style of an interrupted run. The
fix awaiting approval is what the operator acts on, not the error.
- `patrol-intelligence.md` records the rule, including the server side:
the Pro orchestrator keeps the plan's outcome on such a run and answers
a re-investigation request with 409 `action_pending`.

Testing
- `InvestigationSection.test.tsx` (11 tests), type-check and eslint on
pulse-dev. Registry, contract and completion-guard audits pass on this
commit.
- Live, on a private Pro build: the failed run with a queued fix shows
Fix queued, a muted note and no Re-investigate.
2026-10-23 20:33:53 +01:00
pulse-triage[bot] 850a631044 Keep known LXC snapshots when their listing is unavailable
Treat every failed snapshot read as incomplete rather than interpreting 596 or unavailable text as an empty successful listing. Preserve known membership and warnings while independent accepted listings and confirmed removal continue normally. Correct the isolation fixture to use cluster-unique VMIDs, keeping the same-VMID cross-instance control.

Contract-Neutral: Restores existing failed-listing semantics for LXC; no public API, schema, configuration, identity or alert policy delta. Dedicated collection and producer/browser regressions are outside the registry's fixed verification filenames.
Change-source: pulse-maintainer
2026-10-23 20:14:11 +01:00
pulse-triage[bot] 9b607668b9 Compose snapshot warning preservation with current backup-date repair
Resolve the shared contract insertion by retaining both complete subsections. Preserve the reviewed snapshot creation-date repair and current main's independent backup success-date repair, tests and fixtures.

Contract-Neutral: Compose existing snapshot and backup warning preservation repairs without changing their public API, configuration, schema or canonical identity contracts. Retain both internal contract clarifications and executable verification.\nChange-source: pulse-maintainer
Change-source: pulse-maintainer
2026-10-23 19:42:00 +01:00
pulse-triage[bot] 9fb404604b Core runtime and integrations (#3143)
Improvement: backup-age false-recovery repair now has executable and
desktop/phone pixel proof.

**Continuation — truthful monitoring and backup evidence.** Candidate
**6fb43d9302d6673bc65193f3e17944098394ad6d**, above **c67f8bd500**,
contains **208f0cbf1e** plus an additive fixture correction.
Original-runtime control reproduces false recovery; full alert race/vet,
final consumer type checks, **1,404 tests**, compiled real Overview and
both browser journeys pass. Complete output hashes match, source guests
stopped, and all eight captures were natively opened and inspected.
Coordinator/reviewer/publisher own containing CI, independent review and
landing; their containing result or repair return is next. **Not landed,
released or installed recovery.** [Receipts](RECEIPTS.md),
[source](SOURCE.json), [continuation](CONTINUATION.json).

This raises the highest-ranked **guest/backup safety** weakness: a
still-listed backup's missing/zero/future date no longer clears an
established warning or hides valid provider evidence. Last trusted
occurrence/evidence survives; valid recent success, exclusions and
authoritative removal still clear normally. The executed baseline
establishes the defect, not a field incident's cause or native thaw
safety. [Reproduction](REPRODUCTION.json), [scope and original
jobs](RECONCILIATION.json).

The first consumer proof exposed a fixture-only type error; the additive
correction uses the current AI capability store without changing
production code or weakening checks. Both commits pass canonical
preflight; the frontend-file guard skip is not the separately completed
browser proof. [Validation](VALIDATION.json), [adverse
evidence](ADVERSE.json), [pixels](PIXEL-REVIEW.json).

Received continuations **416bb1c9 / 58e731f1** are completed at the
source-validation step. Previous node candidate **3c66ab14e7** and
Delivery/Pro/Host/PI's original installed guest, dashboard, SSO and
notification recoveries remain separately retained. No duplicate
handoff, infrastructure, guest probe, excluded investigation, release or
public action.

---
Fleet candidate `20261023T172026Z-core-runtime` from the `core-runtime`
lane. Reviewed tip `6fb43d9302d6673bc65193f3e17944098394ad6d` on `main`.

[How Pulse handles
triage](https://github.com/rcourtman/Pulse/blob/main/docs/AI_TRANSPARENCY.md)
2026-10-23 18:15:24 +00:00
pulse-triage[bot] c3a6b04fb6 Keep the global alert pause contract in snapshot regression
The focused source proof shows that global alerting disablement pauses evaluation without resolving existing incidents. Assert exact trusted occurrence preservation for that case; retain clearing assertions for snapshot/guest disablement, exclusions, threshold changes and removal. No production source changes.

Change-source: pulse-maintainer
2026-10-23 19:12:43 +01:00
pulse-triage[bot] d07080e8fb Keep snapshot warnings when creation dates are unknown
Distinguish a listed snapshot with an unusable date from removal. Retain the last trusted canonical occurrence and evidence after current policy resolution, including Proxmox's missing-snaptime Unix epoch representation. Add lifecycle, exclusion, identity and real-consumer browser proof fixtures.

Contract-Neutral: Repair existing snapshot warning preservation on incomplete evidence; no API, configuration, schema or canonical identity change. Dedicated regression and browser-producer tests accompany the internal contract clarification.
Change-source: pulse-maintainer
2026-10-23 18:59:08 +01:00
rcourtman d2ac07bccf Cache browser OS packages so slow apt mirrors stop E2E
Since 22 October the E2E jobs often time out installing browsers before
any test runs: 29 installs failed that day and 30 on 23 October, against
none in the week before. Each job downloads about 119 MB of Ubuntu
packages for Chromium and WebKit from the runner's Azure mirror, and
some GitHub-hosted runners now get tens of kB/s from it
(actions/runner-images#14823 and #15040). The same package set on the
same image versions installed in under a minute before then.

The three browser jobs now restore the newest saved .deb set with
actions/cache/restore and admit it through scripts/browser_os_packages.py.
apt reuses a file already in its archive directory after checking only
its size, so the helper refreshes the index and installs a private copy
of a cached file only when that copy's SHA256 matches a signed index
stanza for the package, version and architecture its filename names.
After a successful install, and before any journey can fail the job,
the helper rebuilds the cache from apt's archives and the job saves it
under a run-unique key only when the set changed, so Ubuntu updates
refresh the cache and an empty or unchanged set is never saved.

The transport bounds and install budgets are unchanged, and the jobs
hold no secrets or write tokens, as the workflow trust contract requires
for caches.
2026-10-23 18:02:39 +01:00
pulse-triage[bot] 208f0cbf1e Preserve backup-age incidents across unknown success dates
A still-listed backup with a missing, zero or future success date is not recovery evidence. Keep its existing occurrence and prefer the newest usable date across provider rollups, without changing explicit exclusions or inventory removal.

Add manager regressions and a real Overview consumer fixture. Exact-source execution and browser/pixel verification remain pending the fresh launch required by the completed dependency acquisitions.

Change-source: pulse-maintainer
2026-10-23 17:54:49 +01:00
rcourtman 92f20dafdb Hide Re-investigate while a planned fix is queued
A Patrol investigation can plan a fix and then fail, normally on the
final summary call. The run then shows a queued fix with a failed
status, and the Investigation section offered Re-investigate because
it only looked at the status. Another run would plan a second action
for the same problem while the first still waits for approval.

The section now hides Re-investigate while the outcome is fix_queued,
as it already did for a completed run with a queued fix. The failed
run's note uses the muted style of an interrupted run, because the fix
awaiting approval is what the operator acts on, not the error. The
Pro orchestrator keeps the plan's outcome on such a run and refuses the
re-investigation request too.
2026-10-23 17:43:57 +01:00
rcourtman a32356ae94 Describe the Assistant surface as Pulse Mobile chat
With the web chat window and its backend routes retired (#3126,
#3137), the pulse_assistant surface only serves Pulse Mobile's chat.
Its manifest description still called it the explanation and handoff
surface for Patrol findings, and that text is public: the docs/AI.md
overview, the MCP README surface block, and the External agents
settings panel all show it. It now names the Pulse Mobile role and says
the web chat is retired.

The browser's fallback affordances for an Assistant surface with none
declared still offered interactive questions. They now match the
backend default of tools only, so no client path advertises a question
tool nobody can answer.
2026-10-23 16:10:15 +01:00