=== GROUNDED FACTS FOR W3d ADAPTER-UPDATE APPLY LOOP === 1. CMD_ADAPTER_UPDATE APPLY LOOP (gh_release path): File: /c/Users/decid/Documents/projects/spt-core/crates/spt/src/cli.rs - Function entry: line 6070 (fn cmd_adapter_update) - Loop over registered adapters: line 6098 (for (record, manifest) in &targets) - Compute latest version: line 6117 (gh_latest_release_version) - Version-compare decision: line 6127 (version_is_newer) - Stage/fetch archive: line 6142 (stage_gh_release_archive call) - Optional verify signature: line 6157 (verify_staged_archive call) - Compute dest path: line 6177 (let dest = adapters.join("_github").join(&safe)) - Remove existing dest: line 6180 (std::fs::remove_dir_all(&dest)) - Extract archive: line 6188 (extract_release_archive(&staged, &dest)) - Re-register: line 6195 (registry::register(adapters, &dest, now_ms())) KEY EXTRACTION POINTS TO REPLACE (lines 6180 + 6188): 6180: let _ = std::fs::remove_dir_all(&dest); 6188: let extracted = extract_release_archive(&staged, &dest); --- 2. CLI → DAEMON/BROKER IPC CLIENT: File: /c/Users/decid/Documents/projects/spt-core/crates/spt-daemon/src/brain.rs Main client struct & methods: - Brain struct definition: line 240 (pub struct Brain) - Cold start (connect to broker): line 285 (pub fn cold_start(name: &str, now_ms: u64)) - Send envelope method: line 1298 (fn send(&mut self, kind: &str, payload: serde_json::Value)) - Send effect (idempotent): line 481 (pub fn send_effect(&mut self, op_id: u64, bytes: &[u8])) - Read frame from broker: line 1319 (fn read_frame_until(&mut self, deadline: Option)) Broker socket name (from cli.rs line 2165): File: /c/Users/decid/Documents/projects/spt-core/crates/spt-daemon/src/lib.rs (or broker.rs) - Socket name fn: spt_daemon::broker_socket_name() Pattern example (cmd_daemon_status in cli.rs line 2164-2169): ``` let net_up = spt_daemon::brain::Brain::cold_start( &spt_daemon::broker_socket_name(), now_ms(), ) .ok() .and_then(|mut b| b.net_status().ok()) ``` --- 3. LIVE-ENDPOINT-ON-ADAPTER DETECTION (CLI-side): File: /c/Users/decid/Documents/projects/spt-core/crates/spt/src/roster.rs - Enumerate function: line 25 (pub fn enumerate() -> Vec) - Loop reading perches: line 31 (for entry in entries.flatten()) - Read info.json: line 36 (info::read_info(&perch_path)) - Extract adapter field: line 156 in /c/Users/decid/Documents/projects/spt-core/crates/spt-store/src/info.rs (pub adapter: Option) - Check liveness: line 43 (liveness::is_perch_alive(&perch_path)) Return PerchEntry fields available at line 15-21: - id: String - state: String - address: Option - ready: bool - alive: bool To detect live endpoints running adapter X: 1. Call roster::enumerate() to get all perches 2. For each entry, read info::read_info to get InfoJson 3. Check InfoJson.adapter == Some(X) AND entry.alive == true --- 4. BROKER HANDLE_CONN DISPATCH + REPLY HELPERS: File: /c/Users/decid/Documents/projects/spt-core/crates/spt-daemon/src/broker.rs - handle_conn function: line 1347 (fn handle_conn(self: &Arc, conn: Stream)) - Main dispatch match: line 1364 (match env.kind.as_str()) - Example KIND_SPAWN arm: line 1365-1368 KIND_SPAWN => match self.dispatch_spawn(env, &send) { Ok(id) => my_subs.push(id), Err(msg) => send_error(&send, &msg), } - Example KIND_KILL arm: line 1388-1391 KIND_KILL => { if let Err(msg) = self.dispatch_kill(env) { send_error(&send, &msg); } } Dispatch function signatures: - dispatch_spawn: line 1508 (fn dispatch_spawn(&self, env: Envelope, send: &SharedSend) -> Result) - dispatch_kill: line 2244 (fn dispatch_kill(&self, env: Envelope) -> Result<(), String>) Reply-frame helpers: - send_frame (success): line 2261 (fn send_frame(send: &SharedSend, frame: &Envelope)) - send_error (error): line 2268 (fn send_error(send: &SharedSend, message: &str)) Write-frame util: write_frame(&mut SendHalf, &Envelope) from crate::codec Read-frame util: read_frame(&mut ReceivePart, ) -> io::Result from crate::codec Message KIND constants for adapter-apply: File: /c/Users/decid/Documents/projects/spt-core/crates/spt-daemon/src/msg.rs - KIND_SPAWN: line 24 - KIND_KILL: line 40 - KIND_ERROR: line 78 - All broker→brain reply kinds: KIND_SPAWNED (line 26), KIND_APPLIED (line 76), etc. --- PATTERN FOR NEW KIND_ADAPTER_APPLY: 1. Add to msg.rs (after line 78): pub const KIND_ADAPTER_APPLY: &str = "adapter-apply"; pub const KIND_ADAPTER_APPLIED: &str = "adapter-applied"; 2. Add request struct in msg.rs: #[derive(Debug, Serialize, Deserialize)] pub struct AdapterApplyReq { pub adapter_name: String, pub staged_path: PathBuf, pub dest_path: PathBuf, pub op_id: Option, } 3. In broker.rs handle_conn (line 1364 match), add: KIND_ADAPTER_APPLY => { if let Err(msg) = self.dispatch_adapter_apply(env, &send) { send_error(&send, &msg); } } 4. Implement dispatch_adapter_apply (after dispatch_kill at line 2244): fn dispatch_adapter_apply(&self, env: Envelope, send: &SharedSend) -> Result<(), String> { let req: AdapterApplyReq = serde_json::from_value(env.payload) .map_err(|e| format!("bad adapter-apply payload: {e}"))?; // Call crc_swap::apply_crc_swap or hand to live-endpoint broker // Send reply via send_frame(send, &Envelope::new(KIND_ADAPTER_APPLIED, ...)) } 5. CLI side (cli.rs line 6180+6188): replace with: // Send to broker instead of direct filesystem let mut brain = Brain::cold_start(&broker_socket_name(), now_ms()) .map_err(|e| format!("connect: {e}"))?; brain.send( "adapter-apply", serde_json::json!({ "adapter_name": record.name, "staged_path": staged.display().to_string(), "dest_path": dest.display().to_string(), }), )?; let reply = brain.read_frame_until(None)?; // Check reply.kind == KIND_ADAPTER_APPLIED