Consolidate shared contracts and retire obsolete obligations (#2904)

Shared rules were repeated across subsystem contracts, and some still
required the deleted Discovery scanner or superseded proofs. Keep each
shared rule at its canonical owner with scoped consumer links, retain
local differences, and remove obligations whose runtime or verification
path no longer exists.

Correct the stale temperature-padding expectation, trusted-command
producer description, telemetry adoption fields and proof names against
current source. Qualification stays with the existing customer-journey
document. Assistant, all four Patrol modes, paid entitlements, VM backup
precautions and installer/native qualification limits are preserved.
Runtime code, registry ownership and proof routing are unchanged.

Validation: focused monitoring, API, identity, telemetry and server
tests, contract/registry/status audits, scoped-link and ownership
checks, and normal commit hooks. The two slices remove 466 net lines
across 13 contracts and consolidate 16 shared rule families without
moving the bulk into another catalog.
This commit is contained in:
rcourtman
2026-10-13 13:57:02 +01:00
committed by GitHub
12 changed files with 86 additions and 398 deletions
@@ -3813,10 +3813,8 @@ approval/identity checks, output bounds and fresh config admission.
`TestCommandClientGuestOutcomeContract` covers status/output projection,
truncation, ambiguity and independent subsequent admission.
`TestGuestExecRealServerAgentDiscoveryAdmission` must carry decoded healthy
evidence, exclude failed guest output, and resume on an explicit healthy scan
over the real registered server/agent WebSocket boundary. Fake provider/config
fixtures do not establish native PVE/HAOS safety or delivered behaviour.
Command-result source controls do not establish native PVE/HAOS safety or
delivered behaviour.
### Docker update readback is not mutation history
@@ -4292,10 +4290,8 @@ overlap, thaw, writes, liveness and resumption are verified. Do not unlock,
thaw or automatically restart from this guard. Raw host-shell commands and
typed VM lifecycle operations are not relabelled as guarded guest reads.
`TestCommandClientGuestExecutionAdmissionContract` pins no unverified handoff;
`TestGuestExecRealServerAgentDiscoveryAdmission` connects real registration,
server/agent WebSockets and VM/nested-Docker Discovery with fake config/provider
boundaries. `TestVMGuestExecCapabilityBoundToRegisteredSession` pins old/unknown
`TestCommandClientGuestExecutionAdmissionContract` pins no unverified handoff.
`TestVMGuestExecCapabilityBoundToRegisteredSession` pins old/unknown
agent rejection and retained deferral. Config ambiguity, cancellation,
serialization/capacity and explicit-resumption controls are source proofs, not
native PVE/HAOS-QGA, thaw, installed-agent or release acceptance.
@@ -7205,9 +7201,10 @@ identifier before executing any `PolicyRequireApproval` command so a missed
control-plane gate cannot silently turn into host-level RCE. The single
exception is commands marked `Trusted` in the wire payload: those originate
from a vetted Pulse-internal subsystem whose command catalog is hardcoded
in Pulse source (today only the servicediscovery deep scanner, which wraps
read-only inspections in `docker exec`). `PolicyBlock` still applies to
trusted payloads. The `Trusted` field must never be set by a code path that
in Pulse source. The Proxmox guest Docker checker and inventory collector use
the fixed read-only catalog in `internal/monitoring/docker_detection.go`, wired
through the authenticated executor in `internal/api/router.go`.
`PolicyBlock` still applies to trusted payloads. The `Trusted` field must never be set by a code path that
deserialises user input or accepts a caller-supplied command string.
That same copied install transport must also normalize canonical base URLs
before composing installer asset paths: when operators enter a trailing-slash
@@ -8323,17 +8320,9 @@ workload liveness or containment in installed software.
### Per-tenant resource stores are released on offboarding and shutdown
`ResourceHandlers.getStore` opens a SQLite handle per org and caches it for the
process lifetime. `CloseTenantStore` releases and evicts one org's handle and is
called from `Router.CleanupTenant` alongside the other per-tenant teardown;
`CloseStores`, exposed as `Router.ShutdownResourceStores`, releases all of them.
Without this an offboarded tenant kept its file descriptors and its
`unified_resources.db-wal`/`-shm` files alive and its directory could not be
fully removed. Closed stores are evicted from the cache so a later request opens
a fresh handle rather than using a closed one
(`TestResourceHandlers_CloseTenantStoreReleasesTheHandle`), and both entry points
are idempotent and nil-safe
(`TestResourceHandlers_CloseIsIdempotentAndNilSafe`).
Tenant offboarding and process shutdown use the [API store-lifecycle contract](api-contracts.md#per-tenant-resource-stores-are-released-on-offboarding-and-shutdown),
including its source controls and qualification limits.
### Server upgrades nudge agent self-update within one report cycle
The unified-agent report ack now carries the server's version
@@ -9244,14 +9233,8 @@ sampling only, not report schemas, identity, enrollment or command authority.
### Asymmetric bridge evidence regression coverage
`TestApplyHostReportBridgeIdentity` verifies two consecutive reports through
host ingestion and checks both host-to-node and reciprocal node-to-agent links.
Custom management bridges, private IPv4 and ULA addresses retain association
when names differ and the provider endpoint is DNS-based. A Docker or generated
bridge on either side alone must not supply inferred identity: testing only
symmetric bridges would miss removal of filtering from one inventory.
This proof does not establish safety for arbitrarily renamed Docker bridges
or repair persisted links, and is not reporter or installed-release validation.
Host report ingestion preserves the [monitoring bridge-evidence controls](monitoring.md#asymmetric-bridge-evidence-regression-coverage),
including its source controls and qualification limits.
### Durable host/node association intent (6 September 2026)
@@ -9299,20 +9282,8 @@ the alert manager and canonical resource timeline.
### Node setup action v7 compatibility
Workflow consumers pin `actions/setup-node` to
`820762786026740c76f36085b0efc47a31fe5020` (v7.0.0). The action manifest
retains the Node 24 execution runtime, setup/cache-save entry points and existing
inputs. Its internal ESM/toolkit upgrade does not change the requested Node 24
application toolchain. Consumers do not supply `registry-url` or depend on the
removed dummy `NODE_AUTH_TOKEN` export. Explicit npm lockfile caching for test
jobs and disabled automatic package-manager caching for privileged release jobs
remain unchanged; no permission, dispatch input or signing backend changes.
`test_setup_node_upgrade_preserves_consumer_contract` in the existing workflow
trust suite verifies every consumer pin, absence of the affected auth assumptions,
workflow trust controls and the retained native Windows command/lifecycle proof
steps. Native Windows execution remains a hosted check, not a local Linux claim.
This upgrade is independent of the grouped signing/Docker/Tailscale updates.
Workflow consumers and their trust controls follow the [deployment toolchain contract](deployment-installability.md#node-setup-action-v7-compatibility),
including its source controls and qualification limits.
### Command-channel registration frame ordering
@@ -9339,20 +9310,8 @@ an explicit timeout, and the original server still fails the ordering assertion.
### Physical-disk provider identity round trip
The monitor's disk readback preserves the Proxmox source ID separately from
its canonical resource ID. Skipped physical-disk polls may refresh SMART data
but must not feed canonical hashes into source inventory, changing identity at
each subsequent projection. Views without Proxmox source metadata retain the
legacy ID fallback. This does not relax cross-host/device correlation, migrate
old duplicates, change agent authority, or redefine serial-less disk matching.
`TestPhysicalDiskSkippedPollPreservesSourceIdentity` in
`internal/monitoring/physical_disk_roundtrip_test.go` drives eight real skipped
polls and adapter rebuilds for both path-shaped and bare device names, checking
provider keys, canonical IDs, JSON projection, temperature/size/cadence, separate
nodes/controller members, and confirmed inventory removal.
`TestPhysicalDiskReadbackSourceIDFallback` covers missing source metadata.
This is synthetic runtime evidence, not USB hardware or reporter acceptance.
Disk ingest and readback follow the [unified source-identity contract](unified-resources.md#physical-disk-provider-identity-round-trip),
including its source controls and qualification limits.
### Coalesced host readings follow configured source freshness
@@ -9668,16 +9627,6 @@ registration, enrolment, install, update or removal path changed.
### Mock-mode read views fold linked fixture agents
`internal/monitoring/monitor.go` changed only so the mock-mode unified view
applies the operator's manual links (unified-resources contract, "Mock-mode
unified view applies operator links"). A fixture agent the operator linked
into a guest now folds into that guest in mock read views, as a linked agent
already does in live mode, so host snapshots built from them
(`HostsSnapshot`) stop listing it while the guest carries its facet. Agent
registration, enrolment, report admission, removal, continuity and the
fixture agent set itself are unchanged, and the view writes nothing to the
resource store.
### Demo write guard ignores websocket upgrade headers
`internal/api/demo_middleware.go` changed only so a `POST`, `PUT`, `PATCH` or
@@ -9728,39 +9677,13 @@ continuity are unchanged.
### Windows installer acceptance engine and absence (8 October 2026)
Native installer parsing, service-removal failure controls and actual lifecycle
execution use Windows PowerShell 5.1, never a PowerShell 7 substitute. A missing
or mismatched engine on Windows fails rather than skips. The dedicated runner
rejects pre-existing or unknown service/state instead of deleting it before the
proof. Both first and repeated uninstall require independent SCM, binary, state
and listener absence; only the exact SCM not-found identity/category proves
absence. Failed listener enumeration remains a failure. This changes validation,
not installer authority, transport, enrolment or host containment.
`TestWindowsAgentLifecycleRequiresExactEngineAndIndependentAbsence` rejects the
complete supplied parent and fourteen removed/changed controls. Native
`TestWindowsAgentLifecycleObservationRuntime` executes the actual harness
functions with fourteen no-mutation absence/failure/repeated-uninstall/owned-cleanup controls;
the existing real lifecycle job then uses real SCM. Linux skips are explicit and
do not establish a Windows parser, native runtime, reboot or installed result.
Clean-runner admission checks service, binary, state and listener absence before
machine-environment mutation. A rejected admission cannot clear a pre-existing
machine setting. Cleanup restores only the captured prior setting, even when
owned server teardown fails; an unconfirmed stop fails and retains the handle
rather than reporting clean teardown. Real Windows execution and full terminal
cleanup evidence still have to be returned by the existing native CI job.
Installer lifecycle qualification follows the [deployment Windows acceptance contract](deployment-installability.md#windows-installer-acceptance-engine-and-absence-8-october-2026),
including its source controls and qualification limits.
### Reconciled node-agent relink fold bookkeeping
A declared node-agent join can consume one split row before its newer
operator relink is applied. The relink records that side's ID and source in
the current holder-bound `ManualLinkFold` index, so identity-pin succession
keeps the operator decision and the resources API seed retains the same fold
for report-merge. No synthetic telemetry, identity key, enrollment or service
link is added. `TestOperatorSplitOverridesProxmoxNodeAgentLink` checks that
the monitor and its re-ingested API seed retain the fold across all split
shapes, then survive pin persistence and a repeated split.
Relink consumers preserve the [unified fold-bookkeeping contract](unified-resources.md#reconciled-node-agent-relink-fold-bookkeeping),
including its source controls and qualification limits.
### Mock-mode metrics-target lookups ride the fixture structure revision
@@ -2201,7 +2201,7 @@ tool-result payloads must project through
`OrchestratorToolResultInfo.ProviderToolResult` so the public investigation
handoff contract inherits the same `agentcapabilities` input-map,
thought-signature, and provider-result normalization as Assistant and MCP —
`TestOrchestratorAndChatAdaptersMapTheSameMessageFields` enforces it.
`TestOrchestratorAndChatAdaptersMapTheirMessageContracts` enforces it.
Assistant frontend presentation changes under
`frontend-modern/src/components/AI/Chat/` must keep live tool activity aligned
@@ -8647,11 +8647,9 @@ recompute digest counts from run history, findings, or cost events, and must
not call the endpoint through any other client. Proof:
`frontend-modern/src/api/__tests__/patrol.test.ts`.
The explicit issue explanation journey is qualified separately from provider
reasoning and real remediation in
`docs/qualification/PATROL_ASSISTANT_CUSTOMER_JOURNEY.md`. The repeatable browser
proof is `scripts/check-patrol-assistant-journey.mjs`. A passing scripted
response does not establish a useful customer outcome or model qualification.
Issue explanation follows the [customer-journey qualification boundary](../../../../qualification/PATROL_ASSISTANT_CUSTOMER_JOURNEY.md#real-outcome-qualification-still-required),
which separates scripted interaction proof from provider reasoning, remediation
and repeatable customer outcomes.
### Subscription routing content boundary
@@ -4337,11 +4337,9 @@ populated inventory and checks incident access on desktop and the 390px mobile
rail. Enabled acknowledgement controls prove access only, not persisted
acknowledgement or external notification receipt.
The explicit issue explanation journey is qualified separately from provider
reasoning and real remediation in
`docs/qualification/PATROL_ASSISTANT_CUSTOMER_JOURNEY.md`. The repeatable browser
proof is `scripts/check-patrol-assistant-journey.mjs`. A passing scripted
response does not establish a useful customer outcome or model qualification.
Issue explanation follows the [customer-journey qualification boundary](../../../../qualification/PATROL_ASSISTANT_CUSTOMER_JOURNEY.md#real-outcome-qualification-still-required),
which separates scripted interaction proof from provider reasoning, remediation
and repeatable customer outcomes.
### Delivery health requests have latest-started ownership
@@ -4570,10 +4568,6 @@ Unknown values still fall back to all. The email adapter round-trip matrix and
webhook edit/save regression cover issue #2069 without altering delivery filters
or sending provider notifications. Component proof is not installed delivery.
Formatting-only follow-up retains this warning-level contract. The production-component
browser matrix was rerun after formatting at desktop and narrow widths, including
all/critical/warning save/reload, cancel, and warning webhook creation.
### SMTP rate-limit preference persistence
The existing SMTP editor's rate limit is included by
@@ -284,22 +284,6 @@ warming consumers. The connected browser fixture verifies actual History
components/CSS on desktop Chromium and phone WebKit, not native identity,
appliance history, workload safety or released availability.
### Discovery detail reads preserve final access status
`getDiscovery` retains final HTTP401/403 in a typed client error before any
response-body read. The fixed message exposes no denied body. This applies to
the typed resource detail, agent collection and resolved agent detail paths.
A body or transport message claiming an access status is not that status, and
ordinary transient failures retain their existing error behaviour. Successful
absent-record semantics (detail HTTP404 or an empty agent list) are unchanged.
Retained query consumers can therefore withdraw a denied service/endpoint
rather than treating it as a transient read or a successful empty discovery.
No endpoint, server permission, wire schema or mutation policy changes.
`src/api/__tests__/discovery.test.ts` exercises all three paths with actual
Response status/body objects, denied-body non-read and a misleading 503 body.
The production guest browser fixture verifies the connected final HTTP403
withdrawal, pending retry, transient retention and successful HTTP404 recovery.
### TrueNAS saved probes do not rewrite runtime evidence — issue #2382
The saved connection test uses stored server-owned secrets, or merges an edited
@@ -11407,9 +11391,9 @@ fields.
### Per-tenant resource stores are released on offboarding and shutdown
`ResourceHandlers.getStore` opens a SQLite handle per org and caches it for the
process lifetime. `CloseTenantStore` releases and evicts one org's handle and is
called from `Router.CleanupTenant` alongside the other per-tenant teardown;
The API `resourceapi.QueryService` owns and caches SQLite handles per org.
`ResourceHandlers.getStore` delegates to that owner. `CloseTenantStore` releases
and evicts one org's handle and is called from `Router.CleanupTenant` alongside the other per-tenant teardown;
`CloseStores`, exposed as `Router.ShutdownResourceStores`, releases all of them.
Without this an offboarded tenant kept its file descriptors and its
`unified_resources.db-wal`/`-shm` files alive and its directory could not be
@@ -11970,11 +11954,9 @@ disk. The advanced-reporting entitlement gates both kinds. Proofs:
`internal/api/report_schedules_test.go`, `internal/api/patrol_digest_email_test.go`,
`internal/api/ai_handlers_more_test.go`.
The explicit issue explanation journey is qualified separately from provider
reasoning and real remediation in
`docs/qualification/PATROL_ASSISTANT_CUSTOMER_JOURNEY.md`. The repeatable browser
proof is `scripts/check-patrol-assistant-journey.mjs`. A passing scripted
response does not establish a useful customer outcome or model qualification.
Issue explanation follows the [customer-journey qualification boundary](../../../../qualification/PATROL_ASSISTANT_CUSTOMER_JOURNEY.md#real-outcome-qualification-still-required),
which separates scripted interaction proof from provider reasoning, remediation
and repeatable customer outcomes.
### Report email shares the canonical SMTP retry verdict
@@ -12024,10 +12006,6 @@ repairs a dropped supported value without introducing a new wire enum or changin
backend routing. The notification adapter save/reload matrix verifies each
supported severity in frontend-modern/src/api/__tests__/notifications.test.ts.
Formatting-only follow-up retains this warning-level contract. The production-component
browser matrix was rerun after formatting at desktop and narrow widths, including
all/critical/warning save/reload, cancel, and warning webhook creation.
### Quick security setup preserves unrelated settings
Authenticated force setup in `internal/api/security_setup_fix.go` retains the
@@ -392,41 +392,8 @@ thaw, safe writes, installed acceptance or a containing published release.
### Backup inventory ownership is source-scoped and unambiguous
The existing Backups Coverage and By date views do not select a workload from
an arbitrary substring, display label or first matching row when VMIDs repeat.
PVE files, guest snapshots and task evidence use the supplied connection scope
before native node names or aliases. One VMID inside a matching PVE connection
stays associated after node migration; a known conflicting connection cannot be
rescued by a reused node name or a singleton fallback. With no connection scope,
an exact native node/alias must identify one candidate; wholly unscoped singleton
observations retain their existing compatibility behaviour.
PBS instance and datastore identify a repository, not a PVE workload. A PBS
namespace disambiguates repeated VMIDs only with a unique exact native node,
alias or PVE connection label. Unique type/VMID PBS inventory retains the
existing root-namespace association. This bounded presentation fix does not
implement the runtime PBS source learner or alter server-owned protection policy.
Unresolved artifacts remain listed once and keep complete, collision-safe source
keys (PVE connection/node or PBS instance/datastore/namespace), including host
backup labels. Equal VMIDs, repository labels or host names cannot pool them
across unproven sources. Untyped PVE tasks may join only PVE-scoped rows, not a
PBS-only unresolved row with a lookalike namespace. The existing collapsed group
and count say **unmatched**, not that the guest necessarily ceased to exist;
its explanation wraps on phones. These rows remain **not evaluated**, separate
from canonical provider-owned protection posture and live workload counts.
Verification: `proxmoxBackupRecoveryModel.identity.test.ts` reproduces prefix
and reused-node collisions, order independence, migration/alias controls,
conflicting scope, ambiguous type/namespace and separated unresolved sources.
Mounted `ProxmoxBackupsTable.test.tsx` backup-identity cases cover both views,
artifact preservation, reorder/display rename, expansion identity and no extra
inventory read. `browser-tests/backup-identity.cjs` uses production tables,
router, inventory readers and CSS with synthetic responses on desktop Chromium
and touch WebKit at 390px/320px, including keyboard/touch disclosure, ambiguity,
readable unmatched explanation and same-resource recovery. No native backup
identity, restore, guest thaw or containing-release acceptance follows from
these source/render observations.
Both Backups views follow the [storage source-association contract](storage-recovery.md#backup-inventory-ownership-is-source-scoped-and-unambiguous),
including its source controls and qualification limits.
### Diagnostics are live checks; downloads require a privacy review
@@ -8829,11 +8796,9 @@ App.architecture.test.ts protects the CSS scope and scroll ownership;
non-scrolling wrapper. Browser receipts do not establish physical Android touch
behaviour or every asynchronous update state.
The explicit issue explanation journey is qualified separately from provider
reasoning and real remediation in
`docs/qualification/PATROL_ASSISTANT_CUSTOMER_JOURNEY.md`. The repeatable browser
proof is `scripts/check-patrol-assistant-journey.mjs`. A passing scripted
response does not establish a useful customer outcome or model qualification.
Issue explanation follows the [customer-journey qualification boundary](../../../../qualification/PATROL_ASSISTANT_CUSTOMER_JOURNEY.md#real-outcome-qualification-still-required),
which separates scripted interaction proof from provider reasoning, remediation
and repeatable customer outcomes.
### Alert health attention preserves asynchronous ownership
@@ -8849,20 +8814,8 @@ queue-action responses, without claiming backend notification delivery.
### Alert status distinguishes dispatch from destination evidence
The active alert card renders a valid diagnosis `lastNotified` timestamp as
“Dispatch requested”, never “Notified”: the alert manager records this field
before invoking delivery callbacks. A cooldown's `nextEligibleAt` is labelled
“next eligible”, not a promised send time. Neither field proves destination
acceptance or recipient receipt; that evidence must not be inferred from the
muted presentation tone. Missing or invalid timestamps retain the existing
pending/cooldown fallback; acknowledged alerts retain their badge without a
second status line. No API field, notification policy or shared primitive changes.
The existing wrapping status text must remain readable at desktop and phone
widths despite the longer labels. The presentation and Overview delivery-status
tests cover the evidence boundary; `scripts/check-alert-dispatch-copy.mjs`
qualifies the real Overview with scripted API data in Chromium, not installed
notification delivery.
Shared presentation preserves the [alerts dispatch-evidence contract](alerts.md#alert-status-distinguishes-dispatch-from-destination-evidence),
including its source controls and qualification limits.
### Resource incident reads retain lifecycle ownership
@@ -1520,23 +1520,6 @@ live/collapsed/expanded/History acceptance.
**Availability backfill preserves concurrent discovery changes (7 September 2026)**
The backfill List snapshot is a work list, not an authoritative record to save.
Read the current discovery, derive a missing suggestion from its current identity,
and persist under one store write lock. State-provider reads stay outside that
lock. Preserve newer identity, URL, engine version, user notes and existing
(including dismissed) proposals; never resurrect a discovery deleted after List.
A failed persistence attempt remains an error without installing the proposed
change in cache. `TestService_BackfillPreservesConcurrentManualRepair` in `service_test.go`
deterministically pauses SetReadState
backfill after List, completes manual ESPHome repair, then resumes backfill and
checks both cache and encrypted restart.
`TestStore_BackfillAvailabilitySuggestionUsesCurrentRecord` in `store_test.go`
covers current-identity
inference, dismissal, deletion, unsupported identity and persistence failure.
This is discovery state-integrity proof, not incident or notification acceptance.
**Correlated VM memory fallback — issue #1962 (7 September 2026)**
The next Proxmox guest poll indexes live agent memory embedded in VM read views,
@@ -2790,10 +2773,9 @@ alerts, 5 vSphere incidents and 137 backup-age alerts in live mode, and counted
a mock-mode observation toward a live connection's three-observation
confirmation. `TestLeavingMockModeWaitsForAnInFlightEvaluation` holds an
evaluation in its CPU window read, outside the alert manager's lock, and fails
if the switch returns first; `TestLiveRefreshIgnoresARegistryRepublishedFromMockMode`
lets a mock-mode rebuild republish while a live refresh is held before reading
the registry back, then checks the next live refresh evaluates live records.
Each of those two fails against a fence without its wait or its overlap check.
if the switch returns first; `TestLiveRefreshRejectsCollectedRecordsFromMockMode`
holds collected fixture records across a mode switch and checks that a live
refresh publishes and evaluates live provider records without fixture leakage.
`TestDockerPruneIgnoresTheRegistryMockModeLeft` raises a live Docker alert
before any live rebuild and fails if pruning reads the fixture registry, and
`TestPreviousStateCarryIgnoresTheRegistryMockModeLeft` fails if the
@@ -3509,26 +3491,9 @@ service-history reads plus denial/recovery without fabricated samples.
`internal/monitoring/monitor_host_agents_test.go` and `pkg/agents/host/proxmox_lxc_test.go` must prove JSON wire-report ingestion through `ApplyHostReport`, partial/complete-empty recovery, contradictory IDs, unchanged node/agent/name/TTL protection, and no invalid-list-to-healthy-empty conversion. Omitted telemetry is never zero. Source proofs are distinct from native PVE and released availability.
### Discovery accepts successful command evidence only
Only stdout from a successful result with zero exit status and no error enters
Discovery analysis or saved observations. Stderr, failed/partial output, a nil
result and contradictory success/status cannot identify a service. Independent
healthy output survives optional probe failures. When an attempted scan has
failures and no successful stdout evidence, return a fixed no-evidence error
and truthful terminal progress; manual metadata fallback and background
enhancement must not replace saved results, notes or observation times. An
explicit later healthy scan resumes normally; no failed command is replayed.
`TestDiscoveryFailedCommandEvidenceContract` covers VM/nested-Docker/LXC/host/
Docker output admission, optional partial evidence, saved manual/background
results and explicit recovery. Registered server/agent controls cover actual
guest result propagation, with fake config/provider boundaries. This is source
truthfulness, not native thaw, containing-release or workload acceptance.
1. Update this contract when monitoring truth ownership changes
2. Tighten guardrails when `GetState()`-centric paths are removed
3. Keep discovery-provider, host-agent ingest, guest-memory trust, metrics-history, storage-risk, Docker/Podman container collection, Docker report/model payloads, Proxmox Ceph and ZFS compatibility, Docker Swarm collection, mock runtime fixtures, and container bootstrap proof routes explicit in `registry.json`
3. Keep network-discovery, host-agent ingest, guest-memory trust, metrics-history, storage-risk, Docker/Podman container collection, Docker report/model payloads, Proxmox Ceph and ZFS compatibility, Docker Swarm collection, mock runtime fixtures, and container bootstrap proof routes explicit in `registry.json`
4. Update related read-state or monitor tests when new collector paths land
5. Keep platform ingestion semantics aligned with
`docs/release-control/v6/internal/PLATFORM_SUPPORT_MODEL.md`: hybrid is a
@@ -3753,41 +3718,6 @@ existing reader behaviour and the real authenticated/scoped Pulse route.
V2 partial inventories preserve usable filesystem readings while leaving typed-helper health degraded. Global inventory failure still yields no inventory. No host-network exception, guest mutation, database/retention change or temperature substitution accompanies this repair.
### Discovery observes VM guest execution pauses
Verified VM results describe the guest's terminal status and decoded output,
not successful CLI delivery. Discovery admits only successful stdout as command
evidence for every resource type; stderr and failed/partial output are not
observations. An attempted scan with failures but no successful stdout returns
an error without replacing saved results or invoking analysis. Normal empty
successful probes and independent healthy output alongside optional failures
retain their existing semantics.
Manual, automatic and nested-Docker VM scans use the existing agent execution
boundary; they cannot infer safety from the Web drawer's snapshot. VM routing
requires an exact connected agent ID or a unique owning-node hostname, never a
sole unrelated agent or guest-name hint. The agent independently requires its
current local VM config and checks the operation lock. Discovery runs VM probes
serially and stops on any explicit guest safety deferral, including optional
probes. Deferred in-flight output is not fresh discovery evidence; progress
retains an error and actual partial completion rather than 100% success.
A manual deferral is returned as an error without replacing saved discoveries,
notes or raw observations with metadata fallback. Background enhancement keeps
its existing evidence rather than analysing partial deferred output. Clearing
a pause triggers no scan or retry by itself; an existing normal trigger or
explicit request starts a new, guarded attempt. Healthy catalogue commands,
non-VM parallelism, permissions, feature gates and saved-result access remain.
`TestDiscoveryGuestSafetyStopsAndPreservesSavedEvidence` covers both VM/nested
Docker scans, first/later optional-command deferral, manual error and automatic
saved-evidence preservation. Node ambiguity controls exclude misrouting. These
source-connected controls and the agent protocol guard do not reserve QGA
against PVE backup start, establish native thaw, or satisfy the existing native
overlap/covered-write/liveness/resumption dependency. Preserve the monitoring
and alert outage precaution until that operational outcome is verified.
### Mock-mode unified view applies operator links
The mock branch of currentUnifiedStateView serves the fixture graph to the
@@ -5345,12 +5275,12 @@ temperature, so a retained standby or silent-agent reading never reappears as a
point at the current time.
The SMART history writers in `internal/monitoring/monitor.go` and
`monitor_agents.go` call that helper rather than a private copy of the rule,
and `GetPhysicalDiskTemperatureCharts` pads a short disk temperature series out
to now only with a collected reading: with a retained one the stored samples
stay as they are, and a disk with none gets no series. Mock mode can still
substitute its generic demo series for that disk ID before this step; that
series is not derived from the disk's reading. Proof:
`TestDiskTemperatureChartsPadOnlyWithCollectedReading` in
and `GetPhysicalDiskTemperatureCharts` returns observed history samples without
padding a short series to now, even when the current reading was collected.
Retained readings never manufacture history. A live disk with no history keeps
its metadata without a fabricated series. Mock mode may use its seeded demo
history, which is separate from a disk reading. Proof:
`TestDiskTemperatureChartsRetainOnlyObservedSamples` in
`internal/monitoring/monitor_metrics_slo_test.go`.
Unified-resource physical-disk round trips must retain named
`StorageGroup` membership rather than degrading it to the generic `Used`
@@ -6890,20 +6820,8 @@ that already admitted or provider-accepted deliveries can be recalled.
### Physical-disk provider identity round trip
The monitor's disk readback preserves the Proxmox source ID separately from
its canonical resource ID. Skipped physical-disk polls may refresh SMART data
but must not feed canonical hashes into source inventory, changing identity at
each subsequent projection. Views without Proxmox source metadata retain the
legacy ID fallback. This does not relax cross-host/device correlation, migrate
old duplicates, change agent authority, or redefine serial-less disk matching.
`TestPhysicalDiskSkippedPollPreservesSourceIdentity` in
`internal/monitoring/physical_disk_roundtrip_test.go` drives eight real skipped
polls and adapter rebuilds for both path-shaped and bare device names, checking
provider keys, canonical IDs, JSON projection, temperature/size/cadence, separate
nodes/controller members, and confirmed inventory removal.
`TestPhysicalDiskReadbackSourceIDFallback` covers missing source metadata.
This is synthetic runtime evidence, not USB hardware or reporter acceptance.
Disk ingest and readback follow the [unified source-identity contract](unified-resources.md#physical-disk-provider-identity-round-trip),
including its source controls and qualification limits.
### Unified metric replay guard bounds registry-rebuild writes
@@ -7186,14 +7104,7 @@ for a mock-style view.
### Reconciled node-agent relink fold bookkeeping
A declared node-agent join can consume one split row before its newer
operator relink is applied. The relink records that side's ID and source in
the current holder-bound `ManualLinkFold` index, so identity-pin succession
keeps the operator decision and the resources API seed retains the same fold
for report-merge. No synthetic telemetry, identity key, enrollment or service
link is added. `TestOperatorSplitOverridesProxmoxNodeAgentLink` checks that
the monitor and its re-ingested API seed retain the fold across all split
shapes, then survive pin persistence and a repeated split.
Relink consumers preserve the [unified fold-bookkeeping contract](unified-resources.md#reconciled-node-agent-relink-fold-bookkeeping).
Mock-link lifecycle validation drives the owning ingest/rebuild boundary
after unlink and relink: read hydration intentionally reuses a registry
@@ -1335,10 +1335,6 @@ normalization. Existing backend severity admission and delivery queues remain
unchanged. The notification API adapter round trips and WebhookConfig edit/save
regression establish frontend control preservation, not provider delivery.
Formatting-only follow-up retains this warning-level contract. The production-component
browser matrix was rerun after formatting at desktop and narrow widths, including
all/critical/warning save/reload, cancel, and warning webhook creation.
### Resolution cancellation is occurrence-qualified
The monitor's delayed recovery path calls `CancelResolvedAlert` with the
@@ -2889,11 +2889,9 @@ is unavailable rather than showing zeros. Proofs:
`frontend-modern/src/pages/__tests__/AIIntelligence.test.tsx` (card ordering),
and the browser receipt in `frontend-modern/browser-verification.json`.
The explicit issue explanation journey is qualified separately from provider
reasoning and real remediation in
`docs/qualification/PATROL_ASSISTANT_CUSTOMER_JOURNEY.md`. The repeatable browser
proof is `scripts/check-patrol-assistant-journey.mjs`. A passing scripted
response does not establish a useful customer outcome or model qualification.
Issue explanation follows the [customer-journey qualification boundary](../../../../qualification/PATROL_ASSISTANT_CUSTOMER_JOURNEY.md#real-outcome-qualification-still-required),
which separates scripted interaction proof from provider reasoning, remediation
and repeatable customer outcomes.
### Expired action continuity (2026-09-07)
@@ -366,22 +366,8 @@ metric tracks at narrow width; this is readability, not a throughput claim.
### Bounded confidence/priority work in broadcast grouping — issue #2199
The general identity matcher retains every confidence, reason, ordering and
review flag. Top-level grouping requests only its existing high-confidence
floor (0.90), so hostname-only and IP-only peer sets are not built when no
eligible composite signal exists. High-confidence results are identical to
filtering the full general matcher; machine ID, UUID and hostname+MAC still
keep their existing precedence. Host/IP fallback still requires exactly one
strictly better-priority owner. Equal/worse-priority peers are discarded before
allocating/sorting fallback candidates, and a best-priority group has no possible
attachment target. No identity, ambiguity, count, group ID or explanation rule
changes, and no cached output hides live transitions.
Independent pre-repair matching/grouping oracles, candidate-floor differential
cases/fuzzing, a discarded-peer allocation bound, mixed grouping records and the
complete existing identity/topology suites protect these constraints. The
residual synthetic broadcast profile identified discarded matching/grouping as
the dominant cost; it is not a profile of the reporter's installed agent fleet.
Broadcast performance work preserves the [unified grouping semantics and allocation bounds](unified-resources.md#bounded-confidencepriority-work-in-broadcast-grouping--issue-2199),
including its source controls and qualification limits.
### Connected-dashboard snapshot ownership — issue #2199
@@ -3601,17 +3587,9 @@ nothing at all when workload rows are present.
### Per-tenant resource stores are released on offboarding and shutdown
`ResourceHandlers.getStore` opens a SQLite handle per org and caches it for the
process lifetime. `CloseTenantStore` releases and evicts one org's handle and is
called from `Router.CleanupTenant` alongside the other per-tenant teardown;
`CloseStores`, exposed as `Router.ShutdownResourceStores`, releases all of them.
Without this an offboarded tenant kept its file descriptors and its
`unified_resources.db-wal`/`-shm` files alive and its directory could not be
fully removed. Closed stores are evicted from the cache so a later request opens
a fresh handle rather than using a closed one
(`TestResourceHandlers_CloseTenantStoreReleasesTheHandle`), and both entry points
are idempotent and nil-safe
(`TestResourceHandlers_CloseIsIdempotentAndNilSafe`).
Tenant offboarding and process shutdown use the [API store-lifecycle contract](api-contracts.md#per-tenant-resource-stores-are-released-on-offboarding-and-shutdown),
including its source controls and qualification limits.
### Report-ack version echo adds no steady-state work
The `serverVersion` field on unified-agent report acks is a constant string
@@ -2759,26 +2759,20 @@ capability is not treated as the authorization boundary.
and image normalization.
### Licensed-feature adoption telemetry stays count-only
Schema v6 of the outbound usage payload adds nine licensed-feature adoption
signals so Pro feature usage is measurable at all: `alert_ai_enabled`,
`rbac_custom_roles`, `rbac_user_assignments`, `audit_logging_persistent`,
`audit_events_30d`, `report_schedules`, `report_schedules_enabled`,
`report_schedules_run_30d`, and `agent_profiles`. Every one is a count or a
coarse boolean. Role names, permission sets, usernames, schedule names,
delivery recipients, report scope and contents, profile names, and every audit
event field (type, actor, target, outcome, detail) stay on the install.
`audit_logging_persistent` reports only that a persistent store is active
rather than console logging, and `audit_events_30d` is a retained-row count
inside the same 30-day window used by the other counters, so it cannot be
linked to one pseudonymous identifier indefinitely. Config-sourced signals are
read in `applyLicensedFeatureConfigSnapshot`
(`pkg/server/telemetry_licensed_features.go`); RBAC and audit are read behind
the router in `Router.ApplyLicensedFeatureTelemetrySnapshot`
(`internal/api/telemetry_licensed_features.go`). Both are disclosed in
`docs/PRIVACY.md` and its `frontend-modern/public/docs/` copy, pinned by
`TestAllTelemetryFieldsAreDisclosed`, and the counts are pinned by
Licensed-feature adoption telemetry exports only the coarse counts and booleans
declared in `internal/telemetry/licensed_feature_fields.go`. Role names,
permission sets, usernames, schedule names, delivery recipients, report scope
and contents, profile names, and every audit event field stay on the install.
Config signals come from `pkg/server/telemetry_licensed_features.go`; RBAC counts
come from `internal/api/telemetry_licensed_features.go`, and licensed audit-read
activity follows the gated recorder below.
`docs/PRIVACY.md` and its shipped copy disclose the current fields.
`TestAllTelemetryFieldsAreDisclosed`,
`TestApplyLicensedFeatureConfigSnapshot_CountsScheduledReportingAndProfiles`
and `TestApplyLicensedFeatureTelemetrySnapshot_CountsOnlyOperatorAuthoredRBAC`.
and `TestApplyLicensedFeatureTelemetrySnapshot_CountsOnlyOperatorAuthoredRBAC`
pin disclosure and count semantics. Audit-store presence and background write
volume are not adoption signals.
### Telemetry RBAC reads never provision an RBAC store
@@ -2791,17 +2785,11 @@ side effect of a background read. A store reporting a migration failure is
skipped rather than counted, so a degraded store cannot silently understate
adoption. `TestTenantRBACProvider_PeekManagerDoesNotProvision` and
`TestTenantRBACProvider_PeekManagerNilProviderIsSafe` in
`internal/api/rbac_tenant_provider_test.go` pin both properties, and
`TestApplyLicensedFeatureTelemetrySnapshot_DoesNotCreateRBACStores` pins it
through the router boundary.
`internal/api/rbac_tenant_provider_test.go` pin both properties.
`ApplyLicensedFeatureTelemetrySnapshot` uses that cached-only read boundary.
### Audit adoption telemetry counts gated reads, never store presence
Schema v7 replaced `audit_logging_persistent` and `audit_events_30d` with
`audit_reads_30d`. The first two did not discriminate: `pkg/server` installs the
SQLite audit logger on every install for defense in depth and gates only the
read and export endpoints, so the boolean was true on every install that
reported it and the event count measured background write volume, saturating
the receiver clamp on unlicensed community installs. `audit_reads_30d` counts
`audit_reads_30d` counts
requests that cleared `RequireLicenseFeature(featureAuditLoggingValue, ...)` on
an audit read or export surface, which requires a human action and so cannot
settle into a constant. The recorder in
@@ -2937,17 +2925,9 @@ because it looks like data in the fleet aggregate.
names.
### Per-tenant resource stores are released on offboarding and shutdown
`ResourceHandlers.getStore` opens a SQLite handle per org and caches it for the
process lifetime. `CloseTenantStore` releases and evicts one org's handle and is
called from `Router.CleanupTenant` alongside the other per-tenant teardown;
`CloseStores`, exposed as `Router.ShutdownResourceStores`, releases all of them.
Without this an offboarded tenant kept its file descriptors and its
`unified_resources.db-wal`/`-shm` files alive and its directory could not be
fully removed. Closed stores are evicted from the cache so a later request opens
a fresh handle rather than using a closed one
(`TestResourceHandlers_CloseTenantStoreReleasesTheHandle`), and both entry points
are idempotent and nil-safe
(`TestResourceHandlers_CloseIsIdempotentAndNilSafe`).
Tenant offboarding and process shutdown use the [API store-lifecycle contract](api-contracts.md#per-tenant-resource-stores-are-released-on-offboarding-and-shutdown),
including its source controls and qualification limits.
### Report-ack server versions cannot steer agents beyond one validated check
The unified-agent report ack's `serverVersion` echo gives acks a version
@@ -3218,20 +3198,8 @@ a CSRF-checked `POST`. No authentication, scope or session rule changed.
### Node setup action v7 compatibility
Workflow consumers pin `actions/setup-node` to
`820762786026740c76f36085b0efc47a31fe5020` (v7.0.0). The action manifest
retains the Node 24 execution runtime, setup/cache-save entry points and existing
inputs. Its internal ESM/toolkit upgrade does not change the requested Node 24
application toolchain. Consumers do not supply `registry-url` or depend on the
removed dummy `NODE_AUTH_TOKEN` export. Explicit npm lockfile caching for test
jobs and disabled automatic package-manager caching for privileged release jobs
remain unchanged; no permission, dispatch input or signing backend changes.
`test_setup_node_upgrade_preserves_consumer_contract` in the existing workflow
trust suite verifies every consumer pin, absence of the affected auth assumptions,
workflow trust controls and the retained native Windows command/lifecycle proof
steps. Native Windows execution remains a hosted check, not a local Linux claim.
This upgrade is independent of the grouped signing/Docker/Tailscale updates.
Workflow consumers and their trust controls follow the [deployment toolchain contract](deployment-installability.md#node-setup-action-v7-compatibility),
including its source controls and qualification limits.
### Updates settings copy rename
@@ -6084,17 +6084,8 @@ than drifting into "has ever used"
### Per-tenant resource stores are released on offboarding and shutdown
`ResourceHandlers.getStore` opens a SQLite handle per org and caches it for the
process lifetime. `CloseTenantStore` releases and evicts one org's handle and is
called from `Router.CleanupTenant` alongside the other per-tenant teardown;
`CloseStores`, exposed as `Router.ShutdownResourceStores`, releases all of them.
Without this an offboarded tenant kept its file descriptors and its
`unified_resources.db-wal`/`-shm` files alive and its directory could not be
fully removed. Closed stores are evicted from the cache so a later request opens
a fresh handle rather than using a closed one
(`TestResourceHandlers_CloseTenantStoreReleasesTheHandle`), and both entry points
are idempotent and nil-safe
(`TestResourceHandlers_CloseIsIdempotentAndNilSafe`).
Tenant offboarding and process shutdown use the [API store-lifecycle contract](api-contracts.md#per-tenant-resource-stores-are-released-on-offboarding-and-shutdown),
including its source controls and qualification limits.
### Report-ack version echo persists nothing
@@ -2989,7 +2989,7 @@ application resource-provider or WebSocket lifecycle.
become a per-row store pattern.
Regression coverage:
`TestSQLiteStoreActionAuditOriginRoundTrip`,
`TestSQLiteActionAuditOriginOperationalRecordReader`, and the matching
`TestActionAuditOriginReaderReturnsLatestTransition`, and the matching
memory/batch reader tests in `internal/unifiedresources/store_test.go`.
Incident next-step fallback follows the same canonical resource boundary:
storage guidance is reserved for storage, physical-disk, network-share,