From 62c2b765d080ef3312720fe6410dcc1fb8600366 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Fri, 26 Jun 2026 12:16:11 +0100 Subject: [PATCH] feat: attach availability checks as facet on known resource row Agentless availability checks (ICMP/TCP/HTTP) were always minting standalone network-endpoint resources, leaving them disconnected from the known Proxmox/Docker guest they actually monitor. This made availability evidence invisible on the platform resource row where the user expects it, per the performance-and-scalability bounded-row contract. Backend (unified-resources ingest): - Add LinkedResourceID field to AvailabilityData and AvailabilityTarget - resolveAvailabilityLink: explicit link first, then exact-IP unambiguous correlation; skip hostname-only (lossy); guard against overwriting a different target's facet - Unlinked/unmatched probes still mint network-endpoint (fallback) Frontend: - Relax getAvailabilityProbePresentation for any resource with availability - Add compact protocol badge to UnifiedResourceHostTableCard name cell - Add optional 'Link to resource' field to availability target form - Add linkedResourceId to frontend types Contracts: api-contracts, unified-resources, monitoring, performance-and-scalability, storage-recovery. Governance: coverage_gap + candidate_lane in status.json. --- .../v6/internal/SOURCE_OF_TRUTH.md | 2 +- docs/release-control/v6/internal/status.json | 73 +++++++- .../v6/internal/subsystems/api-contracts.md | 6 + .../v6/internal/subsystems/monitoring.md | 5 + .../subsystems/performance-and-scalability.md | 14 +- .../internal/subsystems/storage-recovery.md | 7 + .../internal/subsystems/unified-resources.md | 18 +- .../src/api/availabilityTargets.ts | 1 + .../UnifiedResourceHostTableCard.tsx | 10 ++ ...esourceTable.performance.contract.test.tsx | 32 ++++ .../AvailabilityTargetSlot.tsx | 17 ++ frontend-modern/src/types/resource.ts | 1 + .../availabilityProbePresentation.test.ts | 27 +++ .../utils/availabilityProbePresentation.ts | 5 +- internal/api/availability_handlers_test.go | 61 +++++++ internal/config/availability.go | 2 + internal/monitoring/availability_poller.go | 1 + .../availability_link_test.go | 163 ++++++++++++++++++ .../unifiedresources/code_standards_test.go | 3 + internal/unifiedresources/registry.go | 89 ++++++++++ internal/unifiedresources/registry_test.go | 32 ++++ internal/unifiedresources/types.go | 1 + .../release_control/subsystem_lookup_test.py | 2 +- 23 files changed, 548 insertions(+), 24 deletions(-) create mode 100644 internal/unifiedresources/availability_link_test.go diff --git a/docs/release-control/v6/internal/SOURCE_OF_TRUTH.md b/docs/release-control/v6/internal/SOURCE_OF_TRUTH.md index bc3b9bac17..2996ac5485 100644 --- a/docs/release-control/v6/internal/SOURCE_OF_TRUTH.md +++ b/docs/release-control/v6/internal/SOURCE_OF_TRUTH.md @@ -1,6 +1,6 @@ # Pulse v6 Source Of Truth -Last updated: 2026-06-25 +Last updated: 2026-06-26 Status: ACTIVE This file is the stable human governance layer for the active v6 release diff --git a/docs/release-control/v6/internal/status.json b/docs/release-control/v6/internal/status.json index 4f0712afa1..d0af5af05b 100644 --- a/docs/release-control/v6/internal/status.json +++ b/docs/release-control/v6/internal/status.json @@ -1,6 +1,6 @@ { "version": "6.0", - "updated_at": "2026-06-25", + "updated_at": "2026-06-26", "scope": { "active_repos": [ "pulse", @@ -7279,6 +7279,41 @@ "kind": "file" } ] + }, + { + "id": "availability-as-resource-facet", + "summary": "Agentless availability checks always mint a standalone network-endpoint resource and never attach to a known resource, so a check that monitors a Proxmox guest, Docker container, or Kubernetes workload appears as a disconnected duplicate on the Machines page instead of on the resource it actually monitors. The performance-and-scalability contract already requires agentless availability evidence to belong on the resource's bounded row path, but the ingest path has no cross-source attach: resolveLinkedResource only handles Proxmox<->agent links and findMatch is restricted to agent and physical_disk types, so SourceAvailability records always take the source-specific network-endpoint ID and the Availability facet never reaches an existing resource. The gap is the missing unambiguous attach (by explicit resource link or unique address/hostname correlation) plus platform-row surfacing of the attached facet.", + "owner": "project-owner", + "status": "planned", + "recorded_at": "2026-06-26", + "lane_ids": [ + "L8", + "L13" + ], + "subsystem_ids": [ + "frontend-primitives", + "monitoring", + "unified-resources" + ], + "proposed_resolution": "lane-expansion", + "coverage_impact": 7, + "evidence": [ + { + "repo": "pulse", + "path": "docs/release-control/v6/internal/subsystems/performance-and-scalability.md", + "kind": "file" + }, + { + "repo": "pulse", + "path": "internal/monitoring/availability_poller.go", + "kind": "file" + }, + { + "repo": "pulse", + "path": "internal/unifiedresources/registry.go", + "kind": "file" + } + ] } ], "candidate_lanes": [ @@ -7330,20 +7365,40 @@ "patrol-intelligence", "unified-resources" ] + }, + { + "id": "availability-as-resource-facet", + "name": "Availability as Resource Facet", + "summary": "Promote availability-as-resource-facet into a governed lane expansion: extend unified-resource ingest so an availability check attaches as a facet on the known resource it monitors (explicit resource link first, unambiguous unique address/hostname correlation second, standalone network-endpoint fallback for genuinely unowned endpoints), keep the ingest on the existing unified-resource hot path with no per-row fetch, and surface the attached Availability facet on the platform resource row as a compact inline target/result readout with the protocol identity badge in the System column, per the performance-and-scalability bounded-row contract.", + "status": "planned", + "recorded_at": "2026-06-26", + "target_id": "v6-product-lane-expansion", + "current_lane_ids": [ + "L8", + "L13" + ], + "coverage_gap_ids": [ + "availability-as-resource-facet" + ], + "subsystem_ids": [ + "frontend-primitives", + "monitoring", + "unified-resources" + ] } ], "work_claims": [ { - "id": "opencode-coverage-gap-protection-posture-attention-queue", - "agent_id": "opencode", - "summary": "Patrol row-level actionable state labels: translate hidden investigation state into plain-language badges on collapsed Patrol finding rows", + "id": "codex-candidate-lane-availability-as-resource-facet", + "agent_id": "codex", + "summary": "Attach agentless availability checks as a facet on the known resource they monitor (explicit link + unique address correlation + network-endpoint fallback) and surface the facet on the platform resource row per the bounded-row contract.", "target_id": "v6-product-lane-expansion", - "claimed_at": "2026-06-25T22:24:34Z", - "heartbeat_at": "2026-06-25T22:24:34Z", - "expires_at": "2026-06-26T00:24:34Z", + "claimed_at": "2026-06-26T10:12:44Z", + "heartbeat_at": "2026-06-26T10:12:44Z", + "expires_at": "2026-06-26T14:12:44Z", "work_item": { - "kind": "coverage-gap", - "id": "protection-posture-attention-queue" + "kind": "candidate-lane", + "id": "availability-as-resource-facet" } } ], diff --git a/docs/release-control/v6/internal/subsystems/api-contracts.md b/docs/release-control/v6/internal/subsystems/api-contracts.md index c85f5102b4..b44bf12a38 100644 --- a/docs/release-control/v6/internal/subsystems/api-contracts.md +++ b/docs/release-control/v6/internal/subsystems/api-contracts.md @@ -993,6 +993,12 @@ payload shape change when the portal presents compact client rows. Mock mode must expose authored availability targets through those same list, saved-test, and connections-ledger payloads so demo endpoints exercise the canonical API contract rather than a frontend-only fixture. + Availability target payloads carry an optional `linkedResourceId` field + that, when set to a known resource id, requests the unified-resource + registry to attach the probe facet onto that resource instead of minting + a standalone `network-endpoint`. The field is advisory: the registry + still applies IP-correlation when no explicit link is provided, and a + link that would overwrite a different target's facet is ignored. 20. `frontend-modern/src/components/Settings/NodeModalAuthenticationSection.tsx` shared with `agent-lifecycle`: the node setup authentication section is both an agent lifecycle control surface and a shared API-backed install/setup contract boundary. 21. `frontend-modern/src/components/Settings/NodeModalBasicInfoSection.tsx` shared with `agent-lifecycle`: the node setup basic-info section is both an agent lifecycle control surface and a shared API-backed install/setup contract boundary. 22. `frontend-modern/src/components/Settings/nodeModalModel.ts` shared with `agent-lifecycle`: the pure node setup modal model is both an agent lifecycle control surface and a shared API-backed install/setup contract boundary. diff --git a/docs/release-control/v6/internal/subsystems/monitoring.md b/docs/release-control/v6/internal/subsystems/monitoring.md index 13dfa8bbb4..e0366e12f8 100644 --- a/docs/release-control/v6/internal/subsystems/monitoring.md +++ b/docs/release-control/v6/internal/subsystems/monitoring.md @@ -390,6 +390,11 @@ load from the config persistence boundary, schedule through the default low-overhead check, while TCP and HTTP are canonical fallbacks for devices or runtimes where ICMP is unavailable or the useful signal is a port or web interface. +Supplemental records carry the saved target's optional `LinkedResourceID` +forward into `AvailabilityData` so the unified-resource registry can attach +the probe facet onto the referenced resource. Monitoring does not perform the +attach decision itself; it only forwards the link hint for the registry to +resolve. Availability target kind is monitoring-owned runtime metadata, not a frontend guess. Saved targets carry the bounded `targetKind` values `machine`, `service`, and `device`; monitoring must preserve that value in probe status, supplemental diff --git a/docs/release-control/v6/internal/subsystems/performance-and-scalability.md b/docs/release-control/v6/internal/subsystems/performance-and-scalability.md index 2968325cb2..499f503940 100644 --- a/docs/release-control/v6/internal/subsystems/performance-and-scalability.md +++ b/docs/release-control/v6/internal/subsystems/performance-and-scalability.md @@ -1433,12 +1433,14 @@ blocking `local-only`/`restricted` posture; non-blocking `sensitive` + `local-first` and redaction-only metadata belongs in Data Handling, detail, and AI/governance surfaces instead of spending default row visual budget. Agentless availability evidence belongs on that same bounded row path. -Infrastructure `network-endpoint` rows may replace otherwise empty host metric -slots with one compact inline target/result readout, while the System column -owns the protocol identity badge (`ICMP`, `TCP`, or `HTTP`). Recent check -timing, latency history, and fuller failure context may stay in bounded -tooltip or drawer detail, but the table must not duplicate the same probe -protocol and result text across the resource identity cell and metric cells. +Any resource row carrying an `AvailabilityData` facet—whether a standalone +`network-endpoint` or a known guest that inherited the facet through explicit +link or IP correlation—may replace otherwise empty host metric slots with one +compact inline target/result readout, while the System column owns the +protocol identity badge (`ICMP`, `TCP`, or `HTTP`). Recent check timing, +latency history, and fuller failure context may stay in bounded tooltip or +drawer detail, but the table must not duplicate the same probe protocol and +result text across the resource identity cell and metric cells. That text must derive from the existing resource payload and shared presentation helper instead of adding a per-row fetch, extra hydration pass, or unbounded badge stack. diff --git a/docs/release-control/v6/internal/subsystems/storage-recovery.md b/docs/release-control/v6/internal/subsystems/storage-recovery.md index 54cdc2eaaa..e2d4bbf929 100644 --- a/docs/release-control/v6/internal/subsystems/storage-recovery.md +++ b/docs/release-control/v6/internal/subsystems/storage-recovery.md @@ -1203,6 +1203,12 @@ recovery scope, or a storage/recovery-owned secret source. disk cache, ZFS ARC sizing, storage-tier health, or capacity-planning evidence; disk and pool truth stays on the canonical storage and physical-disk resources. + `internal/unifiedresources/types.go` also carries `AvailabilityData` + (probe protocol, address, latency, failure state) as a resource facet for + agentless monitoring. Storage and recovery must not treat that availability + facet as protection status, backup health, or recovery readiness; an + unreachable probe on a storage resource is monitoring evidence, not a + backup or recovery failure. ## Forbidden Paths @@ -1233,6 +1239,7 @@ recovery scope, or a storage/recovery-owned secret source. 18. Letting the recovery details panel lead with transport-shaped payloads; operator-facing details must summarize outcome, artifact, target, restore readiness, and readable metadata labels first, while raw JSON and provider-specific keys stay behind an explicitly technical disclosure. 19. Letting protection coverage navigation bypass the canonical recovery workspace and route-state owner; coverage actions may focus stale inventory, attention inventory, or all protected items, but they must not mutate local-only filter state or revive passive posture-counter cards as the navigation owner. 20. Letting storage or recovery surfaces invoke retired self-hosted trial acquisition; `POST /api/license/trial/start` and `/auth/trial-activate` must stay closed on the ordinary self-hosted router, and storage/recovery-adjacent billing or support handoffs must not treat trial activation as recovery identity, restore proof, or backup transport state. +21. Treating an `AvailabilityData` facet on a storage resource as backup, protection, or recovery evidence; an agentless probe attached to a NAS, PBS, or datastore resource is monitoring reachability, not backup success, snapshot health, or restore readiness. Storage and recovery must read protection status from the canonical backup and storage resources, not from the availability probe facet. ## Completion Obligations diff --git a/docs/release-control/v6/internal/subsystems/unified-resources.md b/docs/release-control/v6/internal/subsystems/unified-resources.md index 68d5fb9016..1475a567b8 100644 --- a/docs/release-control/v6/internal/subsystems/unified-resources.md +++ b/docs/release-control/v6/internal/subsystems/unified-resources.md @@ -1451,14 +1451,26 @@ Agentless availability checks are now canonical resources rather than connection-only status rows. `SourceAvailability` emits `network-endpoint` records with the saved target id, probe address, protocol, cadence, last check, failure count, and threshold in `AvailabilityData`. Registry merge policy must -preserve that payload and incident state without trying to fold endpoints into -hosts, VMs, or storage resources solely because an address matches. +preserve that payload and incident state. An availability record attaches as a +facet onto a known resource when (a) the target carries an explicit +`LinkedResourceID` referencing a resource already in the registry, or (b) the +probe address unambiguously matches exactly one known resource by IP through +`FindCandidates` with reason `ip` or `hostname+ip` (confidence at or above the +merge threshold). Fuzzy hostname-only correlation (reason `hostname`, confidence +below threshold) must not attach. When attached, the known resource inherits +the `AvailabilityData` facet and `SourceAvailability` in its source list, and +no standalone `network-endpoint` is minted. When no link resolves, the record +falls back to a standalone `network-endpoint` as before. A second probe must +not overwrite a facet already attached by a different target; the second probe +stays standalone in that case. Frontend resource adapters must preserve that same availability identity on both REST and realtime paths: a thin `network-endpoint` update with availability data is still `platformType=availability`, `sourceType=api`, and must not regress to a generic platform badge in infrastructure rows or drawers. Infrastructure row presentation must also consume that availability payload as -operator evidence, not only as badge identity. `network-endpoint` rows should +operator evidence, not only as badge identity. Any resource row carrying an +`AvailabilityData` facet—whether a standalone `network-endpoint` or a known +guest that inherited the facet through explicit link or IP correlation—must surface one visible probe readout from either the top-level availability field or the live-state `platformData.availability` mirror: the System column uses the probe protocol as the compact identity badge (`ICMP`, `TCP`, or `HTTP`), diff --git a/frontend-modern/src/api/availabilityTargets.ts b/frontend-modern/src/api/availabilityTargets.ts index 42559931d7..127ba34a07 100644 --- a/frontend-modern/src/api/availabilityTargets.ts +++ b/frontend-modern/src/api/availabilityTargets.ts @@ -29,6 +29,7 @@ export interface AvailabilityTarget { protocol: AvailabilityProbeProtocol; port?: number; path?: string; + linkedResourceId?: string; enabled: boolean; pollIntervalSeconds?: number; timeoutMillis?: number; diff --git a/frontend-modern/src/components/Infrastructure/UnifiedResourceHostTableCard.tsx b/frontend-modern/src/components/Infrastructure/UnifiedResourceHostTableCard.tsx index f1ce598391..a9377ea2e8 100644 --- a/frontend-modern/src/components/Infrastructure/UnifiedResourceHostTableCard.tsx +++ b/frontend-modern/src/components/Infrastructure/UnifiedResourceHostTableCard.tsx @@ -428,6 +428,16 @@ export const UnifiedResourceHostTableCard: Component )} + + {(probe) => ( + + {probe().methodLabel} + + )} + +