Kairo-Jinkendo/backend/seeds/seed_004_dogfooding_kairo_jinkendo.py
Lars a1b353fcee
All checks were successful
Deploy Development / deploy (push) Successful in 51s
Test Suite / pytest-backend (push) Successful in 2m27s
Test Suite / lint-backend (push) Successful in 2s
Test Suite / compose-smoke (push) Has been skipped
Test Suite / k6 /api/health Baseline (push) Successful in 18s
Test Suite / playwright-smoke (push) Successful in 14s
fix: Dogfooding-Seed nutzt list_roadmap_items_for_initiative.
Behebt Backend-Startup-Fehler beim Deploy durch falschen Import in seed_004.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-11 10:24:44 +02:00

412 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""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:
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 ✓; AP1.12 ◐; 9be offen"),
("G6 — Archetyp-Steuerung MVP", "active", "AP2.0 ◐; AP2.0d offen"),
("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",
),
(
"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 = [
("AP1.9c — Cockpit-Signale auf Portfolio-Kacheln", g5, project_ids["Frontend"]),
("AP2.0d — Next-Action-Strategien pro Archetyp", g6, project_ids["Backend"]),
("Dogfooding R2 — Ist laufend pflegen", g6, project_ids["Docs & ADPs"]),
]
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] R1 abgeschlossen — Kairo-Jinkendo als Vorhaben gespiegelt")