Preserve selected memory freshness and the VM/LXC interpretation.
Retain both native tool contracts and current API concurrency controls.
Qualify this source separately from release and installed recovery.
Contract-Neutral: Integrate already-governed parents without a new contract change. Incoming verification files already match main. Fresh integration tests and source-bound browser proof cover the combined memory and installer surfaces.
The organization-owner fix judged browser sessions only. API tokens on
instance routes were still checked for scope alone, and the tenant
middleware resolves an org-bound token to its own organization without
the caller naming one. A token bound to a tenant organization and
holding settings:write therefore reached system settings, SSO
providers, token management, relay, log level, updates and the other
instance surfaces.
ensureAdminSession, behind RequireAdmin, and ensureSettingsScope now
refuse a token unless it acts for the instance: not bound to any
organization other than the default one, with OrgIDs taking precedence
over OrgID. The route's scope still applies. The org variants
(RequireOrgAdmin, ensureOrgAdminSession, ensureOrgSettingsWriteScope)
keep judging tokens by scope alone. tokenHasInstanceWideAuthority is
now the same binding rule plus full-access scope.
The test notification broadcasts only to the request organization, so
it moves to RequireOrgAdmin. Its tenant delivery test already relied on
an org-bound token sending it, and that organization's managers may now
send it from a session too.
No path an organization manager can reach mints a settings scope: relay
mobile, install, deploy and action-runner tokens carry relay, agent and
report scopes only, and token management is instance administration.
The API reference and the api-contracts, security-privacy,
agent-lifecycle and storage-recovery contracts record the token rule.
Send draft verification and fingerprint settings to the saved-credential
probe so Test connection reflects the edit form before saving. Preserve
saved defaults for callers without overrides and keep probe state temporary.
Keep shared footer actions visible on narrow screens so the edit and test
flow remains usable through pending, error and confirmation states.
Retain both independent diagnostics and runtime-switch contract additions, preserving the reviewed cc8514de tip and exact upstream aa9548cf ancestry.
Change-source: pulse-maintainer
A paid key activated on the public community build unlocks only the
licence-gated features compiled into that build. Patrol modes, container
update risk, Audit, RBAC and Reporting need the private Pulse Pro
runtime, and the only way to get it was the download portal. Licence
data from 14 October shows 6 of 36 v6 buyers and 39 of 109 migrated v5
licences still running only the community build.
GET /api/updates/pro-runtime reports whether this install can switch,
and POST /api/updates/pro-runtime/apply performs it. The license server
download broker decides eligibility from the activation's licence, the
same broker the Pro binary already updates from, so a refusal there
reads as not_eligible and any other failure as unavailable. Without an
activation the check answers not_activated without a network call.
The in-place switch is limited to systemd and Proxmox LXC installs. It
runs through ApplyUpdate with an explicit SwitchToProRuntime flag, so
ordinary community updates never take the broker path. It accepts the
Pro build of the running version or newer, refuses an older pin, and
keeps the signed-archive, hash, self-test, backup and history-rollback
steps of a normal update. Docker installs get the broker's
digest-pinned image commands instead. The Settings card that uses these
routes follows separately.
The private-response helper promises one request, but real curl expands quoted brace lists and bracket ranges into two or three requests, including POST, PUT and DELETE. Disable URL globbing without changing token-file transport, response privacy, scopes, TLS, deadlines or no-retry rules. Explain the literal-path and separate URL-encoding boundary.
Exercise the copied helper with both authentication headers, every supported method, encoded identifiers and adverse write responses. Add consuming desktop and narrow Docs/router verification.
Contract-Neutral: Documentation and synthetic recipe/browser checks only; API authentication, endpoint and runtime contracts are unchanged.
Change-source: pulse-maintainer
Compose current main e2f4bdff03 additively. Retain both subsystem records and the held change-age control while withdrawing only the obsolete Discovery identification test. Reviewed identities and ancestry, canonical guest History ownership and upstream feature removal remain intact.
Change-source: pulse-maintainer
AI Discovery ran a separate model-backed service scanner beside Patrol
and the Assistant: its own store, scheduler, model route, API, tool and
drawer UI. No telemetry counter ever measured its use (the telemetry
discovery_enabled flag OR'd it with network subnet discovery), the
maintainer judged it a flop, and the context it produced is already
available to the models through pulse_query, pulse_read and the
unified-resource context sections.
This deletes internal/servicediscovery, internal/ai/infradiscovery, the
pulse_discovery tool, the /api/discovery routes, the mock Discovery
fixtures, the Discovery tabs in the resource, guest, node and Docker host
drawers, the identified-service cards, readiness badges, provenance
markers, suggested web-interface URLs, the Workloads AI Context column,
the chat "Discovery is off" hint and the AI Service Context settings page.
The old settings route redirects to Assistant settings. The model-bound
sanitizer allow-list existed only to pass Discovery context through, so it
goes too, along with the CopyableCodeRow primitive that only the
Discovery tab used.
Network subnet discovery (/api/discover, the Infrastructure discovery
settings) and the discoveryTarget agent coordinates are separate features
and stay. Stored discovery_enabled, discovery_interval_hours and
discovery_model AI settings are ignored on load; the settings API decoder
is lenient, so older frontends saving those fields still succeed.
Reconcile the overlapping single and fleet narration documentation with current upstream route assertions, retaining CSV refusal and the actual shared ai:execute guard. Record the incorporated diagnostics token visibility boundary in its dependent contracts without changing runtime authority.
Change-source: pulse-maintainer
Preserve every accepted maintenance commit while incorporating human-authored diagnostics token visibility. Resolve API guide overlap by retaining the shared ai:execute narration gate, explicit deterministic fallback and CSV refusal in both mirrors.
Change-source: pulse-maintainer
Follows the token authority fixes. The API reference now says:
- agent uninstall is a collector's own-token removal, or an admin session;
- AI narration of reports needs ai:execute from API-token callers, for
single and fleet reports, and otherwise falls back to the deterministic
summary;
- API-token callers manage only the tokens in their own organizations,
and only an instance-wide token can create, edit, rotate or delete
another instance-wide token;
- quick setup that would change credentials needs an instance-wide
token from API-token callers.
Adds the missing entry for POST /api/security/tokens/<id>/rotate, which
the regenerate entry already points scoped tokens to.
test_api_reference_routes.py pins each statement to the code that
enforces it, so the reference and the gates cannot drift apart. Each pin
fails when the code or the reference is reverted.
Contract-Neutral: Customer docs corrected to match shipped behaviour; no runtime, API or contract change.
Keep generation's read scope distinct from the ai:execute permission needed for single-resource and fleet PDF narration. Preserve the bounded PR2836 runtime guard unchanged, mirror the canonical API/MSP help into shipped assets, and record the actual contract including token-triggered versus server-run schedules. Replace the old unconditional-POST source assertion with both method and spending-scope checks, and cross GET with all five caller types. The production Docs browser fixture checks the public guidance on desktop and touch phone without API requests or mutations.
Change-source: pulse-maintainer
Follows the token scope fixes. POST /api/security/regenerate-token now
needs an instance-wide full-access token from API-token callers and
replaces every stored token; the reference says so and points scoped
tokens at the per-token rotate route. POST /api/resources/{id}/link,
/unlink and /report-merge need monitoring:write.
test_api_reference_routes.py pins each of these to the code that
enforces it, so the reference and the gates cannot drift apart again.
Each pin fails when the code or the reference is reverted.
Contract-Neutral: Customer docs corrected to match shipped behaviour; no runtime, API or contract change.
The API reference marked the Patrol stream, manual run, acknowledge,
snooze, suppress, suppressions and dismissed routes "(Pro)" and promised
a 402 when unlicensed. None of them is licence-gated: Patrol and
managing its findings are free (ai_patrol is a free feature, and the
frontend offers these controls on every plan). Only governed fixes need
Pro. The routes now show the scope they actually require and the
reference says what Community gets, including the one-full-run-per-hour
cap on manual runs.
Also corrects the findings routes: reinvestigate is Pro and needs the
ai_autofix entitlement and a Patrol mode above Watch only, and
reapprove is retired and always answers 410.
scripts/tests/test_api_reference_routes.py gains checks that a Patrol
route documented without a paid label has no licence wrapper in its
registration, and that reapprove is documented as retired. Each fails
when its claim is reverted.
Contract-Neutral: Customer docs corrected to match shipped behaviour; no runtime, API or contract change.
Audited every HTTP route the server registers against docs/API.md.
Where a documented route and the code disagreed, or an integrator-facing
route was missing, the reference now matches the code.
- Auth and scope labels: notifications, SSO providers, license
activate/clear, security reset-lockout, availability targets, agent
profiles (every method needs settings:write for tokens), relay settings
and the resource metadata routes (no admin requirement).
- Behaviour: POST /api/notifications/test targets one channel per request,
the storage detail route is GET /api/storage/{storageId} (not a list at /api/storage/), token
rename-only updates are accepted, validate-token moved out of the Audit
Log (Pro) section because it has no licence gate, and the setup script
is public.
- Retired routes: /api/ai/remediation/execute and /rollback always answer
410 Gone, as approve already documented.
- Newly documented: GET /api/ai/patrol/runs/{id}, POST
/api/availability-history, POST /api/agents/agent/{id}/allow-reenroll,
PUT /api/truenas/connections/{id} and its /test route.
scripts/tests/test_api_reference_routes.py pins the corrections to the
router so later drift fails a test: every scope named on a route line must
exist in pkg/auth/scopes.go, the newly documented routes must exist in the
router, and the retired routes must say 410. It fails on the previous
reference and passes on this one.
Each doc is mirrored under frontend-modern/public/docs and updated in
both places. Labels where the docs and the code disagree about licence
gating are left alone; that needs a product decision, not a doc edit.
Contract-Neutral: Customer docs corrected to match shipped behaviour; no runtime, API or contract change.
Audited the customer-facing docs against the code: about 5,200 concrete
claims (env vars, endpoints, flags, defaults, ports, UI labels), plus every
environment variable the code reads. Each discrepancy was re-verified
against the source before editing, and every edit was reviewed again.
What users would have hit following the old text:
- UI paths and labels that no longer exist (Settings > Relay, Platform
connections, Access Control, SSO Providers, Create Organization) and
Patrol mode names (now Ask first, Safe auto-fix, Autopilot).
- Wrong values and behaviour: LOG_MAX_AGE=0 does not disable cleanup,
Patrol evidence budget is 10 calls and 12 turns, ALLOW_UNPROTECTED_EXPORT
covers private networks too, PULSE_AUDIT_ASYNC defaults on.
- Agent install: install.sh ignores PULSE_ENABLE_KUBERNETES and
PULSE_ENABLE_PROXMOX, rejects --disable-auto-update, and the token
fallback lives in the state directory.
- API: audit endpoints answer 402 on Community, onboarding QR is GET,
type=container is rejected, several routes need admin plus a scope.
- HTTP_REDIRECT_PORT was not documented anywhere.
Docs are mirrored under frontend-modern/public/docs, so each change is
applied to both copies. The tests that pinned the old wording (including
three that asserted claims the code contradicts) are updated alongside.
Contract-Neutral: Customer docs corrected to match shipped behaviour; no runtime, API or contract change.
Replace root-password and implicit-TLS examples with a private JSON-file recipe using the existing bounded helper. Distinguish live tests, persisted changes, platform credentials and uncertain results; exercise copied commands with synthetic success, denial, redirect and incomplete responses.
Contract-Neutral: Documentation, mirrored guidance and copied-recipe regression tests only; no runtime API, authentication, TLS or frontend contract changes.
Change-source: pulse-maintainer
Reuse the existing private-response API helper for all role and user operations, preserving exact methods, bodies, privileges and targets. Document independent decisions and uncertain mutation readback; exercise copied commands against synthetic private success, denial, redirect and incomplete responses without exposing credentials or retrying.
Contract-Neutral: Documentation, mirrored guides and copied-recipe regression tests only; no runtime authentication, RBAC, API or frontend behaviour changes.
Change-source: pulse-maintainer
Additively integrate exact upstream 1a094646d7 while retaining starting reviewed 19a8358a49. Keep reviewed UUID incident/event IDs and upstream non-wrapping atomic sequences for other memory IDs. Compose upstream shared SMART/Unraid temperatures with reviewed pending context and observation-gap interruption; pin Unraid-only continuity and both grace paths. Retain all browser, completion and security validation limits until evidenced.
Change-source: pulse-maintainer
## 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.
Preserve exact upstream 23bf99e1ba and all reviewed local history. Resolve the sole agent-lifecycle contract conflict by retaining both independent accounts; no frontend source resolution is required. Bound this transaction to the upstream frontier recorded at batch start.
Change-source: pulse-maintainer
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.
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.
Close the token-response fence that swallowed the Audit Log heading and first endpoint, and route migration recovery's safe-reporting link to the existing Getting Help section. Check actual rendered fragment destinations across all shipped guides, including nested documents, rather than accepting headings that only exist inside code blocks.
Contract-Neutral: Documentation rendering inputs and navigation regression tests only; frontend runtime, API behaviour, guest operations, storage, permissions and delivery controls are unchanged.
Change-source: pulse-maintainer
Distinguish legacy sentinels, omitted metrics and retained observations. Bind selected memory provenance and ambiguous guest-agent errors to the existing safety guidance, with rendered link and regression controls.
Change-source: pulse-maintainer
Contract-Neutral: Documentation and renderer controls describe existing guest metric omission, provenance and safety semantics; no API shape or runtime behaviour changes.
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
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
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
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
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
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
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
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
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
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
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.
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
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.
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".
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).
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).