"""Dogfooding R1 — Referenz-Vorhaben „Jinkendo Kairo“ (idempotent, Nicht-Prod). DEPRECATED (2026-07-12): Nur Dev-Visualisierung. Produkt-Anlage über AP2.2a Starter-Kits. Siehe Kairo_MVP_Execution_Plan_v0.2.md — nicht weiter ausbauen. """ from __future__ import annotations import os from typing import Any, Optional GITEA_BASE = "http://192.168.2.144:3000/Lars/Kairo-Jinkendo" INITIATIVE_TITLE = "Jinkendo Kairo" TENANT_SLUG = "stommer" def _gitea_commit(sha: str, label: str = "") -> str: line = f"Commit: [{sha[:7]}]({GITEA_BASE}/commit/{sha})" if label: return f"{label}\n{line}\nBranch: develop" return f"{line}\nBranch: develop" def _resolve_context() -> tuple[str, str]: from db import get_connection from psycopg2.extras import RealDictCursor conn = get_connection() try: with conn.cursor(cursor_factory=RealDictCursor) as cur: cur.execute("SELECT id FROM tenants WHERE slug = %s", (TENANT_SLUG,)) row = cur.fetchone() if not row: raise RuntimeError( f"Tenant '{TENANT_SLUG}' nicht gefunden — zuerst Portal-Admin/seed_003" ) tenant_id = str(row["id"]) cur.execute( """ SELECT a.id FROM actors a WHERE a.tenant_id = %s AND a.actor_type = 'human' AND a.is_active = TRUE ORDER BY a.created_at LIMIT 1 """, (tenant_id,), ) actor_row = cur.fetchone() if not actor_row: raise RuntimeError("Kein Human-Actor im Tenant — seed_003 zuerst") return tenant_id, str(actor_row["id"]) finally: conn.close() def _find_initiative(tenant_id: str) -> Optional[dict[str, Any]]: from services.initiatives import list_initiatives for item in list_initiatives(tenant_id=tenant_id): if item["title"] == INITIATIVE_TITLE: return item return None def _find_by_title(items: list[dict[str, Any]], title: str) -> Optional[dict[str, Any]]: for item in items: if item["title"] == title: return item return None def _close_gate_and_verify(*, tenant_id: str, item_id: str) -> None: from services.roadmap import get_roadmap_item, verify_reached from services.roadmap_criteria import list_criteria_for_item, waive_criterion item = get_roadmap_item(tenant_id=tenant_id, item_id=item_id) if item and item.get("status") == "reached": return for criterion in list_criteria_for_item(tenant_id=tenant_id, item_id=item_id): if criterion["status"] not in {"satisfied", "waived", "deferred"}: waive_criterion( tenant_id=tenant_id, criterion_id=criterion["id"], decision_title="Dogfooding R1 — historisches Gate", decision_description="Rückwirkende Abnahme per Seed.", ) verify_reached(tenant_id=tenant_id, item_id=item_id) def _ensure_gate_chain( *, tenant_id: str, initiative_id: str, gates: list[tuple[str, str, str]], ) -> dict[str, str]: """gates: (title, status, goal) -> id map.""" from services.roadmap import add_dependency, create_roadmap_item, list_roadmap_items_for_initiative existing = list_roadmap_items_for_initiative(tenant_id=tenant_id, initiative_id=initiative_id) ids: dict[str, str] = {} ordered: list[str] = [] for index, (title, status, goal) in enumerate(gates): found = _find_by_title(existing, title) if found: gate_id = found["id"] else: created = create_roadmap_item( tenant_id=tenant_id, initiative_id=initiative_id, title=title, item_type="milestone", goal_description=goal, status="planned" if status == "reached" else status, sort_order=index * 10, initial_criterion_title=f"{title} — Abnahme", ) gate_id = created["id"] existing.append(created) ids[title] = gate_id ordered.append(gate_id) if status == "reached": _close_gate_and_verify(tenant_id=tenant_id, item_id=gate_id) for i in range(1, len(ordered)): try: add_dependency( tenant_id=tenant_id, from_item_id=ordered[i], to_item_id=ordered[i - 1], dependency_type="requires", ) except ValueError as exc: if "existiert bereits" not in str(exc): raise return ids def _ensure_projects( *, tenant_id: str, initiative_id: str, titles: list[str] ) -> dict[str, str]: from services.projects import create_project, list_projects_for_initiative existing = list_projects_for_initiative(tenant_id=tenant_id, initiative_id=initiative_id) ids: dict[str, str] = {} for index, title in enumerate(titles): found = _find_by_title(existing, title) if found: ids[title] = found["id"] continue created = create_project( tenant_id=tenant_id, initiative_id=initiative_id, title=title, description=f"Ist-Struktur — {title}", sort_order=index, ) ids[title] = created["id"] existing.append(created) return ids def _ensure_action( *, tenant_id: str, initiative_id: str, title: str, status: str, roadmap_item_id: Optional[str] = None, project_id: Optional[str] = None, description: str = "", ) -> dict[str, Any]: from services.actions import create_action, list_actions_for_initiative existing = list_actions_for_initiative(tenant_id=tenant_id, initiative_id=initiative_id) found = _find_by_title(existing, title) if found: if found.get("status") != status: from services.actions import update_action updated = update_action( tenant_id=tenant_id, action_id=found["id"], status=status, ) if updated: found = updated return found return create_action( tenant_id=tenant_id, initiative_id=initiative_id, title=title, description=description, status=status, roadmap_item_id=roadmap_item_id, project_id=project_id, ) def _ensure_evidence( *, tenant_id: str, initiative_id: str, title: str, description: str, roadmap_item_id: Optional[str] = None, action_id: Optional[str] = None, actor_id: Optional[str] = None, ) -> None: from services.evidence import create_evidence, list_evidence_for_initiative for item in list_evidence_for_initiative(tenant_id=tenant_id, initiative_id=initiative_id): if item["title"] == title: return create_evidence( tenant_id=tenant_id, initiative_id=initiative_id, title=title, description=description, status="accepted", roadmap_item_id=roadmap_item_id, action_id=action_id, submitted_by_actor_id=actor_id, ) def _ensure_decision( *, tenant_id: str, initiative_id: str, title: str, outcome: str, ) -> None: from db import get_connection from psycopg2.extras import RealDictCursor from services.decisions import create_decision conn = get_connection() try: with conn.cursor(cursor_factory=RealDictCursor) as cur: cur.execute( """ SELECT 1 FROM decisions WHERE tenant_id = %s AND initiative_id = %s AND title = %s """, (tenant_id, initiative_id, title), ) if cur.fetchone(): return finally: conn.close() create_decision( tenant_id=tenant_id, initiative_id=initiative_id, title=title, description=outcome, status="decided", outcome=outcome, ) def _ensure_portfolio_first(*, tenant_id: str, initiative_id: str) -> None: from services.initiatives import list_initiatives, reorder_portfolio all_items = list_initiatives(tenant_id=tenant_id) ids = [initiative_id] + [i["id"] for i in all_items if i["id"] != initiative_id] reorder_portfolio(tenant_id=tenant_id, initiative_ids=ids) def run() -> None: if os.getenv("ENVIRONMENT", "development").strip().lower() == "production": print("[dogfooding] Übersprungen in Production") return tenant_id, owner_actor_id = _resolve_context() initiative = _find_initiative(tenant_id) if not initiative: from services.initiatives import create_initiative initiative = create_initiative( tenant_id=tenant_id, title=INITIATIVE_TITLE, owner_actor_id=owner_actor_id, archetype_key="initiative.product", method_profile_key="product.kairo_dev", goal=( "Operativer Program Director — MVP v0.3 Stufe A (B2b). " f"Repository: {GITEA_BASE} (Branch develop)" ), vision="Kairo steuert Kairo — Dogfooding Referenz-Vorhaben.", target_state_summary="Leitfrage in ≤2 Min; Gates + Ist gespiegelt aus Git/Docs.", priority="high", ) print(f"[dogfooding] Initiative angelegt: {initiative['id']}") else: print(f"[dogfooding] Initiative vorhanden: {initiative['id']}") initiative_id = initiative["id"] gate_defs = [ ("G1 — Foundation AP0", "reached", "Tenant, Actor, Auth, OM, Migrationen"), ("G2 — IA & Steering Skeleton", "reached", "AP1.2c, AP1.0, Snapshot, Signals"), ("G3 — Gates & Checkliste", "reached", "AP1.4/4b/4c, Verify, Kriterien"), ("G4 — Ist-Hierarchie & Plan/Ist", "reached", "AP1.5/5c, AP1.6, AP1.14, Journey"), ("G5 — PM Work Modes & Plan-Outline", "active", "AP1.9a/9c ✓; AP1.12 ◐; AP1.16 ◐"), ("G6 — Archetyp-Steuerung MVP", "active", "AP2.0 ◐; AP2.0d ✓; Execution-Graph ✓"), ("G7 — MVP-Abnahfe B2b", "planned", "Validation, Leitfrage, Cockpit-Signale"), ("G8 — Agent & Integration", "planned", "AP1.7, Gitea-Webhook, MCP"), ] gate_ids = _ensure_gate_chain( tenant_id=tenant_id, initiative_id=initiative_id, gates=gate_defs, ) project_ids = _ensure_projects( tenant_id=tenant_id, initiative_id=initiative_id, titles=["Backend", "Frontend", "Docs & ADPs", "Deploy / CI"], ) g5 = gate_ids["G5 — PM Work Modes & Plan-Outline"] g6 = gate_ids["G6 — Archetyp-Steuerung MVP"] g4 = gate_ids["G4 — Ist-Hierarchie & Plan/Ist"] done_actions = [ ( "AP1.6b — Journey Gate-Fortschritt", "done", g4, project_ids["Frontend"], "b85a6ec", ), ( "AP1.8a — Portfolio-Rang Cockpit", "done", g5, project_ids["Frontend"], "a19c6ab", ), ( "AP1.14 — Plan/Ist-Snapshots", "done", g4, project_ids["Backend"], "064779e", ), ( "AP2.0 — Archetyp-Slice", "done", g6, project_ids["Backend"], "3bd9832", ), ( "AP1.16 — Execution-Graph & Durchführungsplan", "done", g6, project_ids["Backend"], "b9bfa9e", ), ( "AP1.9c — Cockpit-Signale auf Portfolio-Kacheln", "done", g5, project_ids["Frontend"], "b9bfa9e", ), ( "AP2.0d — Next-Action-Strategien pro Archetyp", "done", g6, project_ids["Backend"], "284b9d5", ), ( "DOC-Sync — Status-Review & Roadmap", "done", g6, project_ids["Docs & ADPs"], None, ), ] for title, status, gate_id, project_id, sha in done_actions: action = _ensure_action( tenant_id=tenant_id, initiative_id=initiative_id, title=title, status=status, roadmap_item_id=gate_id, project_id=project_id, ) if sha: _ensure_evidence( tenant_id=tenant_id, initiative_id=initiative_id, title=f"Merge {title}", description=_gitea_commit(sha, title), roadmap_item_id=gate_id, action_id=action["id"], actor_id=owner_actor_id, ) open_actions = [ ("Dogfooding R2 — Ist laufend pflegen", g6, project_ids["Docs & ADPs"]), ("AP1.7 — Operational Actor API", g6, project_ids["Backend"]), ("AP2.0f — work_cycle / Sprint-Zeitbox", g6, project_ids["Backend"]), ] for title, gate_id, project_id in open_actions: _ensure_action( tenant_id=tenant_id, initiative_id=initiative_id, title=title, status="open", roadmap_item_id=gate_id, project_id=project_id, ) _ensure_decision( tenant_id=tenant_id, initiative_id=initiative_id, title="Join/Branch Graph — deferred", outcome="OR-Alternativpfade und volle Join-Logik erst bei Use Case (AP1.15d). " "Siehe ADP_Roadmap_Graph_and_Gate_Checklist_v0.1.md", ) _ensure_decision( tenant_id=tenant_id, initiative_id=initiative_id, title="MCP nach Operational API", outcome="MCP-Adapter erst nach AP1.7 (/api/operational). Schicht 4 eingefroren bis B2b-Validation.", ) _ensure_decision( tenant_id=tenant_id, initiative_id=initiative_id, title="MVP v0.3 PO-Freigabe", outcome="Abnahfe Stufe A: A1, A2, B2b + B3 minimal. Dokument: Kairo_MVP_Definition_v0.3.md", ) _ensure_portfolio_first(tenant_id=tenant_id, initiative_id=initiative_id) from services.actors import create_actor, list_actors agents = list_actors(tenant_id=tenant_id, actor_type="agent", q="Cursor") if not any(a["name"] == "Cursor Agent" for a in agents): create_actor(tenant_id=tenant_id, actor_type="agent", name="Cursor Agent") print("[dogfooding] Agent „Cursor Agent“ angelegt — Service Token manuell erstellen") print("[dogfooding] R2 sync — Ist-Stand gespiegelt (AP1.9c, AP1.16, AP2.0d done)")