Commit Graph

107 Commits

Author SHA1 Message Date
rcourtman 1a094646d7 Let only POST narrate single-resource reports with Pulse Assistant (#2716)
## What

Single-resource report generation (`/api/admin/reports/generate`) now
takes `POST` with a JSON body as its canonical transport, and only
`POST` may narrate a PDF with Pulse Assistant.

- **POST**: the same fields as before (`resourceType`, `resourceId`,
`format`, `metricType`, `start`, `end`, `title`) in a JSON body, decoded
with the `generate-multi` rules (1MB cap, unknown fields and trailing
payload rejected). When Pulse Assistant is configured, a PDF's executive
summary is narrated by the AI provider as before.
- **GET**: still accepts the same fields as query parameters and returns
the same report, but always uses the deterministic summary. The PDF
shows a short note in place of the "Configure Pulse Assistant" tip,
which would be wrong for a tenant that already has the Assistant
configured.
- **GET never resolves the AI service at all.** It reads Patrol findings
only from a tenant AI service that is already running, through a new
`AISettingsHandler.ExistingAIService` lookup. Constructing a tenant
service through `GetAIService` can list provider models or start
background discovery, so a report GET must not trigger it. The router's
reporting wiring moves into `Router.wireReportingAIResolvers` so a test
can drive it.
- **Other methods**: `405` with `Allow: GET, POST`.
- The shared report body decoder now also returns `body_too_large`,
rather than `invalid_body`, when the cap is hit while reading past a
valid value.
- The reporting catalog advertises
`performanceReport.singleResourceMethod: "POST"`, and the settings UI
follows it. A catalog without the field, and the legacy fallback
catalog, keep the GET query transport that older backends accept. Fleet
reports were already `POST`.
- `docs/API.md` and `docs/MSP.md` (and their shipped copies) document
both methods.

## Why

Before this change, a GET for a PDF resolved the tenant's AI narrator.
That made a paid provider call and recorded a usage event in the AI cost
ledger. GET passes the demo-mode guard and the CSRF check, and a
SameSite=Lax session cookie is sent on cross-site top-level GET
navigations. So following a link could spend a signed-in admin's AI
budget.

GET stays available rather than returning `405` because the API
reference and the MSP guide documented it for scripted exports. Breaking
those scripts would add no protection: CSV is never narrated, and GET no
longer reaches the provider.

## Verification

- `TestContract_SingleReportAINarrationRequiresPOST` drives a real
report engine with a counting narrator. GET, HEAD, PUT and DELETE make
zero narrator calls and POST makes one. The GET narrative is heuristic
and carries the note. As a negative control, the same test fails against
the handler on `main` via `go test -overlay` ("GET report called the AI
narrator 1 times, want 0").
- `TestContract_SingleReportGETDoesNotConstructTenantAIService` drives
the router's real resolvers with an uncached tenant: GET constructs no
tenant AI service, while POST resolves one. A negative control that
swaps `ExistingAIService` back to `GetAIService` fails it ("GET report
constructed 1 tenant AI services, want 0").
`TestAISettingsHandler_ExistingAIServiceNeverConstructs` covers the
lookup itself.
- Handler tests cover the POST body rules (empty, unknown field,
trailing value, form-encoded body, fields only in the query, oversized
padding) and the `Allow` header. The catalog contract snapshot includes
the new field.
- Frontend tests cover POST bodies, the GET transport for catalogs that
name GET, parser defaults and validation, and the legacy fallback.
`docsLinks.test.ts` ties the docs to the handler.
- Browser pass on a private stack: real login, dev Pro licence, and
Pulse Assistant pointed at a local stub OpenAI-compatible provider that
logs every call. Checked at 1280×800 and 390×844:
- Generating a single-resource PDF from Settings → Data & Reports sends
one `POST` with a JSON body, downloads the PDF and calls the provider
once. The downloaded PDF carries the AI narrative.
- CSV makes no provider call. The fleet report still posts to
`generate-multi`.
- A top-level browser navigation to the GET URL with the session cookie
downloads the deterministic PDF, with the new note, and makes no
provider call.
- HEAD, PUT, DELETE and PATCH return `405`, and a session POST without
the CSRF header returns `403`.
  - The AI usage summary lists `report_narrative` only for the UI POSTs.
- Full `./internal/api/...` and `./pkg/reporting` run on the dev VM:
everything passed apart from two tests that fail there for environment
reasons in code this change does not touch
(`TestServerInfoEndpointReportsDevelopment` needs `.git`; a `configapi`
shell-hangup bootstrap test).

The same class of issue on the Assistant session routes
(`/api/ai/sessions/{id}/summarize` and friends) is handled separately in
#2670.
2026-10-07 23:48:46 +01:00
rcourtman 9bd1b6e582 Let only POST narrate single-resource reports with Pulse Assistant
GET /api/admin/reports/generate resolved the tenant's AI narrator, so a
PDF requested with GET made a paid provider call and appended a usage
event to the AI cost ledger. GET passes the demo-mode guard and the
CSRF check, and a SameSite=Lax session cookie rides a cross-site
top-level GET navigation, so a link could spend a signed-in admin's AI
budget.

POST with a JSON body, decoded with the generate-multi rules, is now
the canonical single-resource transport and the only one that may use
the AI narrator. The settings UI sends it, following a new
performanceReport.singleResourceMethod catalog field; a catalog without
the field, and the legacy fallback catalog, keep the GET transport that
older backends accept. GET stays available with the same query
parameters but narrates deterministically, printing a note in place of
the "Configure Pulse Assistant" tip, and other methods get 405 with
Allow: GET, POST. GET also stops resolving the AI service at all: it
reads Patrol findings through a new ExistingAIService lookup, because
GetAIService constructs a tenant service on first use and that can list
provider models or start background discovery. The router's reporting
wiring moves into wireReportingAIResolvers so a test can drive it.

Keeping GET rather than answering 405 avoids breaking documented API
scripts (MSP.md pointed providers at it) for no security gain: CSV is
never narrated and GET no longer reaches the provider.
2026-10-07 21:00:34 +01:00
rcourtman f7ac26b573 Keep alert settings a config save leaves out
The Thresholds page builds its PUT /api/alerts/config body from the
settings it edits. It has no control for flapping detection, alert TTL
cleanup or custom rules, so it never sends flappingEnabled,
maxAlertAgeDays, maxAcknowledgedAgeDays, autoAcknowledgeAfterHours or
customRules. The handler decoded the body into a zero AlertConfig, so
every thresholds save turned flapping detection and TTL cleanup off,
reset the flapping tuning, dropped custom rules, and persisted that.
The page's flapping, aggregation and ioNormalization objects are not
AlertConfig fields and never enabled anything.

The handler now hands the body to Manager.ApplyConfigUpdate, which
reads the stored config, merges and applies it under the manager lock,
so two concurrent partial saves cannot revert each other. The body is
decoded on its own, so the keys it carries mean exactly what
encoding/json makes of them; every top-level field it left out is
copied from a JSON round trip of the stored config, sharing no maps
with it. ApplyConfigUpdate returns a cloned snapshot, which the handler
persists, because SaveAlertConfig normalizes the config it is handed in
place and used to write into the live config's maps. Saves run one at
a time so alerts.json ends with the last applied config. A body that
is not a JSON object, null included, is a 400. Stored configs are not
rewritten, so a value an earlier save zeroed stays as it is.

On a private stack, a node CPU change saved from the Thresholds page
zeroed all four fields in memory and in alerts.json on main; with this
change the same save kept them and stored the new threshold.
2026-10-07 16:31:52 +01:00
pulse-triage[bot] c420758068 Keep history diagnostics private and match current query types
Reuse the tested API helper, explain point/source and access distinctions, and preserve data before storage tuning. Copied examples now exercise private success/error responses, redirects, incomplete transport and encoded IDs.

Contract-Neutral: Documentation and recipe checks align with existing history API behaviour; no runtime, storage, entitlement or authentication contract changes.
Change-source: pulse-maintainer
2026-10-05 06:47:09 +01:00
pulse-triage[bot] aece6b5217 Keep copied API requests private, bounded and one-shot
Save every existing reference response privately, including errors and partial POST results. Preserve private header files, reject unsafe preparation and URLs, and stop on redirects or transport errors without retries. Exercise all eight copied GET/POST examples against real synthetic HTTP/TLS peers, including the old response exposure.

Contract-Neutral: Harden existing API documentation recipes and their shipped mirror with connected tests; no runtime, endpoint, authentication, scope, action, dependency or permission contract changes.
Change-source: pulse-maintainer
2026-10-05 01:52:58 +01:00
pulse-triage[bot] b27387d8fc Match probe help to the current observation-location editor
Fresh editor and API inspection shows that local and remote paths can coexist, including in published v6.4.5. Explain the actual location picker, per-path uncertainty and safe canonical-versus-legacy update fields, and validate the linked target-field instructions in the Docs renderer.

Contract-Neutral: Documentation, shipped mirrors and guidance verification only; existing observation and API semantics are unchanged.
Change-source: pulse-maintainer
2026-10-04 07:29:43 +01:00
pulse-triage[bot] 2789a15fe5 Keep authentication diagnostics private and permission-aware
Use private header files rather than shared passwords or proxy secrets in process arguments. Separate intentional AI settings writes from diagnostics, distinguish proxy denials from authentication/transport failures, and preserve the real proxy trust boundary and 5.x limitation. Exercise copied commands, private-file preparation and rendered help.

Contract-Neutral: Documentation and executable help controls only; no authentication, authorization, AI autonomy or runtime contract change.
Change-source: pulse-maintainer
2026-10-03 10:22:45 +01:00
pulse-triage[bot] 7d5c6eb004 Integrate reviewed credential-safe administration guides
Change-source: pulse-maintainer
2026-10-01 09:36:28 +01:00
pulse-triage[bot] 0832be112f Integrate reviewed credential-safe Proxmox bootstrap and demo sampler reuse
Merge Core candidate cf12c37e63 unchanged onto 22380a3546. Resolve only the shared browser receipt to the candidate receipt; changed frontend source exactly matches its non-merge proof-bearing commit. Preserve newer safe API recipes and sparse History source.

Retained focused configapi, hostagent, mock, monitoring and installtests race suites, affected API cases, frontend suite and type-check, builds and PVE/PBS desktop and phone-emulated browser checks cover the changed behaviour. Broad API timeout and output-limit attempts remain unresolved, not passes. No installed recovery, release qualification, publication or deployment is claimed.

Change-source: pulse-maintainer
2026-10-01 09:06:11 +01:00
pulse-triage[bot] 2c98baabea Align administration guide details with current handlers
Use the accepted organisation ID alphabet and canonical resource types, and expect the documented role assignments to return 204. Keep the guide lifecycle checks at the existing API contract.

Contract-Neutral: Documentation and test expectations only; API methods, validation, authentication and tenant isolation are unchanged.
Change-source: pulse-maintainer
2026-10-01 08:44:10 +01:00
pulse-triage[bot] 0e06f2b38b Make administration guides safe and executable
Keep RBAC, audit and organisation tokens out of process arguments. Use signed-in organisation mutations, actual schemas and acceptance rules, a custom role ID, and private audit exports. Exercise copied commands and handler lifecycles without changing runtime authority.

Contract-Neutral: Documentation and documentation tests only; authentication, RBAC, tenant isolation, licensing and API runtime contracts are unchanged.
Change-source: pulse-maintainer
2026-10-01 08:23:23 +01:00
pulse-triage[bot] 704f4eeb4e Keep fleet and action API tokens out of shell recipes
Use the existing HTTP routes with private header files, JSON on stdin and curl defaults disabled. Separate plan, decision and execution, explain their real permissions and retain uncertain-execution safety. Extend the executable guide tests to cover all seven recipes, both header formats, auth failures and trace-enabled local curl settings.

Contract-Neutral: API routes, authentication, scopes, action authority and runtime execution are unchanged; only existing guidance and its executable documentation checks change.
Change-source: pulse-maintainer
2026-10-01 07:27:49 +01:00
pulse-triage[bot] 2fd114b3ac Keep Proxmox bootstrap credentials out of copied commands
Separate PVE/PBS setup and telemetry credentials from shell source and download URLs. Use silent root/sudo input, private-file handoff, complete downloads and the agent preflight; preserve scope, TLS defaults, single-line paste and coherent rolling-upgrade metadata. Reveal tokens through the existing dialog and discard late issuance after close. Pin executable shell, history, TLS/registration and browser contracts without using real credentials.

Change-source: pulse-maintainer
2026-10-01 05:23:08 +01:00
pulse-triage[bot] 45b2fae8db Keep API diagnostic credentials out of command arguments
Use a private header file and an authenticated read-only summary rather than passing tokens in argv to a public health check. Exercise both header formats, file permissions, preserved content and failing HTTP auth errors with synthetic local fixtures.

Contract-Neutral: API authentication, routes, scopes and CSRF enforcement are unchanged; this updates existing usage guidance and executable documentation checks.
Change-source: pulse-maintainer
2026-09-30 13:15:15 +01:00
rcourtman 97f894b009 Retire Relay labels and upsells inside Pulse
Relay left public checkout on 2026-09-29: it only ever connected the
Pulse Mobile app, which retires on 31 March 2027, and existing Relay
subscribers now carry Pro entitlements. The app still sold it. Every
Community install saw a "Get alerts on your phone ... Available with
Relay and Pro plans" upgrade panel on Alerts destinations, the plan
screen offered a Relay card with "Remote web access via Relay", and the
settings section was called Remote Access although Relay never reached
the web UI.

The Alerts push panel now renders only on instances that have the relay
feature, with no upsell. The settings section, nav, header and locale
catalogs say Pulse Mobile and carry the retirement date. Community sees
only the Pro comparison card and Relay-tier licenses see none; gated
mobile features name Pro as their minimum plan. The relay feature is
labelled "Pulse Relay (Mobile Connection)" in the catalog, plan copy no
longer claims remote web access, the backend pairing diagnostics point
at Settings > Pulse Mobile, and the user docs, including PRIVACY.md,
state that Relay does not provide remote web UI access.
2026-09-29 13:19:54 +01:00
pulse-triage[bot] 1b422dd1a6 Document maintenance API and repair documentation section links
Lands contributor PR #2067 (head 5eeea72a3a, author rcourtman) as a single maintainer commit on main. The PR documents the authenticated /api/resources/{id}/operator-state maintenance window contract and repairs GitHub-compatible documentation heading fragments.

Composed from the reviewed web-product candidate tree 68465bc48d (candidate 20260921T054028Z-web-product), which resolved the conflicts against main and replaced the unavailable github-slugger dependency with a dependency-free GitHub-compatible heading slugger. The tree is unchanged from that reviewed candidate; only the commit shape differs so the fdb2be15be..HEAD provenance range contains no external PR commit.

Validation retained from that candidate: vite build passed, check-bundle-size.mjs passed (vendor within budget), 61 focused vitest tests passed, and the offline Playwright docs-fragment-navigation run passed with a content-addressed browser-verification.json receipt based at fdb2be15be.

Change-source: pulse-maintainer
2026-09-21 07:23:24 +01:00
rcourtman 8ea94d792b Add the Patrol weekly digest endpoint
Nothing in Pulse showed a paying customer what Patrol had done for them:
about 164 runs a month per install, findings raised, investigations and
fixes, and none of it summarised. GET /api/ai/patrol/digest rolls the last
N days (default 7, max 30) up from records Pulse already keeps: run
history, the findings store, Patrol-origin action audits, and the usage
cost store. It adds no telemetry and persists nothing. The payload
reports when the bounded run history no longer covers the window and when
model pricing is unknown, rather than quietly under-counting.

This is the first slice of the "Patrol weekly digest" named bet in the
pulse-pro demand ledger; the in-app "This week" card follows once its
browser pass is recorded. docs/PATROL_WEEKLY_DIGEST.md holds the design
note and the honest limits of each line.

status.json registers the patrol-value-visibility coverage gap, the
candidate lane, and its work claim. It also drops the second, identical
copy of the ai-provider-guided-setup coverage gap that landed with #1853;
the duplicate id fails the status audit on main for every pull request.
2026-09-02 08:53:50 +01:00
rcourtman ac328e1eee Guide the Patrol model choice with cost preview and budget pause
Forty-two percent of paying installs never get Patrol running, and the
support and issue evidence says the wall is model choice and cost: a
prospect asked which models Pulse recommends before trying, a Pro customer
picked Flash-Lite to save money and Patrol then could not file verdicts,
and a mispriced Opus row tripped the 20 USD budget so the circuit breaker
disabled Patrol with nothing but a log line (#1789).

Answer both questions at the point of choice. The Patrol and shared
default model pickers pin guided models in a "Suggested for Patrol"
section with recommended / suggested / caution markers and a one-line
reason: the Ollama preflight blessing is the only recommended entry,
Gemini Flash-Lite the only caution, and cloud starting points are labelled
price-driven and unqualified until this install's own readiness pass
upgrades them to "Verified on this install". A server-computed cost
preview sits under the model choice: monthly estimate from Pulse's price
table, the schedule, and the install's own median full run once three
priced runs exist (a measured 104,528-in / 4,491-out run otherwise), with
the assumption stated and a token explained once, plus 30-day spend
against budget. Each schedule preset is priced in the Schedule select.

When a per-token model is picked while the schedule is still the 6-hour
default, the cost model proposes the slowest preset that keeps scheduled
runs under half the budget (20 USD reference when none is set) and says
what the slower check costs in detection delay; a schedule the install
already chose is never changed.

A used-up budget is now a spending decision, not a provider fault: the
budget refusal is a typed sentinel with the figures, classified as the
budget_exhausted cause, excluded from circuit-breaker accounting, and
promoted into the Patrol block state, so the Patrol page shows "Patrol
paused" with the spend and limit and a "Raise the cost budget" action
instead of "Check Patrol model".
2026-09-02 07:55:42 +01:00
pulse-triage[bot] c253495045 feat: add fleet health home
Change-source: pulse-maintainer
2026-09-01 01:02:17 +01:00
pulse-triage[bot] 0f2d7ae055 Make notification delivery failures actionable 2026-08-31 00:39:53 +01:00
Pulse Test 5d3571dbe8 Scope remaining provider inventory hydration 2026-08-30 00:42:18 +01:00
pulse-triage[bot] 3e66f042a5 Document shipped availability probe coverage
Change-source: pulse-maintainer
2026-08-29 18:21:50 +01:00
rcourtman ff507a6e44 feat(alerts): add per-alert snooze 2026-08-27 17:20:32 +01:00
rcourtman f4e1e47e74 feat(alerts): add external dead-man monitoring 2026-08-27 16:27:46 +01:00
rcourtman 9572a113ae feat(alerts): append-only alert event log with suppression reasons
Adds internal/alerts/eventlog: a SQLite-backed, additive event log that
records lifecycle transitions (resolved, acknowledged, unacknowledged,
escalated, flapping detected) and notification decisions (dispatched,
deferred by quiet hours, suppressed — with the mechanism that held
them). Appends never block alert evaluation: a full buffer drops the
event and counts the drop; a store that fails to open degrades to
recording nothing. 90-day retention, hourly prune.

The manager emits at the existing funnels only — dispatchAlert and the
safe-call resolve/ack/escalate seams — so no lifecycle behavior
changes. Lifecycle "fired" is deliberately not recorded yet: the
active-alert store funnel also runs on persisted restore, so firing
waits for the explicit activation seam in a later phase. The monitoring
bootstrap enables the log per manager; ephemeral managers and tests
record nothing unless they opt in.

GET /api/alerts/events (monitoring:read) reads the log with
alertIdentifier/type/since/limit filters, newest first.

Phase 0 of docs/ALERT_ENGINE_EVOLUTION.md (coverage gap
alert-engine-suppression-observability).
2026-08-26 21:15:37 +01:00
rcourtman 515aa319e8 feat(alerts): bulk delivery diagnosis for all active alerts
GET /api/alerts/delivery-diagnosis without alertIdentifier now returns
the diagnosis array for every active alert in one manager pass, so list
surfaces do not need a request per alert. Extracts the per-alert
diagnosis into a locked helper shared by both paths; single-alert
behavior is unchanged.

First slice of coverage gap alert-engine-suppression-observability
(docs/ALERT_ENGINE_EVOLUTION.md Phase 0).
2026-08-26 21:05:37 +01:00
rcourtman bce5564025 fix notification terminal failure recovery 2026-08-23 09:05:54 +01:00
rcourtman 903b579f8a Add durable Patrol objectives 2026-08-13 23:59:33 +01:00
rcourtman 3adeb77d60 Secure configuration transfer authorization (#1714)
Co-authored-by: Pulse Autonomous Maintainer <rcourtman@users.noreply.github.com>
2026-08-12 07:32:50 +01:00
rcourtman be18f99d24 fix(rbac): make SSO user access manageable 2026-08-09 13:36:07 +01:00
rcourtman 63fc89f701 Write the deep-dive docs the AI pages promised
AI.md, AI_AUTONOMY.md and PULSE_PRO.md linked nine times into
docs/architecture/, which .gitignore marks as not for public release. The
targets were never missing, they were deliberately unpublished, so every one
of those links was dead for anybody but the maintainer.

Write the three promised documents against the code and publish them under
docs/ where the rest of the shipped set lives.

PATROL_ARCHITECTURE.md covers a run end to end. The interesting part is that
deterministic signal detection runs after the model, not before, so unmatched
signals catch what the model failed to file. Signal types, the thresholds
those signals derive from your own alert settings, and every condition in
Finding.ShouldInvestigate are documented from internal/ai/patrol_signals.go
and internal/ai/findings.go.

ASSISTANT_SAFETY.md documents the session state machine in
internal/ai/chat/fsm.go, its four states, the tool classification it runs on,
and its invariants. No write without a validated target, no second write
before the first is verified, no final answer about an unverified change, and
no attempt count that wears the gate down.

ASSISTANT_ARCHITECTURE.md covers the agentic loop around that machine, the
three-phase pipeline, why only execution parallelises and at what cap, the
read-before-write batch that must stay ordered, the look-before-asking gate
and its bound, and the stable error codes.

Also removed two older pointers into the same private directory, in API.md
and UPGRADE_v6.md, and the two references to ENTITLEMENT_MATRIX.md in
PULSE_PRO.md. That file exists locally and is a monetization document, so
publishing it is not a documentation decision.

Unresolvable intra-doc links are now 2 of 218, both internal release-control
documents deliberately withheld from the shipped set.

Contract-Neutral: documentation only
2026-08-03 22:20:57 +01:00
courtmanr@gmail.com 51d4b12415 feat(notifications): route alerts by resource tags 2026-07-30 14:48:42 +01:00
courtmanr@gmail.com f985e616d6 Allow in-place API token scope edits (#1629) 2026-07-30 12:06:19 +01:00
courtmanr@gmail.com 28e884e33c docs: mark retired Docker update endpoints as 410 in the API reference
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 19:30:45 +01:00
rcourtman 8d23529c02 Fix availability check identity projection 2026-07-23 23:46:34 +01:00
rcourtman 0b6f7ad49a Fix notification delivery health reporting 2026-07-23 23:38:35 +01:00
rcourtman 615fa4418b Restore Docker agents to Hosts inventory 2026-07-23 22:06:56 +01:00
rcourtman 83672a00a8 Harden Operational Trust rollout contracts 2026-07-19 15:11:02 +01:00
rcourtman 69533c7afb Build canonical availability resource facets 2026-07-19 15:11:01 +01:00
rcourtman 07a4d05ca3 Build canonical Patrol attention workbench 2026-07-19 15:11:01 +01:00
rcourtman 93dbdd91cf Build canonical protection posture 2026-07-19 15:10:38 +01:00
rcourtman 12e6b74ae2 Document the -1 disk usage sentinel in the API reference
Guest disk percentages report -1 when a VM is stopped or its guest
agent is unavailable (issue #1569); consumers were treating it as a
real percentage.
2026-07-14 09:46:12 +01:00
rcourtman 4eac3b535c Accept ping alias for availability targets 2026-06-29 22:43:13 +01:00
rcourtman 597c369a63 Add alert delivery diagnosis endpoint 2026-06-29 22:27:01 +01:00
rcourtman 6b7d0b45f9 Add agent fleet diagnostics endpoint 2026-06-29 18:03:46 +01:00
rcourtman 73208a2863 Document state summary integration contract 2026-06-29 17:39:11 +01:00
rcourtman ee8a24e14a backend and governance: MCP contract, agent capabilities, API, and release-control
Manifest-backed MCP tools, prompts, and resources with surface affordance contracts; agent capability manifest and governance projection; API contract tests and capability route projection; operations-loop and intelligence-funnel telemetry; release-control subsystem documentation, registry, and tooling; licensing and configuration.
2026-06-23 17:26:15 +01:00
rcourtman 738fc4eb13 Reject unsupported Assistant session diff/revert 2026-06-07 18:01:55 +01:00
rcourtman 1afd708e5a Harden unified agent update preflight
Record the single pulse-agent product invariant and clarify Docker / Podman module terminology.
2026-06-02 23:54:48 +01:00
rcourtman e8b3c7fcf7 Restore remote config signature compatibility
Keep desired config fingerprints as response metadata derived from the signed command and settings payload.

Use merged agent profile settings when building remote config fingerprints.
2026-05-13 19:00:02 +01:00