Kairo-Jinkendo/backend/seeds/seed_004_dogfooding_kairo_jinkendo.py
Lars 2359cc8b59
Some checks failed
Deploy Development / deploy (push) Failing after 52s
Test Suite / pytest-backend (push) Failing after 1s
Test Suite / k6 /api/health Baseline (push) Has been skipped
Test Suite / playwright-smoke (push) Has been skipped
Test Suite / lint-backend (push) Successful in 2s
Test Suite / compose-smoke (push) Has been skipped
Dogfooding R2: Seed spiegelt AP1.9c, AP1.16 und AP2.0d als done.
seed_004 aktualisiert Actions/Evidence und offene naechste Schritte; Gate-Beschreibungen und Docs sync.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-12 12:06:04 +02:00

444 lines
14 KiB
Python

"""Dogfooding R1 — Referenz-Vorhaben „Jinkendo Kairo“ (idempotent, Nicht-Prod).
Spiegelt Plan aus docs/product/ gemäß Kairo_Dogfooding_Mirror_Steering_v0.1.md.
"""
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 verify_reached
from services.roadmap_criteria import list_criteria_for_item, waive_criterion
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)
print("[dogfooding] R2 sync — Ist-Stand gespiegelt (AP1.9c, AP1.16, AP2.0d done)")