diff --git a/agents/rap-node-agent/cmd/rap-node-agent/main.go b/agents/rap-node-agent/cmd/rap-node-agent/main.go index b7c1355..c387b57 100644 --- a/agents/rap-node-agent/cmd/rap-node-agent/main.go +++ b/agents/rap-node-agent/cmd/rap-node-agent/main.go @@ -499,12 +499,13 @@ func (s *vpnFabricEndpointObservationStore) Report(observedAt time.Time, maxEntr snapshot := s.Snapshot() if len(snapshot) == 0 { return map[string]any{ - "schema_version": "rap.vpn_fabric_endpoint_health_report.v1", - "observed_at": observedAt.UTC().Format(time.RFC3339Nano), - "total": 0, - "reported": 0, - "dropped": 0, - "observations": []mesh.EndpointCandidateHealthObservation{}, + "schema_version": "rap.vpn_fabric_endpoint_health_report.v1", + "reporter_node_id": s.reporterNodeID, + "observed_at": observedAt.UTC().Format(time.RFC3339Nano), + "total": 0, + "reported": 0, + "dropped": 0, + "observations": []mesh.EndpointCandidateHealthObservation{}, } } values := make([]mesh.EndpointCandidateHealthObservation, 0, len(snapshot)) @@ -522,12 +523,13 @@ func (s *vpnFabricEndpointObservationStore) Report(observedAt time.Time, maxEntr } reported := values[:maxEntries] return map[string]any{ - "schema_version": "rap.vpn_fabric_endpoint_health_report.v1", - "observed_at": observedAt.UTC().Format(time.RFC3339Nano), - "total": len(values), - "reported": len(reported), - "dropped": len(values) - len(reported), - "observations": reported, + "schema_version": "rap.vpn_fabric_endpoint_health_report.v1", + "reporter_node_id": s.reporterNodeID, + "observed_at": observedAt.UTC().Format(time.RFC3339Nano), + "total": len(values), + "reported": len(reported), + "dropped": len(values) - len(reported), + "observations": reported, } } diff --git a/agents/rap-node-agent/cmd/rap-node-agent/main_test.go b/agents/rap-node-agent/cmd/rap-node-agent/main_test.go index 51db325..0fcadca 100644 --- a/agents/rap-node-agent/cmd/rap-node-agent/main_test.go +++ b/agents/rap-node-agent/cmd/rap-node-agent/main_test.go @@ -804,6 +804,7 @@ func TestVPNFabricEndpointObservationReportIsBoundedAndNewestFirst(t *testing.T) } report := store.Report(base, 1) if report["schema_version"] != "rap.vpn_fabric_endpoint_health_report.v1" || + report["reporter_node_id"] != "node-a" || report["total"] != 2 || report["reported"] != 1 || report["dropped"] != 1 { diff --git a/docs/architecture/DISTRIBUTED_FABRIC_NODE_PROTOCOL_PLAN.md b/docs/architecture/DISTRIBUTED_FABRIC_NODE_PROTOCOL_PLAN.md index 7647a30..5821311 100644 --- a/docs/architecture/DISTRIBUTED_FABRIC_NODE_PROTOCOL_PLAN.md +++ b/docs/architecture/DISTRIBUTED_FABRIC_NODE_PROTOCOL_PLAN.md @@ -345,6 +345,8 @@ those remote health hints with local observations using the newest signal. Endpoint health observations include source and reporter node fields so control plane can distinguish local dial feedback from aggregated or policy-generated health hints. +The endpoint health heartbeat report also includes the reporter node id at the +report level for simpler multi-node ingestion and diagnostics. Deliverables: