diff --git a/AGENTS.md b/AGENTS.md index e1c1ee2..724290e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -134,8 +134,6 @@ See `docs/COLIBRI-EXTERNAL-MCP-PROTOTYPE.md` and for Priority 1). - `docs/CLAWDIE-INSTALLER-HANDOFF.md` — FreeBSD agent (Codex): `clawdie` ZFS layout + service install validation. -- **Proof gate tracker:** `cargo run --bin proof-gate-tracker` -- **Platform matrix:** `cargo test --test platform-matrix` - **External MCP smoke:** see `docs/COLIBRI-EXTERNAL-MCP-PROTOTYPE.md` ## Linux Agent Constraints diff --git a/Cargo.toml b/Cargo.toml index 1b10102..c2e6e10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,10 +16,6 @@ path = "src/main.rs" name = "colibri-runtime-inventory" path = "src/bin/runtime_inventory.rs" -[[bin]] -name = "proof-gate-tracker" -path = "tools/proof-gate-tracker.rs" - [dependencies] # Probe logic lives in colibri-deepseek (which pulls reqwest/rustls, chrono…). colibri-deepseek = { path = "crates/colibri-deepseek" } diff --git a/docs/HANDOFF-RC-D-RELEASE-GATE-AUDIT.md b/docs/HANDOFF-RC-D-RELEASE-GATE-AUDIT.md new file mode 100644 index 0000000..c9bb87a --- /dev/null +++ b/docs/HANDOFF-RC-D-RELEASE-GATE-AUDIT.md @@ -0,0 +1,186 @@ +# Handoff — rc.d / Release Gate Audit + +**Created:** 2026-06-15 (Sam & Hermes) +**Status:** open — items below are independently pickable +**Repos involved:** `colibri` (main `2addce9`), `clawdie-iso` (main `1569a04`) + +--- + +## Context + +After landing PR #75 (6 rc.d bug fixes) and pulling the clawdie-iso +`feat/release-gate-whole-stack` PR #59, a cross-repo audit found four remaining +issues. Two are in colibri, two are in clawdie-iso. The most urgent is a clippy +breakage on colibri main that would block a release-channel build. + +PR #59 delivered: + +- **Release gate** — `check_release_gate()` in `build.sh` asserts all 4 repos + (iso/colibri/zot/clawdie-ai) have clean working trees for + `BUILD_CHANNEL=release`. Uses `git status --porcelain` (catches untracked + files, not just modified). +- **Product version 0.10.0** — ISO_VERSION is standalone; build-manifest.json + records `colibri_commit`/`colibri_modified`. +- **ISO repo staging script ahead of colibri repo** — the clawdie-iso copy of + `scripts/stage-colibri-iso.sh` has rc.d validation checks, `colibri-mcp` + staging, `provider.env.sample`, and already uses `colibri_daemon_cost_mode` + and `$(/bin/hostname)`. + +The CHANGELOG (0.10.0, line 34) explicitly references colibri PR #75: +> Colibri daemon now handles SIGTERM (graceful socket cleanup + agent reaping +> on `service stop`), refuses to steal a live socket, and fails closed if it +> cannot bind a control socket (colibri PR #75). + +--- + +## Work items + +### Item 1 (CRITICAL): Fix clippy breakage on colibri main + +**Repo:** colibri +**Owner:** any Rust agent (Linux-doable) +**Blocks:** release-channel ISO builds (release gate needs a clean repo; a +clippy failure prevents validation confidence) + +The live-host commits `b32c3ac` and `4517e13` (merged via PR #75) changed +`socket::serve()` to return `io::Result<()>` instead of `()`. Four call sites +in the client integration test were not updated: + +``` +crates/colibri-client/tests/live_socket_check.rs:102 +crates/colibri-client/tests/live_socket_check.rs:153 +crates/colibri-client/tests/live_socket_check.rs:216 +crates/colibri-client/tests/live_socket_check.rs:319 +``` + +Each currently reads: + +```rust +socket::serve(server_state, shutdown).await; +``` + +Clippy with `-D warnings` treats the unused `Result` as an error. Fix each call +site: + +```rust +let _ = socket::serve(server_state, shutdown).await; +``` + +Or, better, assert the result is Ok in tests that expect the server to start +successfully. + +**Verify:** + +```sh +cargo clippy --workspace --all-targets -- -D warnings +cargo test --workspace +``` + +--- + +### Item 2 (Low): build.sh writes bare `$(hostname)` to rc.conf + +**Repo:** clawdie-iso +**Owner:** any agent +**Location:** `build.sh:893` + +```sh +set_config_line "${MOUNT_POINT}/etc/rc.conf" 'colibri_daemon_host="$(hostname)"' +``` + +The ISO staging script's `rc.conf.sample` already uses `$(/bin/hostname)`, and +the colibri rc.d script uses `$(/bin/hostname)` as its default. But `build.sh` +writes directly to `/etc/rc.conf` with the bare `$(hostname)`. During early +boot, if PATH is not yet populated, the bare command could fail. + +**Fix:** + +```sh +set_config_line "${MOUNT_POINT}/etc/rc.conf" 'colibri_daemon_host="$(/bin/hostname)"' +``` + +--- + +### Item 3 (Low): build.sh sets `chmod 0755` on colibri service dirs + +**Repo:** clawdie-iso +**Owner:** any agent +**Location:** `build.sh:874-877` + +```sh +chmod 0755 \ + "${MOUNT_POINT}/var/db/colibri" \ + "${MOUNT_POINT}/var/run/colibri" \ + "${MOUNT_POINT}/var/log/colibri" +``` + +The rc.d script's prestart creates these directories as `0750` (owner + group +only). The build ships them as `0755` (world-readable/traversable). On first +boot, prestart's `install -d -m 0750` corrects this, but the initial image +ships with wider permissions than intended. + +**Fix:** Change `0755` to `0750`. + +--- + +### Item 4 (Low): Colibri repo staging script is stale + +**Repo:** colibri +**Owner:** any agent (coordinate with Sam) + +The colibri repo's `scripts/stage-colibri-iso.sh` is behind the clawdie-iso +repo's copy. The ISO repo version has: + +- `colibri-mcp` staging (line 64) +- rc.d validation checks (lines 73-88) — catches stale colibri checkouts +- `provider.env.sample` (lines 106-117) +- `colibri_daemon_provider_env` in rc.conf.sample (line 101) +- `$(/bin/hostname)` (line 102) + +The colibri repo version lacks all of these. The ISO build uses its own copy, so +this doesn't block builds, but the split-brain is a maintenance trap: an agent +reading the colibri repo version would not know about the validation checks or +`colibri-mcp`. + +**Options:** + +1. Sync the colibri repo version to match the ISO repo version (recommended). +2. Delete it from the colibri repo and reference the ISO repo as canonical. +3. Leave as-is and document that the ISO repo version is authoritative. + +Option 1 is lowest-risk. The ISO repo version should be considered canonical +since it's the one used in production builds. + +--- + +## What we already fixed (PR #75, merged) + +For reference, these were fixed in the colibri repo and are now on main: + +1. `colibri_cost_mode` → `colibri_daemon_cost_mode` (rc.subr naming convention) +2. Removed redundant socket `chmod 660` in rc.d poststart (Rust sets 0770) +3. Removed unnecessary pidfile `chmod 644` in rc.d poststart +4. Fixed ISO-SERVICE-LAYOUT.md stale permissions + wrong pidfile labels +5. health_cmd now checks daemon response, not just connectivity +6. rc.conf.sample uses `$(/bin/hostname)` consistently + +The live host also contributed (via PR #75 branch): +- SIGTERM handling in `main.rs` (graceful shutdown on `service stop`) +- Liveness-aware socket cleanup in `socket.rs` (`clear_stale_socket`) +- rc.d prestart no longer `rm -f` the socket (daemon handles it safely) + +--- + +## Not bugs (noted for context) + +- **clawdie installer's generated rc.d** (`crates/clawdie/src/platform.rs:102`) + uses `daemon(8) -f -u` without pidfile/procname — `service status` won't work + reliably for clawdie-installed hosts. This is a separate code path from the ISO + rc.d and is the planned deployed-system service, not the live USB service. +- **Release gate test** (`scripts/test-release-gate.sh`) extracts + `assert_clean_repo` from build.sh using `sed`. This is fragile but works + today; if the function formatting changes significantly, the test breaks. +- **ISO rc.d validation** (clawdie-iso staging script lines 73-88) is excellent + and should be kept updated as the rc.d script evolves. It currently checks for + `colibri_daemon_cost_mode`, `colibri_daemon_provider_env`, no + `-u ${colibri_daemon_user}`, and no socket rm in prestart. diff --git a/docs/README.md b/docs/README.md index eb7599e..6a1cd81 100644 --- a/docs/README.md +++ b/docs/README.md @@ -12,6 +12,7 @@ A quick-reference guide to every document in this folder. | [`COLIBRI-TOKENOMICS-TRIFECTA.md`](COLIBRI-TOKENOMICS-TRIFECTA.md) | Strategic vision: useful tokens, cost-per-intelligence, measurement | All | | [`HEADROOM-SIDECAR.md`](HEADROOM-SIDECAR.md) | Optional `headroom-ai` tool-result compression sidecar | Agents | | [`INTEGRATION-LAYERED-SOUL.md`](INTEGRATION-LAYERED-SOUL.md) | How Colibri consumes `layered-soul` reviewed context today vs planned | Agents | +| [`HANDOFF-RC-D-RELEASE-GATE-AUDIT.md`](HANDOFF-RC-D-RELEASE-GATE-AUDIT.md) | Post-PR-#75 audit: clippy fix, build.sh hostname/chmod, staging script sync | All agents | | [`ISO-ACCEPTANCE-RUNBOOK.md`](ISO-ACCEPTANCE-RUNBOOK.md) | Post-boot acceptance commands after staging Colibri into an ISO | Codex (FreeBSD) | | [`ISO-SERVICE-LAYOUT.md`](ISO-SERVICE-LAYOUT.md) | `rc.conf` service layout for the ISO image | All | | [`PRIORITY-HANDOFF-ISO-SPAWN-COST.md`](PRIORITY-HANDOFF-ISO-SPAWN-COST.md) | **Current sprint**: ISO staging wiring, Pi spawn path, cost mode enforcement | All agents | diff --git a/tools/README.md b/tools/README.md deleted file mode 100644 index 7e0a418..0000000 --- a/tools/README.md +++ /dev/null @@ -1,93 +0,0 @@ -# Colibri Tools - -This directory contains utility tools for the Colibri multiagent development workflow. - -## proof-gate-tracker - -Automated proof gate validation tool that checks the status of all 6 migration proof gates. - -### Usage - -```bash -# Build and run -cargo run --release --bin proof-gate-tracker - -# Or build and run directly -cargo build --release --bin proof-gate-tracker -./target/release/proof-gate-tracker -``` - -### Proof Gates Checked - -1. **Gate #1 - Contracts**: Validates golden test fixtures exist and are valid JSON -2. **Gate #2 - Cache Manifest**: Verifies DeepSeek cache hit manifests exist for osa + domedog -3. **Gate #3 - Runtime Inventory**: Ensures runtime inventory parity across all platforms -4. **Gate #4 - Cross-Platform**: Runs `cargo check --workspace` to ensure build passes -5. **Gate #5 - Watchdog**: Validates osa watchdog socket read successful -6. **Gate #6 - Caller Inventory**: Checks caller inventory documentation exists (precondition check) - -### Exit Codes - -- `0`: All critical gates passing -- `1`: Some critical gates failing - -### Integration with CI/CD - -```yaml -# Example GitHub Actions -- name: Validate Proof Gates - run: cargo run --release --bin proof-gate-tracker -``` - -## Platform Matrix Tests - -Cross-platform startup checks are located in `tests/platform-matrix.rs`. - -### Usage - -```bash -# Run all platform matrix tests -cargo test --test platform-matrix - -# Run with output -cargo test --test platform-matrix -- --nocapture - -# Run specific test -cargo test --test platform-matrix all_platforms_validate_core_features -- --nocapture -``` - -### Tests Included - -- `all_platforms_validate_core_features`: Validates all platforms (FreeBSD/Linux) have valid manifests -- `freebsd_specific_tests`: FreeBSD-specific validations (osa) -- `linux_specific_tests`: Linux-specific validations (domedog, debby) -- `cache_economics_parity`: Verifies cache hit rate consistency across platforms - -## Multiagent Workflow Tools - -### Agent Handoff Protocol - -See `doc/-HANDOFF.md` for the standardized handoff protocol used between agents. - -### Handoff Template - -```json -{ - "agent_from": "agent_name", - "agent_to": "agent_name", - "focus_area": "brief description", - "proof_gates_pending": ["gate-1", "gate-2"], - "known_limitations": ["limitation 1"], - "next_steps": ["step 1", "step 2"], - "context_files": ["file1.rs", "file2.md"], - "test_evidence": ["manifests/file1.json"] -} -``` - -### Handoff Checklist - -- [ ] All tests pass (`cargo test --workspace`) -- [ ] Relevant proof gates documented -- [ ] Cross-platform validation recorded -- [ ] Next agent's entry point marked -- [ ] Handoff entry added to `doc/-HANDOFF.md` diff --git a/tools/proof-gate-tracker.rs b/tools/proof-gate-tracker.rs deleted file mode 100644 index 1ed242e..0000000 --- a/tools/proof-gate-tracker.rs +++ /dev/null @@ -1,324 +0,0 @@ -use std::fs; -use std::path::Path; -use std::process::Command; - -struct ProofGate { - name: String, - critical: bool, - check: Box GateStatus>, -} - -#[derive(Debug)] -enum GateStatus { - Pass { evidence: String }, - Fail { reason: String }, - Skipped { reason: String }, -} - -fn check_gate_1_contracts() -> GateStatus { - // Check if golden test fixtures exist and are valid JSON - let manifest_dir = Path::new("manifests"); - if !manifest_dir.exists() { - return GateStatus::Fail { - reason: "manifests/ directory does not exist".to_string(), - }; - } - - let golden_files = [ - "2026-05-26-domedog-deepseek-cache-result.json", - "2026-05-26-osa-deepseek-cache-result.json", - "2026-05-26-domedog-runtime-inventory.json", - "2026-05-26-osa-runtime-inventory.json", - "2026-05-26-debby-runtime-inventory.json", - "2026-05-26-osa-watchdog-host-status.json", - ]; - - let mut valid_count = 0; - for file in &golden_files { - let path = manifest_dir.join(file); - if path.exists() { - match fs::read_to_string(&path) { - Ok(content) => { - if serde_json::from_str::(&content).is_ok() { - valid_count += 1; - } - } - Err(_) => continue, - } - } - } - - if valid_count >= 5 { - GateStatus::Pass { - evidence: format!( - "{}/{} golden fixtures valid", - valid_count, - golden_files.len() - ), - } - } else { - GateStatus::Fail { - reason: format!( - "Only {}/{} golden fixtures valid", - valid_count, - golden_files.len() - ), - } - } -} - -fn check_gate_2_cache_manifest() -> GateStatus { - let manifest_dir = Path::new("manifests"); - let osa_manifest = manifest_dir.join("2026-05-26-osa-deepseek-cache-result.json"); - let domedog_manifest = manifest_dir.join("2026-05-26-domedog-deepseek-cache-result.json"); - - let mut evidence = Vec::new(); - - if osa_manifest.exists() { - if let Ok(content) = fs::read_to_string(&osa_manifest) { - if let Ok(json) = serde_json::from_str::(&content) { - let cache_hit = json["cache_hit_observed"].as_bool().unwrap_or(false); - let hit_tokens = json["cache_hit_tokens"].as_u64().unwrap_or(0); - if cache_hit && hit_tokens > 0 { - evidence.push(format!("osa: {} cache hit tokens", hit_tokens)); - } - } - } - } - - if domedog_manifest.exists() { - if let Ok(content) = fs::read_to_string(&domedog_manifest) { - if let Ok(json) = serde_json::from_str::(&content) { - let cache_hit = json["cache_hit_observed"].as_bool().unwrap_or(false); - let hit_tokens = json["cache_hit_tokens"].as_u64().unwrap_or(0); - if cache_hit && hit_tokens > 0 { - evidence.push(format!("domedog: {} cache hit tokens", hit_tokens)); - } - } - } - } - - if evidence.len() >= 2 { - GateStatus::Pass { - evidence: evidence.join("; "), - } - } else if evidence.len() == 1 { - GateStatus::Pass { - evidence: format!("Partial: {}", evidence[0]), - } - } else { - GateStatus::Fail { - reason: "No cache hit manifests found".to_string(), - } - } -} - -fn check_gate_3_runtime_inventory() -> GateStatus { - let manifest_dir = Path::new("manifests"); - let platforms = [("osa", "FreeBSD"), ("domedog", "Linux"), ("debby", "Linux")]; - let mut evidence = Vec::new(); - - for (host, expected_os) in &platforms { - let manifest_path = - manifest_dir.join(format!("2026-05-26-{}-runtime-inventory.json", host)); - if manifest_path.exists() { - if let Ok(content) = fs::read_to_string(&manifest_path) { - if let Ok(json) = serde_json::from_str::(&content) { - let os = json["os"].as_str().unwrap_or(""); - let pi = json["pi"].as_str().unwrap_or("none"); - if os.contains(expected_os) { - evidence.push(format!("{}: {} (pi: {})", host, expected_os, pi)); - } - } - } - } - } - - if evidence.len() >= 2 { - GateStatus::Pass { - evidence: evidence.join("; "), - } - } else { - GateStatus::Fail { - reason: format!( - "Only {}/{} platforms have valid runtime inventories", - evidence.len(), - platforms.len() - ), - } - } -} - -fn check_gate_4_cross_platform() -> GateStatus { - // Check if build passes (Linux) - let result = Command::new("cargo") - .args(["check", "--workspace"]) - .output(); - - match result { - Ok(output) if output.status.success() => GateStatus::Pass { - evidence: "cargo check --workspace passed".to_string(), - }, - Ok(output) => { - let stderr = String::from_utf8_lossy(&output.stderr); - GateStatus::Fail { - reason: format!( - "Build failed: {}", - stderr.lines().take(3).collect::>().join(" ") - ), - } - } - Err(e) => GateStatus::Fail { - reason: format!("Failed to run cargo check: {}", e), - }, - } -} - -fn check_gate_5_watchdog() -> GateStatus { - // Check if watchdog host status manifest exists - let manifest_path = Path::new("manifests/2026-05-26-osa-watchdog-host-status.json"); - if manifest_path.exists() { - match fs::read_to_string(manifest_path) { - Ok(content) => { - if let Ok(json) = serde_json::from_str::(&content) { - if json["source"].as_str() == Some("watchdog-socket") { - let mode = json["mode"].as_str().unwrap_or("unknown"); - GateStatus::Pass { - evidence: format!( - "osa watchdog socket read successful (mode: {})", - mode - ), - } - } else { - GateStatus::Fail { - reason: "Manifest exists but source is not watchdog-socket".to_string(), - } - } - } else { - GateStatus::Fail { - reason: "Manifest is not valid JSON".to_string(), - } - } - } - Err(e) => GateStatus::Fail { - reason: format!("Failed to read manifest: {}", e), - }, - } - } else { - GateStatus::Fail { - reason: "osa watchdog host status manifest not found".to_string(), - } - } -} - -fn check_gate_6_caller_inventory() -> GateStatus { - // Gate #6 is gated on precondition: CONTROLPLANE_RUNNER=pi in production - // Check if caller inventory exists - let inventory_path = Path::new("docs/CALLER-INVENTORY.md"); - if inventory_path.exists() { - match fs::read_to_string(inventory_path) { - Ok(content) => { - if content.contains("agent-runner.ts") && content.contains("KEEP") { - GateStatus::Skipped { - reason: "Caller inventory documented, awaiting production verification of CONTROLPLANE_RUNNER=pi".to_string() - } - } else { - GateStatus::Fail { - reason: "Caller inventory exists but does not document agent-runner.ts" - .to_string(), - } - } - } - Err(e) => GateStatus::Fail { - reason: format!("Failed to read caller inventory: {}", e), - }, - } - } else { - GateStatus::Fail { - reason: "docs/CALLER-INVENTORY.md not found".to_string(), - } - } -} - -fn main() { - println!("🔍 Colibri Proof Gate Tracker"); - println!("═════════════════════════════\n"); - - let gates: Vec = vec![ - ProofGate { - name: "gate-1-contracts".to_string(), - critical: true, - check: Box::new(check_gate_1_contracts), - }, - ProofGate { - name: "gate-2-cache-manifest".to_string(), - critical: true, - check: Box::new(check_gate_2_cache_manifest), - }, - ProofGate { - name: "gate-3-runtime-inventory".to_string(), - critical: true, - check: Box::new(check_gate_3_runtime_inventory), - }, - ProofGate { - name: "gate-4-cross-platform".to_string(), - critical: true, - check: Box::new(check_gate_4_cross_platform), - }, - ProofGate { - name: "gate-5-watchdog".to_string(), - critical: true, - check: Box::new(check_gate_5_watchdog), - }, - ProofGate { - name: "gate-6-caller-inventory".to_string(), - critical: false, // Precondition check - check: Box::new(check_gate_6_caller_inventory), - }, - ]; - - let mut critical_passed = 0; - let mut critical_total = 0; - let mut all_passed = 0; - - for gate in &gates { - if gate.critical { - critical_total += 1; - } - - let status = (gate.check)(); - let critical_marker = if gate.critical { " 🔒" } else { " ⏸️" }; - - match status { - GateStatus::Pass { evidence } => { - println!("✅ {}{}: {}", gate.name, critical_marker, evidence); - all_passed += 1; - if gate.critical { - critical_passed += 1; - } - } - GateStatus::Fail { reason } => { - println!("❌ {}{}: {}", gate.name, critical_marker, reason); - } - GateStatus::Skipped { reason } => { - println!("⏭️ {}{}: {}", gate.name, critical_marker, reason); - all_passed += 1; - } - } - } - - println!("\n═════════════════════════════"); - println!("Summary: {}/{} gates passing", all_passed, gates.len()); - println!( - "Critical: {}/{} gates passing", - critical_passed, critical_total - ); - - if critical_passed < critical_total { - println!("\n⚠️ Some critical gates are failing!"); - std::process::exit(1); - } else { - println!("\n✅ All critical gates are passing!"); - std::process::exit(0); - } -}