From c2418e5ff25bc5f6b3aa50eb4d89ff11eb953461 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Sat, 16 May 2026 00:29:54 +0300 Subject: [PATCH] Gate fabric session endpoint through node config --- .../rap-node-agent/cmd/rap-host-agent/main.go | 3 ++ .../rap-node-agent/cmd/rap-node-agent/main.go | 33 +++++++++++++++++++ .../cmd/rap-node-agent/main_test.go | 7 ++++ .../rap-node-agent/internal/agent/payload.go | 6 +++- .../rap-node-agent/internal/config/config.go | 2 ++ .../internal/config/config_test.go | 4 +++ .../internal/hostagent/config.go | 1 + .../internal/hostagent/docker.go | 1 + .../internal/hostagent/linux.go | 1 + .../internal/hostagent/profile.go | 4 +++ .../internal/hostagent/update.go | 1 + .../internal/hostagent/windows.go | 1 + .../DISTRIBUTED_FABRIC_NODE_PROTOCOL_PLAN.md | 3 ++ 13 files changed, 66 insertions(+), 1 deletion(-) diff --git a/agents/rap-node-agent/cmd/rap-host-agent/main.go b/agents/rap-node-agent/cmd/rap-host-agent/main.go index cde5873..3801454 100644 --- a/agents/rap-node-agent/cmd/rap-host-agent/main.go +++ b/agents/rap-node-agent/cmd/rap-host-agent/main.go @@ -203,6 +203,7 @@ func runInstallLinux(ctx context.Context, args []string) error { fs.BoolVar(&cfg.RuntimeConfig.WorkloadSupervisionEnabled, "workload-supervision-enabled", getenvBool("RAP_WORKLOAD_SUPERVISION_ENABLED", false), "Enable node-agent workload status reporting.") fs.BoolVar(&cfg.RuntimeConfig.MeshSyntheticRuntimeEnabled, "mesh-synthetic-runtime-enabled", getenvBool("RAP_MESH_SYNTHETIC_RUNTIME_ENABLED", true), "Enable synthetic mesh runtime.") fs.BoolVar(&cfg.RuntimeConfig.MeshProductionForwardingEnabled, "mesh-production-forwarding-enabled", getenvBool("RAP_MESH_PRODUCTION_FORWARDING_ENABLED", false), "Enable production forwarding gate; runtime still fail-closed if unavailable.") + fs.BoolVar(&cfg.RuntimeConfig.MeshFabricSessionEnabled, "mesh-fabric-session-enabled", getenvBool("RAP_MESH_FABRIC_SESSION_ENABLED", false), "Enable authenticated fabric session WebSocket endpoint.") fs.StringVar(&cfg.RuntimeConfig.MeshListenAddr, "mesh-listen-addr", getenv("RAP_MESH_LISTEN_ADDR", ":19131"), "Synthetic mesh HTTP listen address.") fs.StringVar(&cfg.RuntimeConfig.MeshListenPortMode, "mesh-listen-port-mode", getenv("RAP_MESH_LISTEN_PORT_MODE", "auto"), "Mesh listen port behavior: manual, auto, or disabled.") fs.IntVar(&cfg.RuntimeConfig.MeshListenAutoPortStart, "mesh-listen-auto-port-start", getenvInt("RAP_MESH_LISTEN_AUTO_PORT_START", 19131), "First port used when mesh listen port mode is auto.") @@ -284,6 +285,7 @@ func runInstallWindows(ctx context.Context, args []string) error { fs.BoolVar(&cfg.RuntimeConfig.WorkloadSupervisionEnabled, "workload-supervision-enabled", getenvBool("RAP_WORKLOAD_SUPERVISION_ENABLED", false), "Enable node-agent workload status reporting.") fs.BoolVar(&cfg.RuntimeConfig.MeshSyntheticRuntimeEnabled, "mesh-synthetic-runtime-enabled", getenvBool("RAP_MESH_SYNTHETIC_RUNTIME_ENABLED", true), "Enable synthetic mesh runtime.") fs.BoolVar(&cfg.RuntimeConfig.MeshProductionForwardingEnabled, "mesh-production-forwarding-enabled", getenvBool("RAP_MESH_PRODUCTION_FORWARDING_ENABLED", false), "Enable production forwarding gate; runtime still fail-closed if unavailable.") + fs.BoolVar(&cfg.RuntimeConfig.MeshFabricSessionEnabled, "mesh-fabric-session-enabled", getenvBool("RAP_MESH_FABRIC_SESSION_ENABLED", false), "Enable authenticated fabric session WebSocket endpoint.") fs.StringVar(&cfg.RuntimeConfig.MeshListenAddr, "mesh-listen-addr", getenv("RAP_MESH_LISTEN_ADDR", ":19131"), "Synthetic mesh HTTP listen address.") fs.StringVar(&cfg.RuntimeConfig.MeshListenPortMode, "mesh-listen-port-mode", getenv("RAP_MESH_LISTEN_PORT_MODE", "auto"), "Mesh listen port behavior: manual, auto, or disabled.") fs.IntVar(&cfg.RuntimeConfig.MeshListenAutoPortStart, "mesh-listen-auto-port-start", getenvInt("RAP_MESH_LISTEN_AUTO_PORT_START", 19131), "First port used when mesh listen port mode is auto.") @@ -771,6 +773,7 @@ func parseInstall(args []string) (installCommandConfig, error) { fs.BoolVar(&cfg.WorkloadSupervisionEnabled, "workload-supervision-enabled", getenvBool("RAP_WORKLOAD_SUPERVISION_ENABLED", false), "Enable node-agent workload status reporting.") fs.BoolVar(&cfg.MeshSyntheticRuntimeEnabled, "mesh-synthetic-runtime-enabled", getenvBool("RAP_MESH_SYNTHETIC_RUNTIME_ENABLED", false), "Enable synthetic mesh runtime.") fs.BoolVar(&cfg.MeshProductionForwardingEnabled, "mesh-production-forwarding-enabled", getenvBool("RAP_MESH_PRODUCTION_FORWARDING_ENABLED", false), "Enable production forwarding gate; runtime still fail-closed if unavailable.") + fs.BoolVar(&cfg.MeshFabricSessionEnabled, "mesh-fabric-session-enabled", getenvBool("RAP_MESH_FABRIC_SESSION_ENABLED", false), "Enable authenticated fabric session WebSocket endpoint.") fs.StringVar(&cfg.MeshListenAddr, "mesh-listen-addr", getenv("RAP_MESH_LISTEN_ADDR", ""), "Synthetic mesh HTTP listen address inside container.") fs.StringVar(&cfg.MeshListenPortMode, "mesh-listen-port-mode", getenv("RAP_MESH_LISTEN_PORT_MODE", ""), "Mesh listen port behavior: manual, auto, or disabled.") fs.IntVar(&cfg.MeshListenAutoPortStart, "mesh-listen-auto-port-start", getenvInt("RAP_MESH_LISTEN_AUTO_PORT_START", 0), "First port used when mesh listen port mode is auto.") 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 efc904d..8d123b9 100644 --- a/agents/rap-node-agent/cmd/rap-node-agent/main.go +++ b/agents/rap-node-agent/cmd/rap-node-agent/main.go @@ -755,6 +755,15 @@ func startSyntheticMeshEndpoint(ctx context.Context, _ context.CancelFunc, cfg c } log.Printf("fabric_service_channel_access_event=%s", string(payload)) }, + FabricSessionEnabled: cfg.MeshFabricSessionEnabled, + FabricSessionLogger: func(entry mesh.FabricSessionEventLogEntry) { + payload, err := json.Marshal(entry) + if err != nil { + log.Printf("fabric session event marshal failed: %v", err) + return + } + log.Printf("fabric_session_event=%s", string(payload)) + }, RemoteWorkspaceFrameSink: remoteWorkspaceFrameSink, ProductionRoutes: routes, VPNPacketIngress: vpnFabricIngress, @@ -1639,6 +1648,15 @@ func applyRefreshedSyntheticMeshConfig(ctx context.Context, cfg config.Config, i } log.Printf("fabric_service_channel_access_event=%s", string(payload)) }, + FabricSessionEnabled: cfg.MeshFabricSessionEnabled, + FabricSessionLogger: func(entry mesh.FabricSessionEventLogEntry) { + payload, err := json.Marshal(entry) + if err != nil { + log.Printf("fabric session event marshal failed: %v", err) + return + } + log.Printf("fabric_session_event=%s", string(payload)) + }, RemoteWorkspaceFrameSink: meshState.RemoteWorkspaceFrameSink, ProductionRoutes: loadedConfig.Routes, VPNPacketIngress: vpnFabricIngress, @@ -2447,6 +2465,21 @@ func heartbeatPayload(cfg config.Config, identity state.Identity, meshState *syn if cfg.MeshProductionForwardingEnabled || (meshState != nil && meshState.ProductionForwardingEnabled) { payload.Capabilities["mesh_production_forwarding"] = true } + if cfg.MeshFabricSessionEnabled { + payload.Metadata["fabric_session_endpoint_report"] = map[string]any{ + "schema_version": "rap.fabric_session_endpoint_report.v1", + "enabled": true, + "transport": "websocket_binary_frames", + "path": "/mesh/v1/fabric/session/ws", + "auth": "rap_fsn_token_with_optional_signed_authority", + "protocol": "rap.fabric_data_session.v1", + "service_neutral": true, + "traffic_isolation": "logical_streams", + "observed_at": observedAt.UTC().Format(time.RFC3339Nano), + } + payload.Capabilities["fabric_session_websocket_endpoint"] = true + payload.Capabilities["fabric_data_session_v1"] = true + } if meshState != nil && meshState.ConfigLoadError != "" { payload.HealthStatus = "warning" } 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 6fc62e0..5fddc99 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 @@ -634,6 +634,7 @@ func TestHeartbeatPayloadIncludesMeshEndpointReport(t *testing.T) { MeshRegion: "eu", MeshSyntheticRuntimeEnabled: true, MeshProductionForwardingEnabled: true, + MeshFabricSessionEnabled: true, }, state.Identity{ ClusterID: "cluster-1", NodeID: "node-a", @@ -652,6 +653,12 @@ func TestHeartbeatPayloadIncludesMeshEndpointReport(t *testing.T) { if payload.Capabilities["mesh_dynamic_endpoint_reporting"] != true { t.Fatalf("dynamic endpoint capability missing: %+v", payload.Capabilities) } + if payload.Capabilities["fabric_session_websocket_endpoint"] != true || payload.Capabilities["fabric_data_session_v1"] != true { + t.Fatalf("fabric session capabilities missing: %+v", payload.Capabilities) + } + if report, ok := payload.Metadata["fabric_session_endpoint_report"].(map[string]any); !ok || report["path"] != "/mesh/v1/fabric/session/ws" { + t.Fatalf("fabric session endpoint report missing: %+v", payload.Metadata) + } } func TestHeartbeatPayloadReportsMeshListenerFailureWithoutKillingHeartbeat(t *testing.T) { diff --git a/agents/rap-node-agent/internal/agent/payload.go b/agents/rap-node-agent/internal/agent/payload.go index b125632..119db58 100644 --- a/agents/rap-node-agent/internal/agent/payload.go +++ b/agents/rap-node-agent/internal/agent/payload.go @@ -7,7 +7,7 @@ import ( "github.com/example/remote-access-platform/agents/rap-node-agent/internal/state" ) -const Version = "0.2.279-vpnperf" +const Version = "0.2.280-fabricsession" func EnrollmentPayload(clusterID, joinToken string, identity state.Identity) client.EnrollRequest { return client.EnrollRequest{ @@ -37,6 +37,8 @@ func EnrollmentPayload(clusterID, joinToken string, identity state.Identity) cli "vpn_fabric_packet_transport": true, "vpn_local_gateway_shortcut": false, "vpn_farm_owned_dataplane": true, + "fabric_data_session_v1": true, + "fabric_session_websocket_smoke": true, "vpn_backend_relay_fallback": false, "fabric_service_channel_required": true, "external_backend_entry_proxy": true, @@ -64,6 +66,8 @@ func HeartbeatPayload() client.HeartbeatRequest { "vpn_fabric_packet_transport": true, "vpn_local_gateway_shortcut": false, "vpn_farm_owned_dataplane": true, + "fabric_data_session_v1": true, + "fabric_session_websocket_smoke": true, "vpn_backend_relay_fallback": false, "fabric_service_channel_required": true, "external_backend_entry_proxy": true, diff --git a/agents/rap-node-agent/internal/config/config.go b/agents/rap-node-agent/internal/config/config.go index 27cc585..20fb744 100644 --- a/agents/rap-node-agent/internal/config/config.go +++ b/agents/rap-node-agent/internal/config/config.go @@ -26,6 +26,7 @@ type Config struct { EnrollmentPollTimeout time.Duration MeshSyntheticRuntimeEnabled bool MeshProductionForwardingEnabled bool + MeshFabricSessionEnabled bool MeshProductionObservationSinkCapacity int MeshListenAddr string MeshListenPortMode string @@ -63,6 +64,7 @@ func Load(args []string, env map[string]string) (Config, error) { fs.BoolVar(&cfg.WorkloadSupervisionEnabled, "workload-supervision-enabled", getEnvBool(env, "RAP_WORKLOAD_SUPERVISION_ENABLED", false), "Enable desired workload polling and status reporting. Disabled by default while service runtime is not implemented.") fs.BoolVar(&cfg.MeshSyntheticRuntimeEnabled, "mesh-synthetic-runtime-enabled", getEnvBool(env, "RAP_MESH_SYNTHETIC_RUNTIME_ENABLED", false), "Enable C17A synthetic fabric probe runtime. Disabled by default.") fs.BoolVar(&cfg.MeshProductionForwardingEnabled, "mesh-production-forwarding-enabled", getEnvBool(env, "RAP_MESH_PRODUCTION_FORWARDING_ENABLED", false), "Enable production fabric-control direct next-hop forwarding gate. Disabled by default.") + fs.BoolVar(&cfg.MeshFabricSessionEnabled, "mesh-fabric-session-enabled", getEnvBool(env, "RAP_MESH_FABRIC_SESSION_ENABLED", false), "Enable authenticated fabric session WebSocket endpoint. Disabled by default.") fs.IntVar(&cfg.MeshProductionObservationSinkCapacity, "mesh-production-observation-sink-capacity", getEnvSignedInt(env, "RAP_MESH_PRODUCTION_OBSERVATION_SINK_CAPACITY", 0), "Bounded local metadata-only production envelope observation sink capacity. Disabled when 0.") fs.StringVar(&cfg.MeshListenAddr, "mesh-listen-addr", getEnv(env, "RAP_MESH_LISTEN_ADDR", ""), "Listen address for disabled-by-default C17E synthetic mesh HTTP endpoint.") fs.StringVar(&cfg.MeshListenPortMode, "mesh-listen-port-mode", getEnv(env, "RAP_MESH_LISTEN_PORT_MODE", "manual"), "Mesh listen port behavior: manual, auto, or disabled.") diff --git a/agents/rap-node-agent/internal/config/config_test.go b/agents/rap-node-agent/internal/config/config_test.go index da28198..7d2864b 100644 --- a/agents/rap-node-agent/internal/config/config_test.go +++ b/agents/rap-node-agent/internal/config/config_test.go @@ -20,6 +20,7 @@ func TestLoadConfigFromEnvAndArgs(t *testing.T) { "RAP_ENROLLMENT_POLL_TIMEOUT_SECONDS": "30", "RAP_MESH_SYNTHETIC_RUNTIME_ENABLED": "true", "RAP_MESH_PRODUCTION_FORWARDING_ENABLED": "true", + "RAP_MESH_FABRIC_SESSION_ENABLED": "true", "RAP_MESH_PRODUCTION_OBSERVATION_SINK_CAPACITY": "5", "RAP_MESH_LISTEN_ADDR": "127.0.0.1:19001", "RAP_MESH_LISTEN_PORT_MODE": "auto", @@ -66,6 +67,9 @@ func TestLoadConfigFromEnvAndArgs(t *testing.T) { if !cfg.MeshProductionForwardingEnabled { t.Fatal("MeshProductionForwardingEnabled = false, want true") } + if !cfg.MeshFabricSessionEnabled { + t.Fatal("MeshFabricSessionEnabled = false, want true") + } if cfg.MeshProductionObservationSinkCapacity != 5 { t.Fatalf("MeshProductionObservationSinkCapacity = %d, want 5", cfg.MeshProductionObservationSinkCapacity) } diff --git a/agents/rap-node-agent/internal/hostagent/config.go b/agents/rap-node-agent/internal/hostagent/config.go index 341bd44..0429561 100644 --- a/agents/rap-node-agent/internal/hostagent/config.go +++ b/agents/rap-node-agent/internal/hostagent/config.go @@ -29,6 +29,7 @@ type RuntimeConfig struct { WorkloadSupervisionEnabled bool MeshSyntheticRuntimeEnabled bool MeshProductionForwardingEnabled bool + MeshFabricSessionEnabled bool MeshListenAddr string MeshListenPortMode string MeshListenAutoPortStart int diff --git a/agents/rap-node-agent/internal/hostagent/docker.go b/agents/rap-node-agent/internal/hostagent/docker.go index 4eab7e3..70ea9f0 100644 --- a/agents/rap-node-agent/internal/hostagent/docker.go +++ b/agents/rap-node-agent/internal/hostagent/docker.go @@ -264,6 +264,7 @@ func NodeAgentEnvWithStateDir(cfg RuntimeConfig, stateDir string) []string { "RAP_WORKLOAD_SUPERVISION_ENABLED=" + boolString(cfg.WorkloadSupervisionEnabled), "RAP_MESH_SYNTHETIC_RUNTIME_ENABLED=" + boolString(cfg.MeshSyntheticRuntimeEnabled), "RAP_MESH_PRODUCTION_FORWARDING_ENABLED=" + boolString(cfg.MeshProductionForwardingEnabled), + "RAP_MESH_FABRIC_SESSION_ENABLED=" + boolString(cfg.MeshFabricSessionEnabled), } if cfg.JoinToken != "" { env = append(env, "RAP_JOIN_TOKEN="+cfg.JoinToken) diff --git a/agents/rap-node-agent/internal/hostagent/linux.go b/agents/rap-node-agent/internal/hostagent/linux.go index dd9973f..4430432 100644 --- a/agents/rap-node-agent/internal/hostagent/linux.go +++ b/agents/rap-node-agent/internal/hostagent/linux.go @@ -72,6 +72,7 @@ func LinuxInstallConfigFromProfile(profile LinuxInstallProfile) LinuxInstallConf WorkloadSupervisionEnabled: profile.WorkloadSupervisionEnabled, MeshSyntheticRuntimeEnabled: profile.MeshSyntheticRuntimeEnabled, MeshProductionForwardingEnabled: profile.MeshProductionForwardingEnabled, + MeshFabricSessionEnabled: profile.MeshFabricSessionEnabled, MeshListenAddr: profile.MeshListenAddr, MeshListenPortMode: profile.MeshListenPortMode, MeshListenAutoPortStart: profile.MeshListenAutoPortStart, diff --git a/agents/rap-node-agent/internal/hostagent/profile.go b/agents/rap-node-agent/internal/hostagent/profile.go index 86a266b..5233ff5 100644 --- a/agents/rap-node-agent/internal/hostagent/profile.go +++ b/agents/rap-node-agent/internal/hostagent/profile.go @@ -30,6 +30,7 @@ type DockerInstallProfile struct { WorkloadSupervisionEnabled bool `json:"workload_supervision_enabled"` MeshSyntheticRuntimeEnabled bool `json:"mesh_synthetic_runtime_enabled"` MeshProductionForwardingEnabled bool `json:"mesh_production_forwarding_enabled"` + MeshFabricSessionEnabled bool `json:"mesh_fabric_session_enabled"` MeshListenAddr string `json:"mesh_listen_addr"` MeshListenPortMode string `json:"mesh_listen_port_mode"` MeshListenAutoPortStart int `json:"mesh_listen_auto_port_start"` @@ -72,6 +73,7 @@ type WindowsInstallProfile struct { WorkloadSupervisionEnabled bool `json:"workload_supervision_enabled"` MeshSyntheticRuntimeEnabled bool `json:"mesh_synthetic_runtime_enabled"` MeshProductionForwardingEnabled bool `json:"mesh_production_forwarding_enabled"` + MeshFabricSessionEnabled bool `json:"mesh_fabric_session_enabled"` MeshListenAddr string `json:"mesh_listen_addr"` MeshListenPortMode string `json:"mesh_listen_port_mode"` MeshListenAutoPortStart int `json:"mesh_listen_auto_port_start"` @@ -104,6 +106,7 @@ type LinuxInstallProfile struct { WorkloadSupervisionEnabled bool `json:"workload_supervision_enabled"` MeshSyntheticRuntimeEnabled bool `json:"mesh_synthetic_runtime_enabled"` MeshProductionForwardingEnabled bool `json:"mesh_production_forwarding_enabled"` + MeshFabricSessionEnabled bool `json:"mesh_fabric_session_enabled"` MeshListenAddr string `json:"mesh_listen_addr"` MeshListenPortMode string `json:"mesh_listen_port_mode"` MeshListenAutoPortStart int `json:"mesh_listen_auto_port_start"` @@ -281,6 +284,7 @@ func RuntimeConfigFromProfile(profile DockerInstallProfile) RuntimeConfig { WorkloadSupervisionEnabled: profile.WorkloadSupervisionEnabled, MeshSyntheticRuntimeEnabled: profile.MeshSyntheticRuntimeEnabled, MeshProductionForwardingEnabled: profile.MeshProductionForwardingEnabled, + MeshFabricSessionEnabled: profile.MeshFabricSessionEnabled, MeshListenAddr: profile.MeshListenAddr, MeshListenPortMode: profile.MeshListenPortMode, MeshListenAutoPortStart: profile.MeshListenAutoPortStart, diff --git a/agents/rap-node-agent/internal/hostagent/update.go b/agents/rap-node-agent/internal/hostagent/update.go index 73ca570..ee896da 100644 --- a/agents/rap-node-agent/internal/hostagent/update.go +++ b/agents/rap-node-agent/internal/hostagent/update.go @@ -594,6 +594,7 @@ func (m DockerManager) runtimeConfigFromContainer(ctx context.Context, runner Co WorkloadSupervisionEnabled: parseBool(env["RAP_WORKLOAD_SUPERVISION_ENABLED"]), MeshSyntheticRuntimeEnabled: true, MeshProductionForwardingEnabled: parseBool(env["RAP_MESH_PRODUCTION_FORWARDING_ENABLED"]), + MeshFabricSessionEnabled: parseBool(env["RAP_MESH_FABRIC_SESSION_ENABLED"]), MeshListenAddr: env["RAP_MESH_LISTEN_ADDR"], MeshListenPortMode: env["RAP_MESH_LISTEN_PORT_MODE"], MeshListenAutoPortStart: parseInt(env["RAP_MESH_LISTEN_AUTO_PORT_START"]), diff --git a/agents/rap-node-agent/internal/hostagent/windows.go b/agents/rap-node-agent/internal/hostagent/windows.go index f507ed8..d5a70f3 100644 --- a/agents/rap-node-agent/internal/hostagent/windows.go +++ b/agents/rap-node-agent/internal/hostagent/windows.go @@ -66,6 +66,7 @@ func WindowsInstallConfigFromProfile(profile WindowsInstallProfile) WindowsInsta WorkloadSupervisionEnabled: profile.WorkloadSupervisionEnabled, MeshSyntheticRuntimeEnabled: profile.MeshSyntheticRuntimeEnabled, MeshProductionForwardingEnabled: profile.MeshProductionForwardingEnabled, + MeshFabricSessionEnabled: profile.MeshFabricSessionEnabled, MeshListenAddr: profile.MeshListenAddr, MeshListenPortMode: profile.MeshListenPortMode, MeshListenAutoPortStart: profile.MeshListenAutoPortStart, diff --git a/docs/architecture/DISTRIBUTED_FABRIC_NODE_PROTOCOL_PLAN.md b/docs/architecture/DISTRIBUTED_FABRIC_NODE_PROTOCOL_PLAN.md index eb0f46a..9bc2d1a 100644 --- a/docs/architecture/DISTRIBUTED_FABRIC_NODE_PROTOCOL_PLAN.md +++ b/docs/architecture/DISTRIBUTED_FABRIC_NODE_PROTOCOL_PLAN.md @@ -260,6 +260,9 @@ Status: started with a transport-neutral `io.Reader`/`io.Writer` frame loop, WebSocket frame adapter in `agents/rap-node-agent/internal/fabricproto`, and a gated/authenticated mesh smoke endpoint/client at `/mesh/v1/fabric/session/ws`. `rap-host-agent fabric-session-smoke` provides the first operator smoke command. +Node-agent exposes the endpoint only when `RAP_MESH_FABRIC_SESSION_ENABLED` / +`-mesh-fabric-session-enabled` is set, and reports the enabled endpoint in +heartbeat metadata. Deliverables: