"""Forward-deployed engagement lifecycle helpers (KAIZEN-WP-0009 / DEC-FDA-001). File-based Phase 1: ENGAGEMENT.yaml under engagements/pilots// is the source of truth. This module loads, validates, prepares session bundles, and transitions phases — it does not invoke LLMs or touch production hosts. """ from __future__ import annotations import re import shutil from dataclasses import dataclass from datetime import date from pathlib import Path from typing import Any, Dict, List, Optional, Pattern, Sequence, Tuple import yaml ENGAGEMENT_FILENAME = "ENGAGEMENT.yaml" DEFAULT_PILOTS_DIR = Path("engagements") / "pilots" DEFAULT_ROLES_DIR = Path("roles") VALID_PHASES = ( "requested", "quoting", "funded", "staffing", "ramp_up", "operating", "renewing", "ramp_down", "closed", "cancelled", ) # Allowed forward transitions (plus renewing ↔ operating, and cancelled from most) PHASE_TRANSITIONS: Dict[str, Tuple[str, ...]] = { "requested": ("quoting", "cancelled"), "quoting": ("funded", "requested", "cancelled"), "funded": ("staffing", "cancelled"), "staffing": ("ramp_up", "cancelled"), "ramp_up": ("operating", "cancelled"), "operating": ("renewing", "ramp_down", "cancelled"), "renewing": ("operating", "ramp_down", "cancelled"), "ramp_down": ("closed", "cancelled"), "closed": (), "cancelled": (), } # Sensitive patterns for scrub (heuristic; not a security scanner) _SCRUB_PATTERNS: List[Tuple[str, Pattern]] = [ ("private_key_block", re.compile(r"-----BEGIN [A-Z ]*PRIVATE KEY-----")), ("aws_access_key", re.compile(r"AKIA[0-9A-Z]{16}")), ("bearer_token", re.compile(r"(?i)bearer\s+[a-z0-9\-._~+/]+=*")), ( "ipv4_privateish", re.compile(r"\b(?:10|192\.168|172\.(?:1[6-9]|2\d|3[01]))\.\d{1,3}\.\d{1,3}\b"), ), ("password_assignment", re.compile(r"(?i)(password|passwd|secret)\s*[:=]\s*\S+")), ] class EngagementError(Exception): """Raised when an engagement cannot be loaded or operated on.""" @dataclass class ChecklistItem: item_id: str criterion: str status: str evidence: str @property def done(self) -> bool: return self.status.strip().lower() in ("done", "complete", "yes", "ok") @dataclass class Engagement: """Parsed engagement record plus filesystem paths.""" path: Path data: Dict[str, Any] source_path: Path @property def engagement_id(self) -> str: meta = self.data.get("metadata") or {} return str(meta.get("id") or self.path.name) @property def phase(self) -> str: status = self.data.get("status") or {} return str(status.get("phase") or "requested") @property def role_id(self) -> Optional[str]: spec = self.data.get("spec") or {} role = spec.get("role") or {} rid = role.get("id") return str(rid) if rid else None @property def targets(self) -> List[Dict[str, Any]]: spec = self.data.get("spec") or {} targets = spec.get("targets") or [] return list(targets) if isinstance(targets, list) else [] def rel(self, *parts: str) -> Path: return self.path.joinpath(*parts) def agent_definition_path(self) -> Optional[Path]: spec = self.data.get("spec") or {} ad = spec.get("agent_definition") or {} rel = ad.get("path") if not rel: return None p = self.path / rel return p if p.exists() else p def vault_memory_path(self) -> Path: spec = self.data.get("spec") or {} vault = spec.get("vault") or {} rel = vault.get("memory") or "vault/memory.md" return self.path / rel def checklist_path(self, which: str) -> Optional[Path]: spec = self.data.get("spec") or {} checks = spec.get("checklists") or {} key = "ramp_up" if which in ("ramp_up", "ramp-up", "up") else "ramp_down" if which in ("ramp_down", "ramp-down", "down"): key = "ramp_down" rel = checks.get(key) if not rel: default = ( "checklists/ramp-up-status.md" if key == "ramp_up" else "checklists/ramp-down-status.md" ) rel = default p = self.path / rel return p if p.exists() else None def quote_path(self) -> Optional[Path]: spec = self.data.get("spec") or {} commercial = spec.get("commercial") or {} rel = commercial.get("quote_ref") or "commercial/quote.yaml" p = self.path / rel return p if p.exists() else None def ledger_path(self) -> Path: spec = self.data.get("spec") or {} commercial = spec.get("commercial") or {} rel = commercial.get("ledger_ref") or "commercial/ledger.jsonl" return self.path / rel def today_iso() -> str: return date.today().isoformat() def find_repo_root(start: Optional[Path] = None) -> Path: """Walk parents for engagements/ or roles/ or .git.""" cur = (start or Path.cwd()).resolve() for candidate in [cur, *cur.parents]: if (candidate / "engagements").is_dir() or (candidate / "roles").is_dir(): return candidate if (candidate / ".git").exists() and ( (candidate / "pyproject.toml").exists() or (candidate / "workplans").is_dir() ): return candidate return cur def list_engagement_dirs(repo_root: Path) -> List[Path]: """Discover engagement directories (pilots first, then other trees).""" found: List[Path] = [] pilots = repo_root / DEFAULT_PILOTS_DIR if pilots.is_dir(): for child in sorted(pilots.iterdir()): if child.is_dir() and (child / ENGAGEMENT_FILENAME).exists(): found.append(child) engagements = repo_root / "engagements" if engagements.is_dir(): for child in sorted(engagements.iterdir()): if child.name == "pilots": continue if child.is_dir() and (child / ENGAGEMENT_FILENAME).exists(): found.append(child) return found def resolve_engagement_dir( engagement_ref: str, repo_root: Optional[Path] = None ) -> Path: """Resolve an engagement id or path to its directory.""" root = (repo_root or find_repo_root()).resolve() ref = Path(engagement_ref) if ref.is_dir() and (ref / ENGAGEMENT_FILENAME).exists(): return ref.resolve() if ref.is_file() and ref.name == ENGAGEMENT_FILENAME: return ref.parent.resolve() # id under pilots/ pilot = root / DEFAULT_PILOTS_DIR / engagement_ref if (pilot / ENGAGEMENT_FILENAME).exists(): return pilot.resolve() # bare engagements/ bare = root / "engagements" / engagement_ref if (bare / ENGAGEMENT_FILENAME).exists(): return bare.resolve() # search by metadata id for d in list_engagement_dirs(root): try: eng = load_engagement(d) except EngagementError: continue if eng.engagement_id == engagement_ref or d.name == engagement_ref: return d raise EngagementError( f"Engagement not found: {engagement_ref!r} " f"(looked under {root / DEFAULT_PILOTS_DIR})" ) def load_engagement(path: Path) -> Engagement: """Load ENGAGEMENT.yaml from a directory (or path to the file).""" path = Path(path) if path.is_file(): source = path base = path.parent else: source = path / ENGAGEMENT_FILENAME base = path if not source.exists(): raise EngagementError(f"Missing {ENGAGEMENT_FILENAME}: {source}") try: data = yaml.safe_load(source.read_text(encoding="utf-8")) except yaml.YAMLError as exc: raise EngagementError(f"Invalid YAML in {source}: {exc}") from exc if not isinstance(data, dict): raise EngagementError(f"{source} must contain a mapping") if data.get("kind") and data.get("kind") != "Engagement": raise EngagementError( f"{source}: kind must be Engagement (got {data.get('kind')})" ) return Engagement(path=base.resolve(), data=data, source_path=source.resolve()) def save_engagement(eng: Engagement) -> None: """Write ENGAGEMENT.yaml back (preserves structure via dump of data).""" eng.source_path.write_text( yaml.safe_dump(eng.data, sort_keys=False, allow_unicode=True), encoding="utf-8", ) def parse_checklist_markdown(text: str) -> List[ChecklistItem]: """Parse checklist tables with columns ID | Criterion | Status | Evidence.""" items: List[ChecklistItem] = [] for line in text.splitlines(): line = line.strip() if not line.startswith("|"): continue cells = [c.strip() for c in line.strip("|").split("|")] if len(cells) < 4: continue item_id, criterion, status, evidence = cells[0], cells[1], cells[2], cells[3] if item_id.lower() in ("id", "---") or set(item_id) <= {"-", ":"}: continue if not re.match(r"^[A-Z]{1,3}-\d+", item_id): continue items.append( ChecklistItem( item_id=item_id, criterion=criterion, status=status, evidence=evidence, ) ) return items def load_checklist( eng: Engagement, which: str ) -> Tuple[Optional[Path], List[ChecklistItem]]: path = eng.checklist_path(which) if path is None: return None, [] items = parse_checklist_markdown(path.read_text(encoding="utf-8")) return path, items def checklist_summary(items: Sequence[ChecklistItem]) -> Dict[str, Any]: done = sum(1 for i in items if i.done) total = len(items) return { "done": done, "total": total, "complete": total > 0 and done == total, "pending": [i.item_id for i in items if not i.done], } def set_checklist_item_status(checklist_path: Path, item_id: str, status: str) -> bool: """Update a checklist row status by item id. Returns True if updated.""" text = checklist_path.read_text(encoding="utf-8") lines = text.splitlines() changed = False out: List[str] = [] for line in lines: stripped = line.strip() if stripped.startswith("|"): cells = [c.strip() for c in stripped.strip("|").split("|")] if len(cells) >= 4 and cells[0] == item_id: cells[2] = status line = "| " + " | ".join(cells) + " |" changed = True out.append(line) if changed: checklist_path.write_text( "\n".join(out) + ("\n" if text.endswith("\n") else ""), encoding="utf-8" ) return changed def set_phase( eng: Engagement, new_phase: str, *, force: bool = False, notes: Optional[str] = None, ) -> Engagement: """Transition engagement phase; updates YAML on disk.""" new_phase = new_phase.strip() if new_phase not in VALID_PHASES: raise EngagementError( f"Invalid phase {new_phase!r}; expected one of: {', '.join(VALID_PHASES)}" ) current = eng.phase if not force and new_phase != current: allowed = PHASE_TRANSITIONS.get(current, ()) if new_phase not in allowed: raise EngagementError( f"Illegal phase transition {current!r} → {new_phase!r}. " f"Allowed: {', '.join(allowed) or '(terminal)'}. Use --force to override." ) status = eng.data.setdefault("status", {}) if not isinstance(status, dict): eng.data["status"] = {} status = eng.data["status"] status["phase"] = new_phase if notes is not None: status["notes"] = notes meta = eng.data.setdefault("metadata", {}) if isinstance(meta, dict): meta["updated"] = today_iso() save_engagement(eng) # Keep agent definition phase in sync if present _sync_agent_definition_phase(eng, new_phase) return load_engagement(eng.path) def _sync_agent_definition_phase(eng: Engagement, phase: str) -> None: ad_path = eng.agent_definition_path() if ad_path is None or not ad_path.exists(): return text = ad_path.read_text(encoding="utf-8") if not text.startswith("---"): return parts = text.split("---", 2) if len(parts) < 3: return try: fm = yaml.safe_load(parts[1]) or {} except yaml.YAMLError: return if not isinstance(fm, dict): return fm["phase"] = phase new_fm = yaml.safe_dump(fm, sort_keys=False, allow_unicode=True).strip() ad_path.write_text(f"---\n{new_fm}\n---{parts[2]}", encoding="utf-8") def validate_engagement(eng: Engagement) -> List[str]: """Return list of validation errors (empty if ok).""" errors: List[str] = [] if eng.data.get("apiVersion") != "kaizen.agentic/v1": errors.append("apiVersion should be kaizen.agentic/v1") if eng.phase not in VALID_PHASES: errors.append(f"unknown phase: {eng.phase}") if not eng.role_id: errors.append("spec.role.id missing") if not eng.targets: errors.append("spec.targets is empty") ad = eng.agent_definition_path() if ad is None: errors.append("spec.agent_definition.path missing") elif not ad.exists(): errors.append(f"agent definition missing: {ad}") mem = eng.vault_memory_path() if not mem.exists(): errors.append(f"vault memory missing: {mem}") for which in ("ramp_up", "ramp_down"): path, items = load_checklist(eng, which) if path is None: errors.append(f"checklist missing: {which}") elif not items: errors.append(f"checklist empty or unparseable: {path}") access = eng.path / "access-plan.md" if not access.exists(): errors.append("access-plan.md missing") return errors def build_prepare_bundle( eng: Engagement, repo_root: Optional[Path] = None ) -> Dict[str, Any]: """Assemble an offline session orientation bundle for this engagement.""" root = (repo_root or find_repo_root(eng.path)).resolve() ad_path = eng.agent_definition_path() agent_prompt = ( ad_path.read_text(encoding="utf-8") if ad_path and ad_path.exists() else None ) mem_path = eng.vault_memory_path() memory = mem_path.read_text(encoding="utf-8") if mem_path.exists() else None access_path = eng.path / "access-plan.md" access_plan = ( access_path.read_text(encoding="utf-8") if access_path.exists() else None ) phase = eng.phase phase_instructions = None role_dir = root / DEFAULT_ROLES_DIR / (eng.role_id or "") if phase == "ramp_up" and (role_dir / "ramp-up.md").exists(): phase_instructions = (role_dir / "ramp-up.md").read_text(encoding="utf-8") elif phase == "ramp_down" and (role_dir / "ramp-down.md").exists(): phase_instructions = (role_dir / "ramp-down.md").read_text(encoding="utf-8") protocols: Dict[str, Optional[str]] = {} role_yaml = role_dir / "ROLE.yaml" if role_yaml.exists(): try: role_data = yaml.safe_load(role_yaml.read_text(encoding="utf-8")) or {} except yaml.YAMLError: role_data = {} for p in (role_data.get("spec") or {}).get("protocols") or []: if not isinstance(p, dict): continue slug = p.get("slug") or p.get("path") rel = p.get("path") content = None if rel: full = root / rel if full.exists(): content = full.read_text(encoding="utf-8") protocols[str(slug)] = content _, ramp_up_items = load_checklist(eng, "ramp_up") _, ramp_down_items = load_checklist(eng, "ramp_down") return { "engagement_id": eng.engagement_id, "phase": phase, "role_id": eng.role_id, "targets": eng.targets, "generated": today_iso(), "path": str(eng.path), "agent_prompt": agent_prompt, "agent_prompt_found": agent_prompt is not None, "memory": memory, "access_plan": access_plan, "phase_instructions": phase_instructions, "protocols": {k: (v is not None) for k, v in protocols.items()}, "protocol_bodies": {k: v for k, v in protocols.items() if v}, "checklist_ramp_up": checklist_summary(ramp_up_items), "checklist_ramp_down": checklist_summary(ramp_down_items), "validation_errors": validate_engagement(eng), "session_close": [ "Update vault memory (session log, findings, envelope)", "Write reports/-*.md for the duty", "Append commercial/ledger.jsonl Kai duty charge (no secrets)", "kaizen-agentic engagement checklist # refresh status", ], "confidentiality": "client_owned", } def render_prepare_markdown(bundle: Dict[str, Any]) -> str: lines = [ f"# Engagement Prepare: {bundle['engagement_id']}", f"Phase: {bundle['phase']}", f"Role: {bundle.get('role_id')}", f"Generated: {bundle['generated']}", f"Path: {bundle['path']}", "", "## Targets", ] for t in bundle.get("targets") or []: lines.append(f"- {t.get('kind', 'target')}: `{t.get('id')}`") lines.append("") errs = bundle.get("validation_errors") or [] if errs: lines.append("## Validation warnings") for e in errs: lines.append(f"- {e}") lines.append("") ru = bundle.get("checklist_ramp_up") or {} rd = bundle.get("checklist_ramp_down") or {} lines.append("## Checklists") lines.append( f"- ramp_up: {ru.get('done', 0)}/{ru.get('total', 0)}" + (f" pending={ru.get('pending')}" if ru.get("pending") else "") ) lines.append( f"- ramp_down: {rd.get('done', 0)}/{rd.get('total', 0)}" + (f" pending={rd.get('pending')}" if rd.get("pending") else "") ) lines.append("") lines.append("## Access Plan") lines.append(bundle.get("access_plan") or "(missing access-plan.md)") lines.append("") if bundle.get("phase_instructions"): lines.append("## Phase Instructions") lines.append(bundle["phase_instructions"]) lines.append("") lines.append("## Agent Definition") if bundle.get("agent_prompt_found"): lines.append(bundle["agent_prompt"]) else: lines.append("(agent definition not found)") lines.append("") lines.append("## Vault Memory") lines.append(bundle.get("memory") or "(no memory yet)") lines.append("") bodies = bundle.get("protocol_bodies") or {} if bodies: lines.append("## Protocols") for slug, body in bodies.items(): lines.append(f"### {slug}") lines.append(body) lines.append("") else: present = bundle.get("protocols") or {} if present: lines.append("## Protocols (paths only)") for slug, ok in present.items(): lines.append(f"- {slug}: {'loaded' if ok else 'missing'}") lines.append("") lines.append("## Session Close") for cmd in bundle.get("session_close") or []: lines.append(f"- {cmd}") lines.append("") lines.append( "_Confidentiality: operational knowledge is client-owned; " "do not copy vault content into supplier public agents._" ) return "\n".join(lines) def scrub_engagement(eng: Engagement) -> Dict[str, Any]: """Heuristic scan of vault + reports for sensitive patterns.""" hits: List[Dict[str, Any]] = [] scan_roots = [ eng.path / "vault", eng.path / "reports", eng.path / "access-plan.md", ] files: List[Path] = [] for root in scan_roots: if root.is_file(): files.append(root) elif root.is_dir(): files.extend( p for p in root.rglob("*") if p.is_file() and p.suffix in {".md", ".txt", ".yml", ".yaml", ".json", ".jsonl"} ) for fpath in files: try: text = fpath.read_text(encoding="utf-8", errors="replace") except OSError: continue for label, pattern in _SCRUB_PATTERNS: for match in pattern.finditer(text): line_no = text[: match.start()].count("\n") + 1 hits.append( { "file": str(fpath.relative_to(eng.path)), "line": line_no, "pattern": label, "snippet": match.group(0)[:40] + ("…" if len(match.group(0)) > 40 else ""), } ) return { "engagement_id": eng.engagement_id, "files_scanned": len(files), "hits": hits, "clean": len(hits) == 0, "note": "Heuristic only — human review required before contribute_lesson", } def export_handoff(eng: Engagement) -> Path: """Ensure handoff pack exists; return path to handoff directory.""" handoff = eng.path / "vault" / "handoff" handoff.mkdir(parents=True, exist_ok=True) readme = handoff / "README.md" if not readme.exists(): readme.write_text( f"# Handoff — {eng.engagement_id}\n\nGenerated: {today_iso()}\n", encoding="utf-8", ) risks = handoff / "risks.md" if not risks.exists(): risks.write_text( "# Outstanding risks\n\n| Risk | Severity | Status |\n|------|----------|--------|\n", encoding="utf-8", ) # Copy baseline files into handoff/baselines if present baselines = eng.path / "vault" / "baselines" if baselines.is_dir(): dest = handoff / "baselines" dest.mkdir(exist_ok=True) for f in baselines.iterdir(): if f.is_file(): shutil.copy2(f, dest / f.name) return handoff def staff_engagement( *, engagement_id: str, role_id: str, client_id: str, target_id: str, target_kind: str = "host", repo_root: Optional[Path] = None, force: bool = False, ) -> Engagement: """Scaffold a new pilot engagement directory from a Role package.""" root = (repo_root or find_repo_root()).resolve() role_dir = root / DEFAULT_ROLES_DIR / role_id if not role_dir.is_dir(): raise EngagementError(f"Role package not found: {role_dir}") dest = root / DEFAULT_PILOTS_DIR / engagement_id if dest.exists() and not force: if (dest / ENGAGEMENT_FILENAME).exists(): return load_engagement(dest) raise EngagementError(f"Directory exists without engagement: {dest}") dest.mkdir(parents=True, exist_ok=True) (dest / "vault" / "baselines").mkdir(parents=True, exist_ok=True) (dest / "vault" / "session-log").mkdir(parents=True, exist_ok=True) (dest / "vault" / "findings").mkdir(parents=True, exist_ok=True) (dest / "vault" / "handoff").mkdir(parents=True, exist_ok=True) (dest / "checklists").mkdir(parents=True, exist_ok=True) (dest / "commercial").mkdir(parents=True, exist_ok=True) (dest / "reports").mkdir(parents=True, exist_ok=True) role_version = "0.1.0" role_yaml = role_dir / "ROLE.yaml" if role_yaml.exists(): try: rd = yaml.safe_load(role_yaml.read_text(encoding="utf-8")) or {} role_version = str( (rd.get("metadata") or {}).get("version") or role_version ) except yaml.YAMLError: pass # Memory from template mem_tmpl = role_dir / "memory-template.md" mem_path = dest / "vault" / "memory.md" if mem_tmpl.exists(): mem_text = mem_tmpl.read_text(encoding="utf-8") mem_text = mem_text.replace("", engagement_id) mem_text = mem_text.replace( "", f"{client_id}-{target_id}" ) mem_text = mem_text.replace("", today_iso()) mem_path.write_text(mem_text, encoding="utf-8") else: mem_path.write_text( f"---\nagent: {role_id}\nengagement_id: {engagement_id}\n" f'last_updated: "{today_iso()}"\nsession_count: 0\n---\n\n# Memory\n', encoding="utf-8", ) # Agent definition from role template + binding frontmatter ad_tmpl = role_dir / "agent-definition.md" agent_name = f"agent-{role_id}.md" if ad_tmpl.exists(): body = ad_tmpl.read_text(encoding="utf-8") # Replace or inject engagement binding frontmatter if body.startswith("---"): parts = body.split("---", 2) fm = yaml.safe_load(parts[1]) if len(parts) >= 3 else {} if not isinstance(fm, dict): fm = {} fm.update( { "engagement_id": engagement_id, "role_id": role_id, "role_version": role_version, "phase": "staffing", "memory_path": f"engagements/pilots/{engagement_id}/vault/memory.md", "targets": [{"kind": target_kind, "id": target_id}], "confidentiality": "client_owned", "access_classes": ["host_observe", "privileged_ops"], "human_approval_for": [ "privileged_ops", "package_upgrade", "firewall_change", "reboot", ], } ) new_fm = yaml.safe_dump(fm, sort_keys=False, allow_unicode=True).strip() body = f"---\n{new_fm}\n---{parts[2] if len(parts) >= 3 else ''}" (dest / agent_name).write_text(body, encoding="utf-8") else: (dest / agent_name).write_text( f"---\nname: {role_id}\nengagement_id: {engagement_id}\nphase: staffing\n---\n\n# {role_id}\n", encoding="utf-8", ) # Checklists from role ramp docs (status tables) for which, fname, prefix in ( ("ramp_up", "ramp-up-status.md", "RU"), ("ramp_down", "ramp-down-status.md", "RD"), ): role_check = role_dir / ("ramp-up.md" if which == "ramp_up" else "ramp-down.md") dest_check = dest / "checklists" / fname if role_check.exists(): # Extract table rows from role checklist items = parse_checklist_markdown(role_check.read_text(encoding="utf-8")) lines = [ f"# {which.replace('_', '-').title()} status — {engagement_id}", "", f"Source checklist: `roles/{role_id}/{role_check.name}`", "", "| ID | Criterion | Status | Evidence |", "|----|-----------|--------|----------|", ] for it in items: lines.append( f"| {it.item_id} | {it.criterion} | todo | {it.evidence} |" ) if not items: lines.append( f"| {prefix}-01 | Complete {which} | todo | see role checklist |" ) dest_check.write_text("\n".join(lines) + "\n", encoding="utf-8") else: dest_check.write_text( f"# {which}\n\n| ID | Criterion | Status | Evidence |\n" f"|----|-----------|--------|----------|\n" f"| {prefix}-01 | Complete | todo | |\n", encoding="utf-8", ) (dest / "access-plan.md").write_text( f"# Access plan — {engagement_id}\n\n" f"**Target:** {target_kind} `{target_id}`\n\n" "Secrets: never stored in this tree.\n\n" "## Verification log\n\n| Date | Result | Notes |\n|------|--------|-------|\n| _pending_ | | RU-01 |\n", encoding="utf-8", ) (dest / "schedule.yml").write_text( f"engagement_id: {engagement_id}\ntimezone: Europe/Berlin\nentries: []\n", encoding="utf-8", ) (dest / "vault" / "baselines" / f"{target_id}.md").write_text( f"# Baseline — {target_id}\n\n**Status:** pending\n", encoding="utf-8", ) (dest / "commercial" / "ledger.jsonl").write_text("", encoding="utf-8") (dest / "commercial" / "quote.yaml").write_text( f"apiVersion: kaizen.agentic/v1\nkind: KaiQuote\nmetadata:\n" f' engagement_id: {engagement_id}\n created: "{today_iso()}"\n' f" currency: KAI\nspec:\n total_kai: 0\n notes: [scaffold — fill quote]\n", encoding="utf-8", ) (dest / "request.yaml").write_text( f"apiVersion: kaizen.agentic/v1\nkind: EngagementRequest\n" f'metadata:\n id: engreq-{engagement_id}\n created: "{today_iso()}"\n' f"spec:\n client:\n id: {client_id}\n role:\n id: {role_id}\n" f" targets:\n - kind: {target_kind}\n id: {target_id}\n", encoding="utf-8", ) eng_doc = { "apiVersion": "kaizen.agentic/v1", "kind": "Engagement", "metadata": { "id": engagement_id, "created": today_iso(), "updated": today_iso(), }, "status": { "phase": "staffing", "notes": "Scaffolded by kaizen-agentic engagement staff", }, "spec": { "client": {"id": client_id}, "role": {"id": role_id, "version": role_version}, "targets": [{"kind": target_kind, "id": target_id}], "agent_definition": { "path": agent_name, "derived_from": f"roles/{role_id}/agent-definition.md", }, "vault": {"root": "vault/", "memory": "vault/memory.md"}, "schedule": {"path": "schedule.yml"}, "checklists": { "ramp_up": "checklists/ramp-up-status.md", "ramp_down": "checklists/ramp-down-status.md", }, "policy": { "confidentiality": "client_owned", "contribute_metrics": False, "human_approval_for": [ "privileged_ops", "package_upgrade", "firewall_change", "reboot", ], }, "commercial": { "currency": "kai", "ledger_ref": "commercial/ledger.jsonl", "quote_ref": "commercial/quote.yaml", }, }, } (dest / ENGAGEMENT_FILENAME).write_text( yaml.safe_dump(eng_doc, sort_keys=False, allow_unicode=True), encoding="utf-8", ) return load_engagement(dest) def load_quote(eng: Engagement) -> Optional[Dict[str, Any]]: path = eng.quote_path() if path is None: return None try: data = yaml.safe_load(path.read_text(encoding="utf-8")) except yaml.YAMLError as exc: raise EngagementError(f"Invalid quote YAML: {exc}") from exc return data if isinstance(data, dict) else None