Commit Graph

5926 Commits

Author SHA1 Message Date
rcourtman 9cb06ef6ef Show agents awaiting a report after server restart (#3211)
After a server restart, Pulse showed saved agents offline before they
had a chance to report. Recent enrollments now show “Waiting for first
report” until an accepted report arrives or their existing reporting
lease expires.

The shared read model preserves identity and the historical receipt
without inventing metrics. Live updates carry the same source delivery
state, so a browser left open during an update withdraws its previous
current readings. The expanded drawer keeps stored history available and
shows an informational wait without a fabricated zero-value reading.
Expired agents remain offline, rejected buffered reports cannot end the
wait, removed agents remain absent, and provider errors retain their own
status. Machines, resource details and Settings consume the same state.

Validation: targeted frontend and backend regressions, broader registry
and connection checks, type checking, build, governance checks, and
desktop/mobile browser verification of startup, first report, restart
and expiry.

Refs #2980. This change addresses the startup display and does not
establish the cause of the cluster DNS failure reported there.
2026-10-24 18:44:11 +01:00
rcourtman bc5491b6d1 Withdraw cached live history when agent reporting stops
An open drawer could retain a current-only reply after its agent entered
the first-report wait. Withdraw live snapshots immediately and refresh on
reporting transitions while preserving real stored observations.

Integrate published main while retaining the webhook acknowledgement and
chart optimizations. Keep guest metric admission under provider ownership.

Refs #2980
2026-10-24 17:51:28 +01:00
pulse-triage[bot] 30e76af97c Compose completed managed npm graph with current notification repairs
Change-source: pulse-maintainer
2026-10-24 17:24:37 +01:00
rcourtman ddaedc8c78 Keep unreported chart fallbacks empty after lease expiry
An expired saved enrollment still has no current telemetry. Keep summary
charts from turning that deadline into zero-valued live samples, while
preserving real stored history. Cover both sides of the reporting lease
and align the remaining pending-label assertions with the shared wording.

Refs #2980
2026-10-24 17:23:25 +01:00
pulse-triage[bot] 6752b024b3 Complete managed npm graph refresh on counted-navigation repair
Retain PR3208's main UI package graph on the landed accessibility correction. Integration axe/ws/types already match the protected graph. Advance the portal's locked ws to 8.22.0 while retaining its compatible override range and newer Vitest, Vite and Rollup; no production source or portal source-manifest input changes. Exact-graph build and browser acceptance remain pending dependency selection.

Contract-Neutral: Patch and minor npm graph refresh only; existing rendering, counted navigation, portal, entitlement and authorization contracts are unchanged.
Change-source: pulse-maintainer
2026-10-24 17:05:18 +01:00
rcourtman 5f0037fc25 Show agents awaiting a report after server restart
Keep retained enrollments unknown while their accepted-report lease is
current, then show offline if no fresh report arrives. Preserve historical
timestamps, removals and report ordering without manufacturing telemetry.
Use the shared first-report state in Machines, resource details and Settings.

Refs #2980
2026-10-24 16:45:39 +01:00
pulse-triage[bot] 94a50e75b0 Acknowledge persisted webhook deletion to the destination editor
Return the explicit success boolean the existing editor requires after durable and live removal, while retaining the legacy status field. Pin the response contract so a successful deletion is not displayed as failed and does not encourage blind retries.

Change-source: pulse-maintainer
2026-10-24 16:21:39 +01:00
pulse-triage[bot] 5c6811f93d Keep deleted webhook work bound to its original destination
Require an enabled saved ID before queued firing or recovery delivery. A sibling at the same URL cannot release a deleted destination’s retained payload and credentials. Keep the URL fallback for legacy ID-less work, preserve admitted snapshots and cancellation history, and prove restart/operator-retry isolation with real receivers.

Change-source: pulse-maintainer
2026-10-24 16:08:19 +01:00
rcourtman e570ad4132 Patch portal development dependency advisories (#3206)
Raise the portal Vitest minimum to 4.1.12 and the Vite override to 7.3.7
to address seven development dependency advisories. Refresh the lockfile
and rebuild the source manifest. Emitted application JavaScript and CSS
are byte-identical.

Validation on pulse-dev: npm audit reports zero vulnerabilities, type
checking passed, all 132 frontend tests passed, the frontend build and
Go portal tests passed. The main frontend lint and type checking also
passed for this exact commit through the shared validation worker.
Canonical status and ownership audits passed.
2026-10-24 15:58:48 +01:00
pulse-triage[bot] ea99456ffd Assemble retained metric series directly by statement-local number (#3205)
**Assemble retained metric series directly by statement-local number**

Avoid per-observation string resolution and nested-map flushes for
interleaved filtered fleets. Bind only nonempty series after each read
and bound the direct buffers, preserving the existing wide-sparse
fallback, SQL snapshots, tiers and exact observations. Verify against
single-resource readers on both layouts.

**Cap retained chart observations before allocating wire points**

Share the existing time-nearest selection by original indices, so chart
conversion materialises only selected observations. Preserve exact
timestamps, values, tie breaks, degenerate spans and oldest-source
metadata. Check the frozen former sampler across 1,440 cases and the
exported fallback contract.

Contract-Neutral: Exact chart observations, wire schema, sampling policy
and guest/storage lifecycle contracts are unchanged; only index
selection and allocation order change.

---
Fleet candidate `20261024T123325Z-core-runtime` from the `core-runtime`
lane. Reviewed tip `bf9f0ed9822c8ee72ca25b5caecb35a2cd4a5881` on `main`.

[How Pulse handles
triage](https://github.com/rcourtman/Pulse/blob/main/docs/AI_TRANSPARENCY.md)
2026-10-24 14:51:32 +00:00
pulse-triage[bot] bf9f0ed982 Cap retained chart observations before allocating wire points
Share the existing time-nearest selection by original indices, so chart conversion materialises only selected observations. Preserve exact timestamps, values, tie breaks, degenerate spans and oldest-source metadata. Check the frozen former sampler across 1,440 cases and the exported fallback contract.

Change-source: pulse-maintainer
Contract-Neutral: Exact chart observations, wire schema, sampling policy and guest/storage lifecycle contracts are unchanged; only index selection and allocation order change.
2026-10-24 14:08:51 +01:00
rcourtman b45b5562fa Name demo Patrol findings by their unified resource
Mock and demo mode synthesize Patrol findings from the legacy state
snapshot and stamped each one with the snapshot's source ID
(nebula-1-mock, mock-cluster-1-pve1-local). Everything that joins a
finding to its resource uses the unified ID: since #3188 an Inbox item
names its subject by the unified resource its alert resolves to, and the
resource-scoped findings list and resource intelligence look findings up
by that ID. On a mock or DEMO_MODE stack the Docker host offline item
therefore said "Patrol has not raised a finding about it" beside an open
finding on that host, offered no Browse button, the host's intelligence
showed no findings, and the finding never folded under its alert. The
LXC memory finding was also typed "container", which reads back as
"node".

Resolve the reference an alert on each record carries the way Inbox
subjects resolve, app containers through the container views (mock
container IDs are too short to bind a Docker alert reference), and the
snapshot's own registry when the read state has not ingested the record
yet, so a first cycle cannot pin source IDs for the life of the process.
Guests are typed vm or system-container.
2026-10-24 13:49:11 +01:00
rcourtman 0a98f0a8de Patch portal development dependency advisories
Raise Vitest to 4.1.12 and Vite to 7.3.7 to close the portal tooling advisories. Refresh the lockfile and regenerate the frontend source manifest.

Contract-Neutral: Development dependency security fixes; emitted application bundles are byte-identical and the portal product contract is unchanged.
2026-10-24 13:16:14 +01:00
pulse-triage[bot] fbe410e244 Compose chart SLO repair without dropping current administrative guarantees
Resolve adjacent contract append conflicts by keeping bounded hosted-agent classification in its chart section and preserving all newer SSO, RBAC and uninstall guarantees. Runtime, workflow and synthetic chart controls merge without conflict; no latency budget or customer journey changes.

Change-source: pulse-maintainer
2026-10-24 11:42:23 +01:00
pulse-triage[bot] 7c0fc7f79e Reject competing audit webhook destination fields (#3192)
**Reject competing audit webhook destination fields**

Parse one bounded update object without discarding repeated urls
members, including escaped and case-folded aliases. Preserve deliberate
clears, singleton aliases and unknown fields; reject ambiguous or
incomplete selections before validation and mutation.

**Bind audit guidance smoke checks to the unambiguous decoder**

The complete-selection admission moved out of the inline struct. Retain
the route/access and organisation assertions, and require decoding
before URL validation instead of the obsolete struct spelling.

Contract-Neutral: Align a static documentation check with the reviewed
decoder; runtime and published contracts are unchanged.

**Reject competing public SSO and recovery request fields**

Validate complete bounded admin objects before decoding or applying
them. Preserve singleton aliases, unknown fields, case-sensitive role
map keys, explicit access clears, saved secrets and existing tenant and
one-use token boundaries. Add public producer, persistence and API
contract controls.

**Preserve published audit smoke bindings in the SSO candidate**

Resolve the overlapping audit-doc test correction against published main
52457777c4 by retaining its stronger
bounded decoder and mutation-order assertions. No runtime or frontend
source changes; the reviewed SSO and recovery proof remains applicable.

Contract-Neutral: Integration-only static test reconciliation; API,
runtime, frontend and documented contracts are unchanged.

---
Fleet candidate `20261024T084021Z-core-runtime` from the `core-runtime`
lane. Reviewed tip `7a50adc5ac6924829c1e871c638db9b461b40011` on `main`.

[How Pulse handles
triage](https://github.com/rcourtman/Pulse/blob/main/docs/AI_TRANSPARENCY.md)
2026-10-24 10:17:29 +00:00
pulse-triage[bot] b25c22c00f Keep bounded hosted provisioning overhead in chart SLO verdicts
Retained hosted samples include both an over-budget failure and an under-budget pass with one provisioning agent occupying about one logical CPU. Attribute only that bounded Actions overhead; retain raw CPU and absolute 90 ms latency, and keep other or unattributed CPU, guest, steal and swap invalidating. Exercise the real CLI and workflow shells with adverse controls; no timed workload or release acceptance is claimed.

Change-source: pulse-maintainer
2026-10-24 10:48:40 +01:00
pulse-triage[bot] 52457777c4 Reject competing audit webhook destination fields (#3190)
**Reject competing audit webhook destination fields**

Parse one bounded update object without discarding repeated urls
members, including escaped and case-folded aliases. Preserve deliberate
clears, singleton aliases and unknown fields; reject ambiguous or
incomplete selections before validation and mutation.

**Bind audit help checks to the bounded selection decoder**

Repair the containing CI failure from PR 3186 without restoring
last-member-wins parsing. Keep the explicit array, request bound, URL
validation before mutation and organisation-scoped replacement checks;
runtime selection semantics remain covered by the existing handler
tests.

Contract-Neutral: Static guidance/source-binding test repair only; the
composed audit runtime and documented API, access, storage and delivery
contracts are unchanged.

---
Fleet candidate `20261024T082008Z-delivery-trust` from the
`delivery-trust` lane. Reviewed tip
`58e46e21d7c9bd265e88485d26bfed38e03fb31e` on `main`.

[How Pulse handles
triage](https://github.com/rcourtman/Pulse/blob/main/docs/AI_TRANSPARENCY.md)
2026-10-24 09:34:36 +00:00
pulse-triage[bot] 3d05fd3cda Reject competing public SSO and recovery request fields
Validate complete bounded admin objects before decoding or applying them. Preserve singleton aliases, unknown fields, case-sensitive role map keys, explicit access clears, saved secrets and existing tenant and one-use token boundaries. Add public producer, persistence and API contract controls.

Change-source: pulse-maintainer
2026-10-24 09:48:15 +01:00
rcourtman 1120502a1d Name Patrol inbox alert subjects by their unified resource
The Patrol inbox passed each alert's own resource reference through as the
item subject: docker:<host>/<container> for a Docker container,
<instance>:<node>:<vmid> for a guest, agent:<host>/disk:<label> for a
filesystem. Patrol findings, protection postures and resource actions carry
the unified resource ID instead, so "Browse Patrol findings for this
resource" opened an empty list, postures never attached, and the alert
detail said Patrol had raised no finding while it had one on that exact
container.

The attention projection now resolves each record's subject through the
org registry's alert reference resolver and names the unified resource. The
operational record keeps the alert's reference, and a reference inventory
cannot bind stays as before. The resolver gains Docker container references
with a complete ID, the key RecordChange already joins their history to, so
container alerts resolve like every other kind and alert-triggered Patrol
scopes for them keep the triggering alert. It also reads source targets
from the registry's view cache rather than scanning every mapping per
reference, which a page of alerts would otherwise multiply.

The alert-mirror reconcile compared the same mismatched strings and never
folded a finding under its alert; it now matches either identity. A
filesystem, sensor or ZFS pool alert resolves only to the owner its siblings
share, so the resolver marks it as a component and the reconcile does not
match on that owner. The inbox links a finding to the exact alert it
restates by the stamped alert ID, not by resource and kind, which several
alerts on one host share. The restart offer checks evidence against the
record's own reference, since the item subject is now the unified container
its registry lookup needs.

When no finding restates the alert but Patrol has open findings on the same
resource, the detail names them instead of saying Patrol raised nothing,
and the browse button appears only when there is something to browse.
2026-10-24 09:46:33 +01:00
pulse-triage[bot] 62b6b821a7 Compose reviewed audit destination protection on current main
Change-source: pulse-maintainer
2026-10-24 09:41:54 +01:00
rcourtman bee3a94ce6 Stop Patrol resolving Docker alerts the snapshot still shows
Patrol's post-run alert review skipped the model only for metric and
offline alerts that were still firing. Every other aged alert went to
the model with a context line of CPU, memory and status. On a private
Pro stack on 24 Oct 2026 the review resolved a docker-container-health
alert for a container Docker still reported unhealthy ("running with
normal CPU/Memory, no unhealthy indicator present"): the context never
showed health. The alert engine raised the alert again on the agent's
next report about 24 seconds later, so every Patrol pass flapped it,
with a resolved and a new alert and their notifications.

The still-firing gate now keeps, without a model call, the Docker
container alerts whose condition the container's current report still
carries, mirroring the alert engine's checks: a health result other
than healthy, starting or none while running; a runtime state the
state alert covers; exited or dead with OOM-killed set. A restart-loop
alert is kept while the container is caught in Docker's restart
backoff; the snapshot has no restart window, so a running container's
loop still goes to the model. The gate finds the container by the
alert's docker:<host>/<container> ID. Matching the display name, as the
lookup did, judged an alert against whichever same-named container
came first, possibly on another host.

The review context for a Docker container now carries its health,
restart count and OOM kill (yes, no or unknown), so a recovered
container's alert reaches the model with that evidence and still
resolves on its verdict. The prompt makes a condition the shown state
does not cover a KEEP, for alert types the gate and context do not
model yet. And an alert whose subject the context lookup cannot read
but inventory still holds, or holds the owner of, by durable identity
(a ZFS pool's storage, a guest's backup, a Swarm service) is described
as present, not as "not found (may have been removed)", which invited
a RESOLVE.
2026-10-24 09:13:30 +01:00
pulse-triage[bot] 3883796a9b Reject competing audit webhook destination fields
Parse one bounded update object without discarding repeated urls members, including escaped and case-folded aliases. Preserve deliberate clears, singleton aliases and unknown fields; reject ambiguous or incomplete selections before validation and mutation.

Change-source: pulse-maintainer
2026-10-24 08:04:34 +01:00
pulse-triage[bot] 93bcdbbd7c Keep missing vzdump guest verdicts unknown
An ended parent job does not establish the result of a guest whose log section has no verdict. Preserve explicit results, leave missing results unknown and keep unqualified job logs retryable until attribution is complete. Cover parser, poll/cache, served state and recovery mapping with offline fixtures.

Change-source: pulse-maintainer
2026-10-24 06:56:44 +01:00
pulse-triage[bot] f98c1eae78 Compare tenant audit mutations with each request starting state
The second tenant control must preserve the first tenant deliberate clear, not require that the earlier update never happened. Capture both update counters before the rejected request and require them to stay unchanged. No production behaviour changes.

Change-source: pulse-maintainer
Contract-Neutral: Correct the synthetic tenant control baseline; production audit handlers and contracts are unchanged.
2026-10-24 05:30:25 +01:00
pulse-triage[bot] 433f617db2 Preserve explicit audit selections before reads and webhook updates
Reject damaged query encoding and repeated singleton selectors in the canonical list, export and summary handlers before reading events. Require an explicit webhook urls array so absent selections cannot silently disable forwarding; intentional empty arrays, URL validation and tenant boundaries remain intact. Add executable handler/exporter and mutation preservation controls and update the owning contracts.

Change-source: pulse-maintainer
2026-10-24 05:21:01 +01:00
pulse-triage[bot] 0dfa8c193e Keep unread disabled backup storage out of complete PVE history
Count configured backup targets before the no-query check, preserve their accepted rows without renewing evidence, and publish incomplete provider history instead of reconciling unseen points away. Keep disabled storage unqueried; cover warm and rebuilt live state, one or all disabled targets, normal re-enable, confirmed empty reads and recovery.

Change-source: pulse-maintainer
2026-10-24 04:05:40 +01: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] 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] 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] 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
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
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] 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
pulse-triage[bot] d1ecdcb939 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.

Change-source: pulse-maintainer
2026-10-23 23:13:40 +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
pulse-triage[bot] 5db3120f36 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.

Change-source: pulse-maintainer
2026-10-23 22:30:36 +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] 11d1620807 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.

Change-source: pulse-maintainer
2026-10-23 22:02:03 +01: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