diff --git a/SESSION_LOG_2026_07_18.md b/SESSION_LOG_2026_07_18.md index abcd59f..ff6a2a0 100644 --- a/SESSION_LOG_2026_07_18.md +++ b/SESSION_LOG_2026_07_18.md @@ -163,6 +163,26 @@ The canonical `test-env/run.sh` gate then passed end-to-end: 657 library tests, all serial Rust targets, release build, demos, and 11/11 integration scripts. No service mutation occurred during this audit. +PR #370 merged the topology correction through 14 green protected checks as +`b6a6d490610705d98a6d9a40c878ca46726df192`. The merged and reviewed trees +matched. Installed release SHA-256 +`569ae2eb7b8ce356bc05d43930d4459a88c19221a58935ad368ca42d1991e28b` +atomically and preserved the prior adapter binary at +`~/.cargo/bin/wire.pre-doctor-topology-20260718`; launchd was not restarted. + +The installed audit then exposed one last lifecycle-policy mismatch: +`sync_freshness` warned that an intentionally inactive identity's loop might be +wedged and recommended reactivation. The supervisor correctly had no worker +for that home, no lease was live, and no event was queued. GitNexus rated the +doctor-only change MEDIUM (five direct diagnostic/test callers, no execution +flows). TDD added an inactive-identity verdict: stale sync now PASSes only when +the supervisor is healthy, the current registered home has no cwd binding or +live lease, and nothing is queued. Queued work and explicitly live identities +retain the existing stale-sync WARN/FAIL behavior. The live candidate changed +that warning to: `identity inactive with no queued events; supervisor correctly +leaves its worker retired`. Final `test-env/run.sh` passed 659 library tests, +all serial Rust targets, the release demos, and 11/11 integration scripts. + ## Artifacts - `docs/superpowers/specs/2026-07-18-codex-thread-identity-design.md` — approved diff --git a/src/cli/status.rs b/src/cli/status.rs index 901eced..156f558 100644 --- a/src/cli/status.rs +++ b/src/cli/status.rs @@ -1488,6 +1488,47 @@ fn sync_freshness_verdict(last_sync_age: Option, pending_total: u64) -> Doc } } +fn sync_freshness_verdict_for_lifecycle( + last_sync_age: Option, + pending_total: u64, + worker_expected: bool, +) -> DoctorCheck { + if !worker_expected && pending_total == 0 { + DoctorCheck::pass( + "sync_freshness", + "identity inactive with no queued events; supervisor correctly leaves its worker retired", + ) + } else { + sync_freshness_verdict(last_sync_age, pending_total) + } +} + +fn current_session_worker_expected(pending_total: u64) -> bool { + if pending_total > 0 { + return true; + } + let Some(home) = std::env::var_os("WIRE_HOME").map(std::path::PathBuf::from) else { + return true; + }; + let Some(session) = crate::session::list_sessions() + .unwrap_or_default() + .into_iter() + .find(|session| session.home_dir == home) + else { + return true; + }; + if crate::retire::is_retired(&home) { + return false; + } + session.cwd.is_some() + || !crate::session_lifecycle::active_leases_at( + &home, + time::OffsetDateTime::now_utc(), + crate::platform::process_alive, + ) + .is_empty() +} + /// Check: the daemon's sync loop is actually advancing, not just alive. /// Reuses the same shared helpers `wire status` surfaces (last_sync age + /// pending-push breakdown) so the two agree by construction. @@ -1497,7 +1538,9 @@ fn check_sync_freshness() -> DoctorCheck { .iter() .map(|p| p.count) .sum(); - sync_freshness_verdict(last_sync_age, pending_total) + let (_, supervisor_alive, _, _, _) = supervisor_runtime_topology(); + let worker_expected = !supervisor_alive || current_session_worker_expected(pending_total); + sync_freshness_verdict_for_lifecycle(last_sync_age, pending_total, worker_expected) } /// Check: daemon running, exactly one instance, no orphans. @@ -2354,6 +2397,13 @@ mod doctor_tests { assert_eq!(c.status, "WARN"); } + #[test] + fn sync_freshness_passes_for_intentionally_inactive_identity() { + let c = sync_freshness_verdict_for_lifecycle(Some(3600), 0, false); + assert_eq!(c.status, "PASS"); + assert!(c.detail.contains("inactive")); + } + #[test] fn sync_freshness_passes_when_recent() { let c = sync_freshness_verdict(Some(5), 0);