"""CLI + module tests for forward-deployed engagements (KAIZEN-WP-0009 T08).""" from __future__ import annotations import json from pathlib import Path import pytest import yaml from click.testing import CliRunner from kaizen_agentic.cli import cli from kaizen_agentic.engagement import ( EngagementError, build_prepare_bundle, checklist_summary, close_session, compute_duty_kai, load_checklist, load_engagement, parse_checklist_markdown, set_checklist_item_status, set_phase, staff_engagement, text_has_sensitive_content, validate_engagement, ) @pytest.fixture def runner() -> CliRunner: return CliRunner() @pytest.fixture def mini_repo(tmp_path: Path) -> Path: """Minimal repo with host-operator Role package.""" root = tmp_path / "kaizen-root" role = root / "roles" / "host-operator" role.mkdir(parents=True) (role / "ROLE.yaml").write_text( yaml.safe_dump( { "apiVersion": "kaizen.agentic/v1", "kind": "Role", "metadata": {"id": "host-operator", "version": "0.1.0"}, "spec": { "protocols": [ { "agent": "host-operator", "slug": "load-workload-review", "path": "roles/host-operator/protocols/load-workload-review.md", } ] }, } ), encoding="utf-8", ) (role / "agent-definition.md").write_text( "---\nname: host-operator\ncategory: infrastructure\nmemory: enabled\n---\n\n# Host Operator\n\nDo ops.\n", encoding="utf-8", ) (role / "memory-template.md").write_text( "---\nagent: host-operator\nengagement_id: \n---\n\n# Memory\n", encoding="utf-8", ) (role / "ramp-up.md").write_text( "# Ramp-up\n\n| ID | Criterion | Status | Evidence |\n" "|----|-----------|--------|----------|\n" "| RU-01 | Access path verified | todo | access-plan.md |\n" "| RU-02 | Host baseline documented | todo | vault/baselines |\n", encoding="utf-8", ) (role / "ramp-down.md").write_text( "# Ramp-down\n\n| ID | Criterion | Status | Evidence |\n" "|----|-----------|--------|----------|\n" "| RD-01 | Open threads triaged | todo | memory |\n", encoding="utf-8", ) proto = role / "protocols" proto.mkdir() (proto / "load-workload-review.md").write_text( "---\nslug: load-workload-review\n---\n\n# Load review\n", encoding="utf-8", ) (root / "engagements" / "pilots").mkdir(parents=True) return root class TestChecklistParse: def test_parse_checklist_rows(self): text = ( "| ID | Criterion | Status | Evidence |\n" "|----|-----------|--------|----------|\n" "| RU-01 | Access | done | access-plan.md |\n" "| RU-02 | Baseline | todo | vault |\n" ) items = parse_checklist_markdown(text) assert len(items) == 2 assert items[0].done is True assert items[1].done is False summary = checklist_summary(items) assert summary == { "done": 1, "total": 2, "complete": False, "pending": ["RU-02"], } def test_set_checklist_item_status(self, tmp_path: Path): p = tmp_path / "c.md" p.write_text( "| ID | Criterion | Status | Evidence |\n" "|----|-----------|--------|----------|\n" "| RU-01 | Access | todo | a |\n", encoding="utf-8", ) assert set_checklist_item_status(p, "RU-01", "done") assert "RU-01" in p.read_text() assert "done" in p.read_text() items = parse_checklist_markdown(p.read_text()) assert items[0].done class TestStaffAndLifecycle: def test_staff_validate_phase_prepare(self, mini_repo: Path): eng = staff_engagement( engagement_id="eng-test-host-001", role_id="host-operator", client_id="coulomb", target_id="railiance01", repo_root=mini_repo, ) assert eng.phase == "staffing" assert eng.engagement_id == "eng-test-host-001" assert (eng.path / "vault" / "memory.md").exists() assert (eng.path / "agent-host-operator.md").exists() errs = validate_engagement(eng) assert errs == [] path, items = load_checklist(eng, "ramp_up") assert path is not None assert any(i.item_id == "RU-01" for i in items) eng = set_phase(eng, "ramp_up") assert eng.phase == "ramp_up" ad = (eng.path / "agent-host-operator.md").read_text(encoding="utf-8") assert "phase: ramp_up" in ad or "phase: ramp_up" in ad.replace('"', "") bundle = build_prepare_bundle(eng, mini_repo) assert bundle["engagement_id"] == "eng-test-host-001" assert bundle["agent_prompt_found"] is True assert bundle["phase"] == "ramp_up" assert "Access" in (bundle.get("phase_instructions") or "") with pytest.raises(EngagementError): set_phase(eng, "closed") # illegal without force eng = set_phase(eng, "closed", force=True) assert eng.phase == "closed" class TestCloseSession: def test_compute_duty_kai(self): assert ( compute_duty_kai("standard_review", tier=4, access_class="host_observe") == 1700 ) assert compute_duty_kai("ramp_up_package", tier=4) == 10000 assert compute_duty_kai("short_assist", tier=1, amount_override=50) == 50 def test_sensitive_summary_rejected(self, mini_repo: Path): eng = staff_engagement( engagement_id="eng-close-sens", role_id="host-operator", client_id="c", target_id="h1", repo_root=mini_repo, ) with pytest.raises(EngagementError, match="sensitive"): close_session( eng, success=True, summary="password: hunter2 leaked", record_metrics=False, record_ledger=False, write_report=False, ) assert text_has_sensitive_content("-----BEGIN RSA PRIVATE KEY-----") def test_close_session_writes_all_artifacts(self, mini_repo: Path): eng = staff_engagement( engagement_id="eng-close-001", role_id="host-operator", client_id="coulomb", target_id="railiance01", repo_root=mini_repo, ) result = close_session( eng, success=True, duty="standard_review", summary="first health pass watch-level disk", execution_time_s=90.0, quality=0.85, access_class="host_observe", ) assert result["amount_kai"] == 1700 assert result["metrics_recorded"] is True mem = Path(result["memory_path"]).read_text(encoding="utf-8") assert "first health pass" in mem assert "session_count: 1" in mem or "session_count: 1" in mem.replace('"', "") report = Path(result["report_path"]) assert report.exists() assert "first health pass" in report.read_text(encoding="utf-8") ledger = ( Path(result["ledger_path"]).read_text(encoding="utf-8").strip().splitlines() ) last = json.loads(ledger[-1]) assert last["amount_kai"] == 1700 assert last["product"] == "standard_review" assert "password" not in last metrics_path = Path(result["metrics_path"]) assert metrics_path.exists() assert "engagement_id" in metrics_path.read_text(encoding="utf-8") class TestEngagementCli: def test_staff_list_checklist_prepare(self, runner: CliRunner, mini_repo: Path): result = runner.invoke( cli, [ "engagement", "staff", "--id", "eng-cli-001", "--role", "host-operator", "--client", "coulomb", "--target", "railiance01", "--repo-root", str(mini_repo), ], ) assert result.exit_code == 0, result.output assert "Staffed engagement" in result.output result = runner.invoke( cli, ["engagement", "list", "--repo-root", str(mini_repo), "--json"] ) assert result.exit_code == 0, result.output rows = json.loads(result.output) assert any(r["id"] == "eng-cli-001" for r in rows) result = runner.invoke( cli, [ "engagement", "validate", "eng-cli-001", "--repo-root", str(mini_repo), ], ) assert result.exit_code == 0, result.output result = runner.invoke( cli, [ "engagement", "checklist", "eng-cli-001", "--repo-root", str(mini_repo), "--mark", "RU-01=done", ], ) assert result.exit_code == 0, result.output assert "RU-01" in result.output result = runner.invoke( cli, [ "engagement", "checklist", "eng-cli-001", "--repo-root", str(mini_repo), "--json", ], ) assert result.exit_code == 0, result.output data = json.loads(result.output) ru = data["checklists"]["ramp_up"]["items"] assert any(i["id"] == "RU-01" and i["done"] for i in ru) result = runner.invoke( cli, [ "engagement", "phase", "eng-cli-001", "--to", "ramp_up", "--repo-root", str(mini_repo), ], ) assert result.exit_code == 0, result.output assert "ramp_up" in result.output result = runner.invoke( cli, [ "engagement", "prepare", "eng-cli-001", "--repo-root", str(mini_repo), "--format", "json", ], ) assert result.exit_code == 0, result.output bundle = json.loads(result.output) assert bundle["phase"] == "ramp_up" assert bundle["agent_prompt_found"] is True def test_phase_rejects_illegal_transition(self, runner: CliRunner, mini_repo: Path): runner.invoke( cli, [ "engagement", "staff", "--id", "eng-cli-002", "--role", "host-operator", "--client", "c", "--target", "h1", "--repo-root", str(mini_repo), ], ) result = runner.invoke( cli, [ "engagement", "phase", "eng-cli-002", "--to", "closed", "--repo-root", str(mini_repo), ], ) assert result.exit_code != 0 assert "Illegal phase" in result.output def test_real_pilot_if_present(self, runner: CliRunner): """Smoke against repo pilot when checked out as kaizen-agentic.""" repo = Path.cwd() pilot = ( repo / "engagements" / "pilots" / "eng-coulomb-railiance01-ho-001" / "ENGAGEMENT.yaml" ) if not pilot.exists(): pytest.skip("pilot engagement not in cwd") result = runner.invoke( cli, [ "engagement", "show", "eng-coulomb-railiance01-ho-001", "--repo-root", str(repo), "--json", ], ) assert result.exit_code == 0, result.output data = json.loads(result.output) assert data["id"] == "eng-coulomb-railiance01-ho-001" assert data["role"] == "host-operator" result = runner.invoke( cli, [ "engagement", "checklist", "eng-coulomb-railiance01-ho-001", "--repo-root", str(repo), "--which", "ramp_up", ], ) assert result.exit_code == 0, result.output assert "RU-01" in result.output result = runner.invoke( cli, [ "engagement", "validate", "eng-coulomb-railiance01-ho-001", "--repo-root", str(repo), ], ) assert result.exit_code == 0, result.output result = runner.invoke( cli, [ "engagement", "quote", "eng-coulomb-railiance01-ho-001", "--repo-root", str(repo), ], ) assert result.exit_code == 0, result.output assert "72800" in result.output or "Kai" in result.output def test_close_session_cli(self, runner: CliRunner, mini_repo: Path): runner.invoke( cli, [ "engagement", "staff", "--id", "eng-close-cli", "--role", "host-operator", "--client", "coulomb", "--target", "railiance01", "--repo-root", str(mini_repo), ], ) result = runner.invoke( cli, [ "engagement", "close-session", "eng-close-cli", "--success", "--duty", "standard_review", "--summary", "load review healthy", "--time", "60", "--quality", "0.9", "--repo-root", str(mini_repo), "--json", ], ) assert result.exit_code == 0, result.output data = json.loads(result.output) assert data["amount_kai"] == 1700 assert data["metrics_recorded"] is True