mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-24 04:33:17 +00:00
Merge remote-tracking branch 'origin/main' into fix/sensor-setup-notice-names
This commit is contained in:
+7
-4
@@ -836,9 +836,12 @@ delivery for all resources.
|
||||
- `GET /api/admin/reports/catalog` (admin, `settings:read`)
|
||||
- Returns the canonical reporting catalog for the settings surface, including locked-state teaser copy, enabled-surface guidance copy, performance-report options, canonical single-report filename subject, canonical fallback filename date style, and the nested VM inventory export definition.
|
||||
- Metadata route: readable without the `advanced_reporting` feature so locked admin surfaces can render the same reporting definition before upsell.
|
||||
- `GET /api/admin/reports/generate` (admin, `settings:read`)
|
||||
- Query params: `format` (pdf/csv, default `pdf`), `resourceType`, `resourceId`, `metricType` (optional), `start`/`end` (RFC3339, optional; defaults to last 24h), `title` (optional)
|
||||
- `POST /api/admin/reports/generate` (admin, `settings:read`)
|
||||
- Body fields: `format` (pdf/csv, default `pdf`), `resourceType`, `resourceId`, `metricType` (optional), `start`/`end` (RFC3339, optional; defaults to last 24h), `title` (optional)
|
||||
- If `title` is omitted, the backend applies the canonical default title for that resource report.
|
||||
- When Pulse Assistant is configured, the configured AI provider narrates a PDF's executive summary; each narration counts against the AI budget and appears in AI usage. If the call fails, times out or the budget is spent, the PDF falls back to the deterministic summary. CSV output is never narrated. The settings UI uses this method.
|
||||
- `GET /api/admin/reports/generate` (admin, `settings:read`)
|
||||
- Takes the same fields as query params and returns the same report, except that it never calls an AI provider: a PDF carries the deterministic summary and a note saying AI narration needs `POST`. Other methods return `405` with `Allow: GET, POST`.
|
||||
- `POST /api/admin/reports/generate-multi` (admin, `settings:read`)
|
||||
- Body fields: `resources` (1-50 entries of `{resourceType,resourceId}`), `format`, `metricType` (optional), `start`/`end` (RFC3339, optional; defaults to last 24h), `title` (optional)
|
||||
- If `title` is omitted, the backend applies the canonical default fleet report title.
|
||||
@@ -851,8 +854,8 @@ Validation and limits:
|
||||
- Malformed `start`/`end` values, `end` not strictly after `start`, or report windows over 366 days return `400 invalid_time_range`.
|
||||
- `metricType` must match `[a-zA-Z0-9._:-]+` and be <= 64 chars, otherwise `400 invalid_metric_type`.
|
||||
- `title` must be <= 256 chars, otherwise `400 invalid_title`.
|
||||
- Multi-report body max size is 1MB; oversized payloads return `400 body_too_large`.
|
||||
- Multi-report bodies reject trailing payload and unknown JSON fields with `400 invalid_body`.
|
||||
- Report body max size is 1MB; oversized payloads return `400 body_too_large`.
|
||||
- Report bodies reject trailing payload and unknown JSON fields with `400 invalid_body`.
|
||||
- VM inventory export only accepts `csv`.
|
||||
|
||||
Common reporting error codes:
|
||||
|
||||
+4
-2
@@ -260,10 +260,12 @@ Each client runtime (or organization) generates its own reports, scoped to
|
||||
that client's resources:
|
||||
|
||||
- **UI**: Settings → Data & Reports.
|
||||
- **API**: `GET /api/admin/reports/generate` (single resource) and
|
||||
- **API**: `POST /api/admin/reports/generate` (single resource) and
|
||||
`POST /api/admin/reports/generate-multi` (up to 50 resources per report),
|
||||
returning PDF or CSV. In shared-process mode, scope with `X-Pulse-Org-ID`
|
||||
or an org-bound token.
|
||||
or an org-bound token. `GET /api/admin/reports/generate` with query
|
||||
parameters still returns a single-resource report, but never adds the
|
||||
Pulse Assistant narrative.
|
||||
- **Schedules**: `GET`/`POST /api/admin/reports/schedules`,
|
||||
`PUT`/`DELETE /api/admin/reports/schedules/{id}`, and
|
||||
`POST /api/admin/reports/schedules/{id}/run`. Schedules can target explicit
|
||||
|
||||
@@ -7665,6 +7665,16 @@ bounded and classified temperature nodes, and leaves the host report intact
|
||||
when the helper is absent or incompatible. It does not treat Windows ACPI
|
||||
thermal zones as hardware sensor evidence, accept remote provider locations,
|
||||
or grant Pulse hardware-control authority.
|
||||
### Single-resource report AI narration requires POST
|
||||
|
||||
`internal/api/metrics_reporting_handlers.go`, `internal/api/router.go` and
|
||||
`internal/api/ai_handlers.go` changed only how `/api/admin/reports/generate`
|
||||
reads its fields (a `POST` JSON body or `GET` query parameters), which of those
|
||||
may use the tenant's AI narrator (`POST` only), and how a `GET` finds Patrol
|
||||
findings (`ExistingAIService`, which never constructs a tenant AI service). No
|
||||
agent registration, enrolment, install, update, removal or report ingest path
|
||||
changed.
|
||||
|
||||
### Agent profile adoption is reported as a count only
|
||||
|
||||
The usage telemetry snapshot reads `LoadAgentProfiles` and reports
|
||||
|
||||
@@ -128,6 +128,25 @@ on component mount. Closing clears the registered input so later keyboard
|
||||
commands cannot target a detached composer. A handoff must leave Escape and
|
||||
keyboard input in Assistant, not the underlying alert search.
|
||||
|
||||
### Process-wide memory ID sequences
|
||||
|
||||
`internal/ai/memory` mints incident, incident-event, remediation-record and
|
||||
context-memory IDs from package-level sequences that every store in the process
|
||||
shares. Multi-tenant monitoring gives each tenant monitor its own
|
||||
`IncidentStore`, and their alert checks call `RecordAlertFired` from separate
|
||||
goroutines, while a store's mutex covers only its own state. Each sequence is
|
||||
therefore an `atomic.Int64` minted through `nextSequencedID` in
|
||||
`internal/ai/memory/ids.go`, never a plain package counter. The ID shape stays
|
||||
`<prefix><local second>-<n>`, but `n` no longer wraps at 1000. The old
|
||||
`counter%1000` suffix repeated an ID whenever one generator minted more than a
|
||||
thousand in a wall-clock second, and incident notes resolve an incident by ID
|
||||
while timeline merges deduplicate events by ID. Within one process IDs are now
|
||||
unique by construction. Across restarts only the timestamp separates them, so a
|
||||
repeated local second (DST fall-back or a backward clock step) can still meet a
|
||||
restarted sequence's suffix. Persisted IDs and the stored format are unchanged.
|
||||
`internal/ai/memory/ids_test.go` mints from every generator concurrently and
|
||||
records alerts into two tenant stores concurrently; run it under `-race`.
|
||||
|
||||
### Canonical incident-history queries
|
||||
|
||||
Incident context reads `IncidentStore.QueryIncidents`, whose page contains
|
||||
@@ -7516,6 +7535,18 @@ prompt budget. Reporting is therefore an additive consumer of AI
|
||||
runtime, not a new ownership boundary, and the narrator/findings
|
||||
surfaces inherit the same governance the rest of the canonical AI
|
||||
runtime already enforces.
|
||||
Report narration is caller-requested spend, so on the single-resource HTTP
|
||||
route only `POST /api/admin/reports/generate` reaches it (fleet reports, from
|
||||
`POST .../generate-multi` or a report schedule, use the fleet narrator). The
|
||||
reporting narrator resolver goes through `AISettingsHandler.GetAIService`,
|
||||
which constructs a tenant service on first use and can then list provider
|
||||
models or start background discovery. A GET single-resource report never calls
|
||||
that resolver:
|
||||
it narrates deterministically and reads Patrol findings through
|
||||
`AISettingsHandler.ExistingAIService`, which returns only a service that is
|
||||
already running and never constructs one
|
||||
(`TestAISettingsHandler_ExistingAIServiceNeverConstructs`, and through the
|
||||
router's real wiring `TestContract_SingleReportGETDoesNotConstructTenantAIService`).
|
||||
|
||||
The same canonical AI runtime now also owns the fleet-level report
|
||||
narrative through `report_fleet_narrator.go`. `Service` implements
|
||||
|
||||
@@ -4039,12 +4039,16 @@ the authoritative analysis outcome.
|
||||
`/api/settings/ai/update` may save a selected Patrol provider/model even
|
||||
when that model is not ready for tool-backed Patrol execution, but it must
|
||||
echo `patrol_readiness` with stable `cause` metadata and execution routes
|
||||
must continue to fail closed before model calls. Frontend Patrol settings
|
||||
must continue to fail closed before model calls. Frontend settings
|
||||
consumers must surface that saved-but-not-ready response as a saved
|
||||
configuration issue with the echoed provider, model, cause, and summary
|
||||
instead of reporting the successful save as a failed save or hiding the
|
||||
readiness blocker behind a generic notification; and the structured
|
||||
investigation-record contract, so unified findings may
|
||||
configuration issue rather than a failed save, and never as inline failure
|
||||
state: every Pulse Intelligence settings page (Provider & Models, Patrol,
|
||||
Assistant, Service context) raises a warning notification naming the echoed
|
||||
summary, provider, and model, while the Patrol page's on/off save raises a
|
||||
fixed not-ready warning and leaves the diagnosis to the page's own readiness
|
||||
surfaces, such as the readiness banner that names the summary, provider,
|
||||
and model while the header offers `Fix setup` when Patrol is active; and
|
||||
the structured investigation-record contract, so unified findings may
|
||||
expose `investigation_record` only through the shared
|
||||
`aicontracts.InvestigationRecord` payload shape, with frontend API types
|
||||
and backend contract tests updated in the same slice as any field change.
|
||||
@@ -9687,6 +9691,32 @@ narrator must fail closed: nil provider, parse failure, timeout, or
|
||||
empty response causes the engine to fall back to the heuristic
|
||||
narrative without surfacing the AI failure to the caller, so reporting
|
||||
is never blocked by AI availability.
|
||||
Only `POST /api/admin/reports/generate` may hand the tenant's AI narrator to
|
||||
the engine, because a narrated PDF makes a paid provider call and appends a
|
||||
`cost.UsageEvent` to the cost ledger. The single-resource handler accepts
|
||||
`POST` with a JSON body (the `generate-multi` decoding rules: 1MB cap, unknown
|
||||
fields and trailing payload rejected, through `decodeReportingRequestBody`) and
|
||||
`GET` with the same fields as query parameters, and answers any other method
|
||||
with `405` and `Allow: GET, POST`. GET never calls `resolveNarrator`, whose
|
||||
`GetAIService` can construct a tenant AI service (and with it list provider
|
||||
models or start background discovery): it narrates through `getReportNarrator`,
|
||||
the heuristic summary whose `Disclaimer` replaces the "Configure Pulse
|
||||
Assistant" tip with a note that AI narration needs POST, and reads Patrol
|
||||
findings through `SetExistingFindingsResolver`, which `router.go` backs with
|
||||
`AISettingsHandler.ExistingAIService` so only an already running service is
|
||||
used. 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 must not
|
||||
be able to spend AI budget. The reporting catalog advertises the transport as
|
||||
`performanceReport.singleResourceMethod` (`POST`), and the settings UI follows
|
||||
it. `generate-multi` and the manual schedule run endpoint
|
||||
(`POST /api/admin/reports/schedules/{id}/run`) were already POST-only; due
|
||||
schedules run from the server's scheduler, not an HTTP request.
|
||||
`TestContract_SingleReportAINarrationRequiresPOST` pins the rule on a real
|
||||
engine: GET, HEAD, PUT and DELETE make no narrator call and no AI service
|
||||
resolution, while POST makes one of each.
|
||||
`TestContract_SingleReportGETDoesNotConstructTenantAIService` drives
|
||||
`Router.wireReportingAIResolvers` with an uncached tenant: GET constructs no
|
||||
tenant AI service and POST resolves one.
|
||||
Report alerts keep the alert engine's resolution. The node, VM and
|
||||
container enrichers in `internal/api/metrics_reporting_handlers.go` build
|
||||
every row through `reportActiveAlertInfo` and `reportResolvedAlertInfo`,
|
||||
|
||||
@@ -6101,6 +6101,14 @@ summaries whose scope, format, and attachments the server fixes. The delivery
|
||||
surface (`/api/admin/reports/schedules`) and tenant-local storage are
|
||||
unchanged.
|
||||
|
||||
### Provider docs name POST for single-resource reports
|
||||
|
||||
`docs/MSP.md` and its shipped copy now give `POST /api/admin/reports/generate`
|
||||
for per-client single-resource reports and say a `GET` with query parameters
|
||||
still returns the report without the Pulse Assistant narrative, matching the
|
||||
server, which lets only `POST` narrate. Install, upgrade and provider bundle
|
||||
delivery are unchanged.
|
||||
|
||||
### Convergence log-reader compatibility (5 September 2026)
|
||||
|
||||
Scheduled reconciliation run 33939926237 failed when `gh api` refused terminal
|
||||
|
||||
@@ -3164,33 +3164,30 @@ Agent`), with the plain-language source phrase available through accessible
|
||||
operator action or selected Patrol context, uses distinct monitor labels,
|
||||
and does not become a nested card, generic dashboard strip, trust summary,
|
||||
or duplicate Patrol empty-work list. The
|
||||
Patrol schedule and model drawer is part of that shared
|
||||
feature-presentation boundary: it must stay viewport-bounded, expose an
|
||||
accessible dialog label, keep the four-level control policy on the default
|
||||
Patrol header, and keep provider model, schedule, trigger tuning, and
|
||||
readiness validation inside the secondary disclosure. Backend save rejection reasons must pass
|
||||
through as inline dialog state instead of being replaced with generic toast
|
||||
copy, and that advanced disclosure must open when the inline state exists.
|
||||
When the failure includes
|
||||
Patrol readiness context, the inline state must expose the provider, model,
|
||||
and readiness summary next to a direct provider-settings action instead of
|
||||
hiding that diagnosis behind Assistant alone. The provider-model selector in
|
||||
that popover must stay bound to the shared runtime settings/model catalog
|
||||
even when the popover mounts after async catalog loading, but the full
|
||||
catalog must stay behind an explicit change action so the default advanced
|
||||
drawer leads with the current effective model summary rather than a raw
|
||||
provider route list. A saved direct-provider Patrol model still renders as
|
||||
that model instead of visually falling back to the default selection.
|
||||
Successful provider-model saves that return a not-ready Patrol
|
||||
readiness snapshot must use that same inline surface with `needs attention`
|
||||
wording, and any Assistant handoff from that state describes a saved
|
||||
configuration issue rather than a failed save. When governed fixes are locked, the same Patrol state
|
||||
owner must clear stale full-mode unlock state before persisting the
|
||||
monitor-only autonomy payload, so disabled paid controls cannot leak stale
|
||||
permission into a save. If that inline state opens Assistant, the Patrol
|
||||
feature must hand off
|
||||
a source-named, model-only briefing and close the popover so the shared
|
||||
Assistant drawer is not visually hidden behind feature chrome. When a
|
||||
Patrol page keeps the four-level control policy on its header and has no
|
||||
schedule and model drawer: schedule, trigger tuning, and the Patrol model
|
||||
override live on Settings > Pulse Intelligence > Patrol, and providers and
|
||||
the shared default model on Settings > Provider & Models. Those saves
|
||||
report through the shared notification toasts, not inline state: a
|
||||
rejected save raises an error toast carrying the backend's concrete reason
|
||||
rather than generic fallback copy, and a saved response whose Patrol
|
||||
readiness is not ready or degraded raises a warning toast naming the
|
||||
readiness summary, provider, and model instead of a success toast. On the
|
||||
Patrol page, an on/off save whose response reports Patrol not ready raises
|
||||
a fixed warning toast, and a failed Patrol mode save raises an error toast
|
||||
and restores the previous mode; neither renders inline save-failure state
|
||||
or offers an Assistant handoff. If a Patrol settings popover or inline
|
||||
save-failure state returns, it must stay viewport-bounded, expose an
|
||||
accessible dialog label, bind any model selector to the shared runtime
|
||||
settings and model catalog, show readiness context (provider, model,
|
||||
summary) next to a direct provider-settings action rather than behind
|
||||
Assistant alone, and describe a saved but not ready response as a saved
|
||||
configuration issue rather than a failed save. An Assistant handoff from it
|
||||
must send a source-named, model-only briefing and close the popover so the
|
||||
shared Assistant drawer is not visually hidden behind feature chrome. When
|
||||
governed fixes are locked, the Patrol state owner must clear stale
|
||||
full-mode unlock state before persisting the monitor-only autonomy payload,
|
||||
so disabled paid controls cannot leak stale permission into a save. When a
|
||||
restored Patrol assessment session is attached, the shared Assistant drawer
|
||||
empty state must
|
||||
stay aligned with that source-named briefing and must not render generic
|
||||
@@ -4848,6 +4845,13 @@ performance-report transport (`/api/reporting` and `/api/reporting/generate-mult
|
||||
so the reporting panel does not go dead on mixed-version installs, but that
|
||||
compatibility path is intentionally report-only and must not invent the newer
|
||||
catalog-owned VM inventory export surface.
|
||||
`reportingPanelModel.ts` picks the single-resource transport from the catalog's
|
||||
`performanceReport.singleResourceMethod`: `POST` sends the request fields as a
|
||||
JSON body, which is the only transport the server lets narrate a PDF with Pulse
|
||||
Assistant, and `GET` sends the same fields as query parameters.
|
||||
`reportingCatalogModel.ts` reads a missing field as `GET`, the only method a
|
||||
backend that predates the field accepts, and the legacy compatibility catalog
|
||||
names `GET` for `/api/reporting`. Fleet reports stay `POST` on every backend.
|
||||
`ReportingPanel.tsx` must therefore treat `vmInventoryExport` as optional when
|
||||
it renders a governed reporting catalog. A legacy compatibility catalog with no
|
||||
inventory export still owns a valid enabled reporting surface and must continue
|
||||
|
||||
@@ -2699,7 +2699,18 @@ truthfulness, not native thaw, containing-release or workload acceptance.
|
||||
ingestion so canonical host IDs stay stable across restarts (see the
|
||||
unified-resources contract's durable identity-pin obligation). Rebuild
|
||||
paths added to the adapter must keep that persistence step; ephemeral
|
||||
snapshot-bridge adapters stay read-only.
|
||||
snapshot-bridge adapters stay read-only. The rebuild, the live
|
||||
supplemental refresh and the read-state overlay ingest records with the
|
||||
adapter's configured stale thresholds, because record ingest joins
|
||||
operator links and the freshness gate of every metric merge, a link's or
|
||||
a source's into an existing row, reads them (unified-resources contract,
|
||||
"Operator links reach record-ingested resources"). Regression
|
||||
coverage:
|
||||
`TestMonitorAdapterJoinsLinkedRecordsWithConfiguredStaleThresholds` and
|
||||
`TestMonitorAdapterSourceMergesUseConfiguredStaleThresholds` in
|
||||
`internal/unifiedresources/monitor_adapter_read_state_test.go` and
|
||||
`TestManualLinkToSupplementalGuestHoldsWithAndWithoutContinuity` in
|
||||
`internal/monitoring/issue1913_host_continuity_test.go`.
|
||||
|
||||
11. The TrueNAS provider projects pools with `Storage.Topology` fixed to
|
||||
`pool` and the ZFS data vdev layout in `Storage.VDevLayout`. The
|
||||
|
||||
@@ -1581,17 +1581,18 @@ suppress stale healthy summary headlines such as `Health A · 100/100` even if
|
||||
the last summary payload still looks healthy. Legacy `/ai` entry points must
|
||||
redirect into that same Patrol-owned shell rather than preserving a second
|
||||
canonical route.
|
||||
That same browser proof now covers the Patrol control and advanced-settings
|
||||
split. The advanced Patrol settings drawer must stay within the desktop
|
||||
viewport, avoid duplicating the inline Patrol control policy, expose
|
||||
provider/model, schedule, trigger, and user-level model checks directly, and surface
|
||||
the backend's concrete license/validation reason when a settings change is
|
||||
rejected instead of replacing it with a generic `Failed to save advanced
|
||||
settings` toast. That inline failure no longer
|
||||
opens Assistant. If it does again, the handoff is model-only explanation
|
||||
context: raw command, script, credential, and provider-detail payloads stay
|
||||
redacted, Assistant opens with `autonomousMode:false`, and the Patrol control
|
||||
panel closes so the operator is not left behind an overlapping popover.
|
||||
That same browser proof now covers where Patrol settings are saved. The
|
||||
Patrol page has no settings drawer or popover: Patrol schedule, triggers, and
|
||||
the Patrol model override live on Settings > Pulse Intelligence > Patrol
|
||||
(`Save Patrol settings`), and providers and the shared default model on
|
||||
Settings > Provider & Models. A rejected save there raises an error
|
||||
notification carrying the backend's concrete license/validation reason
|
||||
instead of generic fallback copy, and it offers no Assistant handoff. If a
|
||||
Patrol settings popover or an Assistant handoff from a settings failure
|
||||
returns, the handoff is model-only explanation context: raw command, script,
|
||||
credential, and provider-detail payloads stay redacted, Assistant opens with
|
||||
`autonomousMode:false`, and the popover closes so the operator is not left
|
||||
behind it.
|
||||
Patrol-control save-failure sessions saved by earlier builds keep the
|
||||
compatible `handoff_metadata.kind=patrol_configuration_failure` plus only the
|
||||
runtime-failure boolean needed for drawer/session presentation, so they restore
|
||||
@@ -1637,12 +1638,13 @@ ready raises a fixed warning notification, a failed Patrol mode save raises an e
|
||||
notification, and neither offers an Assistant handoff. A provider or model save
|
||||
in Settings > Provider & Models reports the readiness summary, provider and
|
||||
model in its own save message.
|
||||
The Patrol mode selector in that header and configuration dialog must
|
||||
compose the shared `frontend-modern/src/components/shared/FilterButtonGroup.tsx`
|
||||
instead of rebuilding a local active-button group. The wide default Patrol
|
||||
header uses the segmented layout; the constrained configuration dialog may use
|
||||
the shared prominent layout so all four mode labels remain readable without
|
||||
inventing a Patrol-local selector. Patrol owns the default visible four-level
|
||||
The Patrol mode selector in that header must compose the shared
|
||||
`frontend-modern/src/components/shared/FilterButtonGroup.tsx` instead of
|
||||
rebuilding a local active-button group. When the header shows Patrol mode
|
||||
options, it renders the selector in the segmented layout inside its `Mode and
|
||||
automation` disclosure; a constrained surface that hosts it may use the shared
|
||||
prominent layout so all four mode labels remain readable without inventing a
|
||||
Patrol-local selector. Patrol owns the default visible four-level
|
||||
policy presentation (`Watch only`, `Ask first`, `Safe auto-fix`,
|
||||
`Autopilot`), entitlement locks, and the rule that choosing the highest
|
||||
Autopilot level sends `full_mode_unlocked:true` while choosing any lower level clears that
|
||||
|
||||
@@ -3025,6 +3025,18 @@ Removing the disconnected recorder does not alter alert, action approval or
|
||||
operator authority. An unrelated organization receives no default archive
|
||||
fallback.
|
||||
|
||||
### Report GETs cannot spend AI budget
|
||||
|
||||
`Router.wireReportingAIResolvers` in `internal/api/router.go` wires a second
|
||||
reporting resolver, `SetExistingFindingsResolver`, backed by
|
||||
`AISettingsHandler.ExistingAIService`.
|
||||
A `GET /api/admin/reports/generate` uses only that resolver and the
|
||||
deterministic narrator, so it makes no AI provider call, writes no cost-ledger
|
||||
entry and never constructs a tenant AI service. GET passes the demo-mode guard
|
||||
and the CSRF check, and a SameSite=Lax session cookie rides a cross-site
|
||||
top-level navigation, so AI narration of a single-resource report now requires
|
||||
a CSRF-checked `POST`. No authentication, scope or session rule changed.
|
||||
|
||||
### Node setup action v7 compatibility
|
||||
|
||||
Workflow consumers pin `actions/setup-node` to
|
||||
|
||||
@@ -6265,6 +6265,19 @@ is in `applyLicensedFeatureConfigSnapshot`
|
||||
(`pkg/server/telemetry_licensed_features.go`), pinned by
|
||||
`TestApplyLicensedFeatureConfigSnapshot_CountsScheduledReportingAndProfiles`.
|
||||
|
||||
### Single-resource report AI narration requires POST
|
||||
|
||||
`internal/api/metrics_reporting_handlers.go` now accepts `POST` with a JSON
|
||||
body for `/api/admin/reports/generate` and lets only that method use the
|
||||
tenant's AI narrator; `GET` keeps working with query parameters and the
|
||||
deterministic summary. A narrated PDF records the same AI cost-ledger entry as
|
||||
before, and only a `POST` can now produce one. A `GET` reads Patrol findings
|
||||
only from an AI service that is already running
|
||||
(`AISettingsHandler.ExistingAIService`), so it no longer constructs a tenant AI
|
||||
service, whose construction opens that tenant's SQLite resource export store.
|
||||
Report content, backup and recovery enrichment, and tenant storage are
|
||||
otherwise unchanged.
|
||||
|
||||
### Audit-read activity is a bounded, content-free local history
|
||||
|
||||
`audit_read_activity.json` follows the same shape as the existing external-agent
|
||||
|
||||
@@ -1609,7 +1609,12 @@ Source freshness is a separate unified-resource status input, not a fixed
|
||||
global health timer. Snapshot rebuilds, resource seeding, supplemental record
|
||||
overlays, and cloned read-state overlays must preserve the monitoring-provided
|
||||
stale thresholds for Proxmox, PBS, and PMG sources so resources do not flap to
|
||||
warning/degraded between successful configured poll cycles.
|
||||
warning/degraded between successful configured poll cycles. A source merge
|
||||
into an existing row judges metric freshness by the ingest's thresholds and
|
||||
the stale pass's own threshold rule, so the merge's freshness gate never
|
||||
calls a source current that the stale pass marks stale, or the reverse. The
|
||||
gate decides only where no stronger rule does: a hypervisor-managed guest
|
||||
keeps its platform CPU even while that source is stale.
|
||||
|
||||
Service-discovery readiness is a unified-resource payload contract, not a
|
||||
drawer-local decoration. Resource list/detail payloads that expose a
|
||||
@@ -2813,7 +2818,9 @@ application resource-provider or WebSocket lifecycle.
|
||||
ID and drops the superseded pin row (`ApplyCanonicalIDSuccessions`
|
||||
in `internal/unifiedresources/canonical_id_succession.go`).
|
||||
Successions never fire while the old ID still belongs to a live
|
||||
resource, never follow a contradicting machine key (a reinstalled
|
||||
resource or to one a manual link folded into its primary (see
|
||||
"Operator links reach record-ingested resources"), never follow a
|
||||
contradicting machine key (a reinstalled
|
||||
machine mints fresh, it does not absorb the old host's operator
|
||||
state), and never rewrite change-journal rows. Known limitation:
|
||||
machine-keyless hosts whose history already merged under a collapsed
|
||||
@@ -2894,8 +2901,9 @@ application resource-provider or WebSocket lifecycle.
|
||||
`ResourceRegistry.IngestRecords` applies the same
|
||||
`ApplyCanonicalIDSuccessions` semantics as pin-driven successions
|
||||
(operator state and action audits re-key, the superseded pin row
|
||||
drops, never while the old ID belongs to a live resource, journal
|
||||
rows never rewritten). Regression coverage:
|
||||
drops, never while the old ID belongs to a live resource or one a
|
||||
manual link folded into its primary, journal rows never rewritten).
|
||||
Regression coverage:
|
||||
`TestRegistryIngestRecordsKeepsSameHostnameSystemsDistinct` /
|
||||
`TestIngestRecordsSucceedLegacyHostnameScopedCanonicalIDs` /
|
||||
`TestIngestRecordsDoNotCompleteTrueNASIdentityFromPins` in
|
||||
@@ -6596,6 +6604,75 @@ full heading. This shared drawer rule applies across resource types. The 390px
|
||||
browser qualification checks heading fit and keyboard expansion; no data model,
|
||||
authorisation, action dispatch or resource admission contract changes.
|
||||
|
||||
### Operator links reach record-ingested resources
|
||||
|
||||
Every registry ingest path applies the operator's manual links: snapshot
|
||||
ingest, resource ingest and record ingest (`IngestRecords`,
|
||||
`IngestRecordsWithStaleThresholds` and the read-state overlay's record pass).
|
||||
Either side of a link can arrive only as a supplemental record, such as a
|
||||
vSphere or TrueNAS VM with a Pulse agent inside, or an agent on a TrueNAS host,
|
||||
and the monitor's rebuild ingests those records after the snapshot. While only
|
||||
snapshot and resource ingest applied links, the rebuilt registry behind the
|
||||
websocket broadcast and `/api/state` kept both rows. The resources API, which
|
||||
seeds through resource ingest, showed them merged, and so did the published
|
||||
inventory whenever an unrelated absent machine made continuity hydration
|
||||
clone the registry. Mock mode builds its unified view from fixtures without
|
||||
the link store, so links do not reach the mock broadcast; that boundary is
|
||||
separate.
|
||||
|
||||
- The monitor adapter passes its configured stale thresholds to record
|
||||
ingest, so a link joined there judges each side's metrics the way a
|
||||
snapshot-time link does. Ordinary source merges into an existing row use
|
||||
the same thresholds on every ingest path: snapshot and record ingest hold
|
||||
the caller's thresholds while they run, and `mergeInto` judges each
|
||||
metric's freshness with them through `sourceStaleThreshold`, the rule the
|
||||
stale pass reads. Before this, those merges used the defaults, so a
|
||||
Proxmox node polled every two minutes showed a silent auto-linked agent's
|
||||
readings while the stale pass called the node's poll current, and a
|
||||
supplemental refresh of a linked vSphere VM kept the agent's memory over
|
||||
vSphere's current reading. Outside mock mode the monitor configures only
|
||||
Proxmox, PBS and PMG; mock mode also configures TrueNAS, vSphere and
|
||||
availability. Regression coverage:
|
||||
`TestMonitorAdapterSourceMergesUseConfiguredStaleThresholds` (rebuild,
|
||||
supplemental refresh, overlay, node with auto-linked agent),
|
||||
`TestSourceMergesJudgeFreshnessByTheIngestStaleThresholds` and
|
||||
`TestMetricMergeFreshnessMatchesTheStalePass`.
|
||||
- Continuity records are the one record ingest that never joins links. A
|
||||
saved enrollment only fills an absent machine, so it stays its own row
|
||||
instead of lending its linked guest an offline verdict and an old agent
|
||||
payload.
|
||||
- A link merge keeps the TrueNAS and vSphere payloads the primary lacks, so
|
||||
an agent chosen as primary over a TrueNAS system still carries the
|
||||
system's facet, and the system's pools re-parent to the merged row.
|
||||
- The registry remembers each resource a manual link folded into its
|
||||
primary. A folded resource is still observed, so neither pin-driven nor
|
||||
record-declared succession may succeed its ID. An agent-type primary
|
||||
carries the folded resource's machine keys and is pinned with them, and
|
||||
succeeding the folded ID re-keyed the link onto the primary itself, which
|
||||
split the pair on the next rebuild. That already broke snapshot links whose
|
||||
primary is a machine-keyless agent-type row, such as a Proxmox node.
|
||||
- Fold history is registry-local: resource seeding and overlay clones do not
|
||||
carry it. A reference to the folded resource's canonical ID, such as an
|
||||
availability check configured against an agent later linked into its
|
||||
guest, does not resolve to the primary, so the check loses its target in
|
||||
the broadcast as it already did in the resources API.
|
||||
|
||||
`registry_merge_policy_test.go` pins the record links in both directions
|
||||
through two rebuilds and the live supplemental refresh, and checks that a
|
||||
registry seeded from the monitor's listing through resource ingest, as the
|
||||
resources API seeds, lists the same rows
|
||||
(`TestManualLinksJoinRecordIngestedResourcesInMonitorRebuild`). The same file
|
||||
pins the node case (`TestManualLinkKeepsItsEndpointsThroughIdentityPinPersistence`),
|
||||
record-declared succession (`TestRecordDeclaredSuccessionSparesALinkFoldedResource`)
|
||||
and both payloads (`TestManualLinkKeepsProviderPayloadsThePrimaryLacks`).
|
||||
`monitor_adapter_read_state_test.go`
|
||||
(`TestMonitorAdapterJoinsLinkedRecordsWithConfiguredStaleThresholds`) pins the
|
||||
thresholds on the rebuild, supplemental and overlay paths, and
|
||||
`internal/monitoring/issue1913_host_continuity_test.go`
|
||||
(`TestManualLinkToSupplementalGuestHoldsWithAndWithoutContinuity`) pins the
|
||||
published inventory with no continuity, an unrelated absent machine and the
|
||||
linked agent's own saved enrollment.
|
||||
|
||||
### Provider link network corroboration
|
||||
|
||||
An existing reciprocal host/provider link remains authoritative, including a
|
||||
|
||||
Reference in New Issue
Block a user