AP1.16a-b: Execution-Graph für Arbeitspaket-Abhängigkeiten.
All checks were successful
Deploy Development / deploy (push) Successful in 48s
Test Suite / pytest-backend (push) Successful in 2m20s
Test Suite / lint-backend (push) Successful in 3s
Test Suite / compose-smoke (push) Has been skipped
Test Suite / k6 /api/health Baseline (push) Successful in 19s
Test Suite / playwright-smoke (push) Successful in 14s

Migration 023, Engine, API und ADP für Durchführungsplan getrennt vom Gate-Graph; Docs und Sprint-Assignment synchronisiert.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Lars 2026-07-12 10:38:01 +02:00
parent a1b353fcee
commit 1166a0c7f1
16 changed files with 1421 additions and 43 deletions

View File

@ -65,6 +65,7 @@ from routers import ( # noqa: E402
decisions,
entity_archetypes,
evidence,
execution_plan,
features,
initiatives,
journey,
@ -100,6 +101,7 @@ app.include_router(roadmap.initiative_router)
app.include_router(roadmap.items_router)
app.include_router(roadmap.deps_router)
app.include_router(roadmap.criteria_router)
app.include_router(execution_plan.initiative_router)
app.include_router(evidence.router)
app.include_router(decisions.router)
app.include_router(reviews.router)

View File

@ -0,0 +1,32 @@
-- AP1.16a: Durchführungsplan — Action-Reihenfolge, kind, Abhängigkeiten
ALTER TABLE actions
ADD COLUMN IF NOT EXISTS sort_order INT NOT NULL DEFAULT 0;
ALTER TABLE actions
ADD COLUMN IF NOT EXISTS action_kind VARCHAR(32) NOT NULL DEFAULT 'delivery'
CHECK (action_kind IN ('delivery', 'planning', 'review'));
CREATE INDEX idx_actions_initiative_sort
ON actions(tenant_id, initiative_id, sort_order);
CREATE TABLE action_dependencies (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
initiative_id UUID NOT NULL REFERENCES initiatives(id) ON DELETE CASCADE,
predecessor_action_id UUID NOT NULL REFERENCES actions(id) ON DELETE CASCADE,
successor_action_id UUID NOT NULL REFERENCES actions(id) ON DELETE CASCADE,
dependency_kind VARCHAR(32) NOT NULL DEFAULT 'requires'
CHECK (dependency_kind IN ('requires', 'blocks', 'relates')),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (tenant_id, predecessor_action_id, successor_action_id, dependency_kind)
);
CREATE INDEX idx_action_deps_predecessor
ON action_dependencies(tenant_id, predecessor_action_id);
CREATE INDEX idx_action_deps_successor
ON action_dependencies(tenant_id, successor_action_id);
CREATE INDEX idx_action_deps_initiative
ON action_dependencies(tenant_id, initiative_id);

View File

@ -29,6 +29,8 @@ class ActionUpdateRequest(BaseModel):
clear_project: bool = False
roadmap_item_id: Optional[str] = None
clear_roadmap_item: bool = False
sort_order: Optional[int] = None
action_kind: Optional[Literal["delivery", "planning", "review"]] = None
class TaskCreateRequest(BaseModel):
@ -135,6 +137,8 @@ def update_action(
clear_project=body.clear_project,
roadmap_item_id=body.roadmap_item_id,
clear_roadmap_item=body.clear_roadmap_item,
sort_order=body.sort_order,
action_kind=body.action_kind,
)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc

View File

@ -0,0 +1,119 @@
"""Execution plan API — AP1.16b."""
from __future__ import annotations
from typing import Literal, Optional
from capabilities import require_capability
from fastapi import APIRouter, Depends, HTTPException, Query
from pydantic import BaseModel
from services import actions as action_service
from services import execution_plan as execution_plan_service
from steering.graph.execution_engine import load_initiative_execution_graph_state
from tenant_context import TenantContext
initiative_router = APIRouter(prefix="/api/initiatives", tags=["execution-plan"])
class ActionDependencyCreateRequest(BaseModel):
predecessor_action_id: str
successor_action_id: str
dependency_kind: Literal["requires", "blocks", "relates"] = "requires"
@initiative_router.get("/{initiative_id}/execution/graph-state")
def get_initiative_execution_graph_state(
initiative_id: str,
scope_roadmap_item_id: Optional[str] = Query(default=None),
ctx: TenantContext = Depends(require_capability("kairo.action.read")),
):
try:
state = load_initiative_execution_graph_state(
tenant_id=ctx.tenant_id,
initiative_id=initiative_id,
scope_roadmap_item_id=scope_roadmap_item_id,
)
if scope_roadmap_item_id is None:
from services import actions as action_service
from services import roadmap as roadmap_service
all_actions = action_service.list_actions_for_initiative(
tenant_id=ctx.tenant_id, initiative_id=initiative_id
)
roadmap_items = roadmap_service.list_roadmap_items_for_initiative(
tenant_id=ctx.tenant_id, initiative_id=initiative_id
)
from steering.graph.execution_engine import compute_planning_debt
state["planning_debt"] = compute_planning_debt(
actions=all_actions,
roadmap_items=roadmap_items,
)
return state
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
@initiative_router.get("/{initiative_id}/execution/dependencies")
def list_initiative_action_dependencies(
initiative_id: str,
ctx: TenantContext = Depends(require_capability("kairo.action.read")),
):
try:
return execution_plan_service.list_dependencies_for_initiative(
tenant_id=ctx.tenant_id, initiative_id=initiative_id
)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
@initiative_router.post("/{initiative_id}/execution/dependencies", status_code=201)
def create_initiative_action_dependency(
initiative_id: str,
body: ActionDependencyCreateRequest,
ctx: TenantContext = Depends(require_capability("kairo.action.manage")),
):
try:
predecessor = action_service.get_action(
tenant_id=ctx.tenant_id, action_id=body.predecessor_action_id
)
successor = action_service.get_action(
tenant_id=ctx.tenant_id, action_id=body.successor_action_id
)
if not predecessor or not successor:
raise ValueError("Action nicht gefunden")
if (
predecessor["initiative_id"] != initiative_id
or successor["initiative_id"] != initiative_id
):
raise ValueError("Actions gehören nicht zum angegebenen Vorhaben")
return execution_plan_service.add_dependency(
tenant_id=ctx.tenant_id,
predecessor_action_id=body.predecessor_action_id,
successor_action_id=body.successor_action_id,
dependency_kind=body.dependency_kind,
user_id=ctx.user_id,
)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
@initiative_router.delete(
"/{initiative_id}/execution/dependencies/{dependency_id}",
status_code=204,
)
def delete_initiative_action_dependency(
initiative_id: str,
dependency_id: str,
ctx: TenantContext = Depends(require_capability("kairo.action.manage")),
):
del initiative_id # scope validated via tenant + dependency row
try:
execution_plan_service.delete_dependency(
tenant_id=ctx.tenant_id,
dependency_id=dependency_id,
user_id=ctx.user_id,
)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc

View File

@ -67,6 +67,8 @@ class ActionCreateRequest(BaseModel):
project_id: Optional[str] = None
roadmap_item_id: Optional[str] = None
assigned_actor_ids: list[str] = Field(default_factory=list)
sort_order: int = 0
action_kind: Literal["delivery", "planning", "review"] = "delivery"
class BlockerCreateRequest(BaseModel):
@ -331,6 +333,8 @@ def create_initiative_action(
project_id=body.project_id,
roadmap_item_id=body.roadmap_item_id,
assigned_actor_ids=body.assigned_actor_ids,
sort_order=body.sort_order,
action_kind=body.action_kind,
user_id=ctx.user_id,
)
except ValueError as exc:

View File

@ -25,9 +25,11 @@ OPEN_ACTION_STATUSES = frozenset(
_ACTION_COLUMNS = """
id, tenant_id, initiative_id, project_id, roadmap_item_id, title, description,
status, priority, due_at, created_at, updated_at
status, priority, due_at, sort_order, action_kind, created_at, updated_at
"""
ACTION_KINDS = frozenset({"delivery", "planning", "review"})
def _serialize_row(row: dict[str, Any]) -> dict[str, Any]:
result = dict(row)
@ -43,6 +45,11 @@ def _serialize_row(row: dict[str, Any]) -> dict[str, Any]:
return result
def _validate_action_kind(action_kind: str) -> None:
if action_kind not in ACTION_KINDS:
raise ValueError(f"Ungültiger action_kind: {action_kind}")
def _validate_action_status(status: str) -> None:
if status not in ACTION_STATUSES:
raise ValueError(f"Ungültiger Action-Status: {status}")
@ -138,6 +145,8 @@ def create_action(
project_id: Optional[str] = None,
roadmap_item_id: Optional[str] = None,
assigned_actor_ids: Optional[list[str]] = None,
sort_order: int = 0,
action_kind: str = "delivery",
user_id: Optional[str] = None,
) -> dict[str, Any]:
title = title.strip()
@ -145,6 +154,7 @@ def create_action(
raise ValueError("Titel ist erforderlich")
_validate_action_status(status)
_validate_priority(priority)
_validate_action_kind(action_kind)
if not get_initiative(tenant_id=tenant_id, initiative_id=initiative_id):
raise ValueError("Initiative nicht gefunden")
_validate_project_in_initiative(
@ -169,9 +179,9 @@ def create_action(
f"""
INSERT INTO actions (
tenant_id, initiative_id, project_id, roadmap_item_id, title, description,
status, priority, due_at
status, priority, due_at, sort_order, action_kind
)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
RETURNING {_ACTION_COLUMNS}
""",
(
@ -184,6 +194,8 @@ def create_action(
status,
priority,
due_at,
sort_order,
action_kind,
),
)
row = _serialize_row(dict(cur.fetchone()))
@ -228,7 +240,7 @@ def list_actions_for_initiative(
SELECT {_ACTION_COLUMNS}
FROM actions
WHERE tenant_id = %s AND initiative_id = %s
ORDER BY updated_at DESC, title
ORDER BY sort_order ASC, title ASC, created_at DESC
""",
(tenant_id, initiative_id),
)
@ -274,6 +286,8 @@ def update_action(
clear_project: bool = False,
roadmap_item_id: Optional[str] = None,
clear_roadmap_item: bool = False,
sort_order: Optional[int] = None,
action_kind: Optional[str] = None,
) -> Optional[dict[str, Any]]:
existing = get_action(tenant_id=tenant_id, action_id=action_id)
if not existing:
@ -331,6 +345,13 @@ def update_action(
conn.close()
updates.append("roadmap_item_id = %s")
params.append(roadmap_item_id)
if sort_order is not None:
updates.append("sort_order = %s")
params.append(sort_order)
if action_kind is not None:
_validate_action_kind(action_kind)
updates.append("action_kind = %s")
params.append(action_kind)
if not updates:
return existing

View File

@ -0,0 +1,225 @@
"""Execution plan — Action dependencies CRUD (AP1.16a/b)."""
from __future__ import annotations
from typing import Any, Literal, Optional
from psycopg2.extras import RealDictCursor
from db import get_connection
from services.audit import log_audit
from services.actions import get_action
DependencyKind = Literal["requires", "blocks", "relates"]
DEPENDENCY_KINDS = frozenset({"requires", "blocks", "relates"})
def _validate_dependency_kind(kind: str) -> None:
if kind not in DEPENDENCY_KINDS:
raise ValueError(f"Ungültiger dependency_kind: {kind}")
def _serialize_dependency(row: dict[str, Any]) -> dict[str, Any]:
result = dict(row)
for key in (
"id",
"tenant_id",
"initiative_id",
"predecessor_action_id",
"successor_action_id",
):
if result.get(key):
result[key] = str(result[key])
if result.get("created_at"):
result["created_at"] = result["created_at"].isoformat()
return result
def _would_create_cycle(
*,
dependencies: list[dict[str, Any]],
predecessor_id: str,
successor_id: str,
) -> bool:
"""Prüft Zyklus wenn successor transitiv predecessor requires."""
graph: dict[str, list[str]] = {}
for dep in dependencies:
if dep.get("dependency_kind", "requires") != "requires":
continue
pred = str(dep["predecessor_action_id"])
succ = str(dep["successor_action_id"])
graph.setdefault(succ, []).append(pred)
graph.setdefault(successor_id, []).append(predecessor_id)
visiting: set[str] = set()
visited: set[str] = set()
def dfs(node: str) -> bool:
if node in visiting:
return True
if node in visited:
return False
visiting.add(node)
for upstream in graph.get(node, []):
if dfs(upstream):
return True
visiting.remove(node)
visited.add(node)
return False
return dfs(successor_id)
def list_dependencies_for_initiative(
*, tenant_id: str, initiative_id: str
) -> list[dict[str, Any]]:
conn = get_connection()
try:
with conn.cursor(cursor_factory=RealDictCursor) as cur:
cur.execute(
"""
SELECT id, tenant_id, initiative_id,
predecessor_action_id, successor_action_id,
dependency_kind, created_at
FROM action_dependencies
WHERE tenant_id = %s AND initiative_id = %s
ORDER BY created_at
""",
(tenant_id, initiative_id),
)
return [_serialize_dependency(dict(row)) for row in cur.fetchall()]
finally:
conn.close()
def add_dependency(
*,
tenant_id: str,
predecessor_action_id: str,
successor_action_id: str,
dependency_kind: DependencyKind = "requires",
user_id: Optional[str] = None,
) -> dict[str, Any]:
_validate_dependency_kind(dependency_kind)
if predecessor_action_id == successor_action_id:
raise ValueError("Abhängigkeit auf sich selbst nicht erlaubt")
predecessor = get_action(tenant_id=tenant_id, action_id=predecessor_action_id)
successor = get_action(tenant_id=tenant_id, action_id=successor_action_id)
if not predecessor or not successor:
raise ValueError("Action nicht gefunden")
if predecessor["initiative_id"] != successor["initiative_id"]:
raise ValueError("Abhängigkeiten nur innerhalb eines Vorhabens")
initiative_id = predecessor["initiative_id"]
existing = list_dependencies_for_initiative(
tenant_id=tenant_id, initiative_id=initiative_id
)
if _would_create_cycle(
dependencies=existing,
predecessor_id=predecessor_action_id,
successor_id=successor_action_id,
):
raise ValueError("Abhängigkeit würde Zyklus erzeugen")
conn = get_connection()
try:
with conn.cursor(cursor_factory=RealDictCursor) as cur:
cur.execute(
"""
SELECT 1 FROM action_dependencies
WHERE tenant_id = %s
AND predecessor_action_id = %s
AND successor_action_id = %s
AND dependency_kind = %s
""",
(
tenant_id,
predecessor_action_id,
successor_action_id,
dependency_kind,
),
)
if cur.fetchone():
raise ValueError("Abhängigkeit existiert bereits")
cur.execute(
"""
INSERT INTO action_dependencies (
tenant_id, initiative_id,
predecessor_action_id, successor_action_id,
dependency_kind
)
VALUES (%s, %s, %s, %s, %s)
RETURNING id, tenant_id, initiative_id,
predecessor_action_id, successor_action_id,
dependency_kind, created_at
""",
(
tenant_id,
initiative_id,
predecessor_action_id,
successor_action_id,
dependency_kind,
),
)
row = _serialize_dependency(dict(cur.fetchone()))
conn.commit()
finally:
conn.close()
log_audit(
"action.dependency_added",
user_id=user_id,
tenant_id=tenant_id,
details={
"predecessor_action_id": predecessor_action_id,
"successor_action_id": successor_action_id,
"dependency_kind": dependency_kind,
},
)
return row
def delete_dependency(
*, tenant_id: str, dependency_id: str, user_id: Optional[str] = None
) -> bool:
conn = get_connection()
try:
with conn.cursor(cursor_factory=RealDictCursor) as cur:
cur.execute(
"""
SELECT id, predecessor_action_id, successor_action_id, dependency_kind
FROM action_dependencies
WHERE id = %s AND tenant_id = %s
""",
(dependency_id, tenant_id),
)
row = cur.fetchone()
if not row:
raise ValueError("Abhängigkeit nicht gefunden")
cur.execute(
"""
DELETE FROM action_dependencies
WHERE id = %s AND tenant_id = %s
""",
(dependency_id, tenant_id),
)
conn.commit()
finally:
conn.close()
log_audit(
"action.dependency_removed",
user_id=user_id,
tenant_id=tenant_id,
details={
"dependency_id": dependency_id,
"predecessor_action_id": str(row["predecessor_action_id"]),
"successor_action_id": str(row["successor_action_id"]),
},
)
return True

View File

@ -0,0 +1,246 @@
"""Execution plan read models — AP1.16b (Action dependency graph)."""
from __future__ import annotations
from typing import Any, Optional
DONE_STATUSES = frozenset({"done"})
OPEN_STATUSES = frozenset(
{"open", "ready", "in_progress", "blocked", "review_required"}
)
TERMINAL_SKIP = frozenset({"done", "discarded"})
def _is_predecessor_satisfied(status: str) -> bool:
return status in DONE_STATUSES
def _sort_actions(actions: list[dict[str, Any]]) -> list[dict[str, Any]]:
return sorted(
actions,
key=lambda action: (
action.get("sort_order", 0),
action.get("title") or "",
str(action.get("id", "")),
),
)
def _sequential_order_blocked_by(
action: dict[str, Any],
sorted_actions: list[dict[str, Any]],
) -> list[str]:
"""Fallback ohne Kanten: sequenzielle Actions nach sort_order."""
blocked_by: list[str] = []
action_id = str(action["id"])
for prev in sorted_actions:
if str(prev["id"]) == action_id:
break
if not _is_predecessor_satisfied(prev.get("status", "open")):
blocked_by.append(str(prev["id"]))
return blocked_by
def _compute_critical_path(
*,
action_by_id: dict[str, dict[str, Any]],
requires_predecessors: dict[str, list[str]],
) -> list[str]:
"""Längste Kette über requires-Kanten (Memoization)."""
memo: dict[str, list[str]] = {}
def chain_for(action_id: str) -> list[str]:
if action_id in memo:
return memo[action_id]
preds = requires_predecessors.get(action_id, [])
if not preds:
memo[action_id] = [action_id]
return memo[action_id]
best: list[str] = []
for pred_id in preds:
if pred_id not in action_by_id:
continue
upstream = chain_for(pred_id)
if len(upstream) > len(best):
best = upstream
memo[action_id] = best + [action_id]
return memo[action_id]
longest: list[str] = []
for action_id in action_by_id:
candidate = chain_for(action_id)
if len(candidate) > len(longest):
longest = candidate
return longest
def compute_planning_debt(
*,
actions: list[dict[str, Any]],
roadmap_items: list[dict[str, Any]],
) -> list[dict[str, Any]]:
"""Aktive Gates ohne verknüpfte Actions → Planning Debt (AP1.16d light)."""
active_gates = [
item
for item in roadmap_items
if item.get("status") == "active"
]
if not active_gates:
return []
actions_by_gate: dict[str, int] = {}
for action in actions:
gate_id = action.get("roadmap_item_id")
if not gate_id:
continue
gid = str(gate_id)
actions_by_gate[gid] = actions_by_gate.get(gid, 0) + 1
debts: list[dict[str, Any]] = []
for gate in active_gates:
gid = str(gate["id"])
if actions_by_gate.get(gid, 0) == 0:
debts.append(
{
"kind": "missing_execution_plan",
"roadmap_item_id": gid,
"title": gate.get("title"),
"message": "Aktiver Zielzustand ohne Durchführungsplan",
}
)
return debts
def compute_execution_graph_state(
*,
actions: list[dict[str, Any]],
dependencies: list[dict[str, Any]],
scope_roadmap_item_id: Optional[str] = None,
) -> dict[str, Any]:
"""
Read Model pro Initiative (optional Gate-Scope).
DB-Kante: predecessor successor
- requires: successor blocked bis predecessor done
- blocks: successor blocked solange predecessor offen
- relates: keine Blockade
"""
if scope_roadmap_item_id:
scope = str(scope_roadmap_item_id)
actions = [
a
for a in actions
if a.get("roadmap_item_id") and str(a["roadmap_item_id"]) == scope
]
action_ids = {str(a["id"]) for a in actions}
dependencies = [
dep
for dep in dependencies
if str(dep["predecessor_action_id"]) in action_ids
and str(dep["successor_action_id"]) in action_ids
]
sorted_actions = _sort_actions(actions)
action_by_id = {str(action["id"]): action for action in actions}
has_any_deps = len(dependencies) > 0
blocked_by_map: dict[str, list[str]] = {
action_id: [] for action_id in action_by_id
}
requires_predecessors: dict[str, list[str]] = {
action_id: [] for action_id in action_by_id
}
for dep in dependencies:
kind = dep.get("dependency_kind") or "requires"
pred_id = str(dep["predecessor_action_id"])
succ_id = str(dep["successor_action_id"])
if pred_id not in action_by_id or succ_id not in action_by_id:
continue
if kind == "requires":
requires_predecessors.setdefault(succ_id, []).append(pred_id)
predecessor = action_by_id[pred_id]
if not _is_predecessor_satisfied(predecessor.get("status", "open")):
if pred_id not in blocked_by_map[succ_id]:
blocked_by_map[succ_id].append(pred_id)
elif kind == "blocks":
blocker = action_by_id[pred_id]
if blocker.get("status", "open") in OPEN_STATUSES:
if pred_id not in blocked_by_map[succ_id]:
blocked_by_map[succ_id].append(pred_id)
# relates: no block
if not has_any_deps:
for action in sorted_actions:
action_id = str(action["id"])
for prereq_id in _sequential_order_blocked_by(action, sorted_actions):
if prereq_id not in blocked_by_map[action_id]:
blocked_by_map[action_id].append(prereq_id)
if prereq_id not in requires_predecessors.get(action_id, []):
requires_predecessors.setdefault(action_id, []).append(prereq_id)
action_states: dict[str, dict[str, Any]] = {}
blocked_actions: list[str] = []
ready_actions: list[str] = []
for action_id, action in action_by_id.items():
status = action.get("status", "open")
blocked_by = blocked_by_map.get(action_id, [])
blocked = len(blocked_by) > 0 and status not in TERMINAL_SKIP
ready = status in OPEN_STATUSES and not blocked
action_states[action_id] = {
"status": status,
"action_kind": action.get("action_kind", "delivery"),
"sort_order": action.get("sort_order", 0),
"roadmap_item_id": (
str(action["roadmap_item_id"]) if action.get("roadmap_item_id") else None
),
"blocked": blocked,
"ready": ready,
"blocked_by": blocked_by,
}
if blocked:
blocked_actions.append(action_id)
if ready:
ready_actions.append(action_id)
critical_path = _compute_critical_path(
action_by_id=action_by_id,
requires_predecessors=requires_predecessors,
)
result: dict[str, Any] = {
"items": action_states,
"blocked_actions": blocked_actions,
"ready_actions": ready_actions,
"critical_path": critical_path,
"scope_roadmap_item_id": scope_roadmap_item_id,
}
return result
def load_initiative_execution_graph_state(
*,
tenant_id: str,
initiative_id: str,
scope_roadmap_item_id: Optional[str] = None,
) -> dict[str, Any]:
"""Lädt Actions, Kanten und optional Planning Debt."""
from services import actions as action_service
from services import execution_plan as execution_plan_service
actions = action_service.list_actions_for_initiative(
tenant_id=tenant_id, initiative_id=initiative_id
)
dependencies = execution_plan_service.list_dependencies_for_initiative(
tenant_id=tenant_id, initiative_id=initiative_id
)
return compute_execution_graph_state(
actions=actions,
dependencies=dependencies,
scope_roadmap_item_id=scope_roadmap_item_id,
)

View File

@ -0,0 +1,116 @@
"""Unit tests for execution graph engine (AP1.16b)."""
from steering.graph.execution_engine import (
compute_execution_graph_state,
compute_planning_debt,
)
def _action(action_id: str, status: str = "open", **kwargs):
return {
"id": action_id,
"status": status,
"sort_order": kwargs.get("sort_order", 0),
"title": kwargs.get("title", action_id),
"action_kind": kwargs.get("action_kind", "delivery"),
"roadmap_item_id": kwargs.get("roadmap_item_id"),
}
def test_requires_blocks_until_predecessor_done():
actions = [_action("b", "open"), _action("a", "open")]
deps = [
{
"predecessor_action_id": "b",
"successor_action_id": "a",
"dependency_kind": "requires",
}
]
state = compute_execution_graph_state(actions=actions, dependencies=deps)
assert state["items"]["a"]["blocked"] is True
assert state["items"]["a"]["blocked_by"] == ["b"]
assert "a" in state["blocked_actions"]
assert "a" not in state["ready_actions"]
actions[0]["status"] = "done"
state = compute_execution_graph_state(actions=actions, dependencies=deps)
assert state["items"]["a"]["ready"] is True
assert "a" in state["ready_actions"]
def test_sequential_fallback_without_edges():
actions = [
_action("first", "open", sort_order=0),
_action("second", "open", sort_order=1),
]
state = compute_execution_graph_state(actions=actions, dependencies=[])
assert state["items"]["first"]["ready"] is True
assert state["items"]["second"]["blocked"] is True
assert state["items"]["second"]["blocked_by"] == ["first"]
actions[0]["status"] = "done"
state = compute_execution_graph_state(actions=actions, dependencies=[])
assert state["items"]["second"]["ready"] is True
def test_blocks_edge():
actions = [_action("blocker", "in_progress"), _action("blocked", "open")]
deps = [
{
"predecessor_action_id": "blocker",
"successor_action_id": "blocked",
"dependency_kind": "blocks",
}
]
state = compute_execution_graph_state(actions=actions, dependencies=deps)
assert state["items"]["blocked"]["blocked"] is True
actions[0]["status"] = "done"
state = compute_execution_graph_state(actions=actions, dependencies=deps)
assert state["items"]["blocked"]["ready"] is True
def test_critical_path():
actions = [
_action("a", "done", sort_order=0),
_action("b", "open", sort_order=1),
_action("c", "open", sort_order=2),
]
deps = [
{
"predecessor_action_id": "a",
"successor_action_id": "b",
"dependency_kind": "requires",
},
{
"predecessor_action_id": "b",
"successor_action_id": "c",
"dependency_kind": "requires",
},
]
state = compute_execution_graph_state(actions=actions, dependencies=deps)
assert state["critical_path"] == ["a", "b", "c"]
def test_planning_debt_active_gate_without_actions():
actions = [_action("x", roadmap_item_id="gate-2")]
roadmap_items = [
{"id": "gate-1", "status": "active", "title": "G5"},
{"id": "gate-2", "status": "active", "title": "G6"},
]
debts = compute_planning_debt(actions=actions, roadmap_items=roadmap_items)
assert len(debts) == 1
assert debts[0]["roadmap_item_id"] == "gate-1"
def test_scope_filters_to_gate():
actions = [
_action("a", roadmap_item_id="g1", sort_order=0),
_action("b", roadmap_item_id="g2", sort_order=0),
]
state = compute_execution_graph_state(
actions=actions,
dependencies=[],
scope_roadmap_item_id="g1",
)
assert set(state["items"].keys()) == {"a"}

View File

@ -1,3 +1,3 @@
APP_VERSION = "0.18.0-ap2.0"
DB_SCHEMA_VERSION = "019"
DB_SCHEMA_VERSION = "023"
APP_NAME = "jinkendo-kairo"

View File

@ -0,0 +1,354 @@
# ADP — Durchführungsplan, Work-Package-Abhängigkeiten & Progressive Planung v0.1
**Status:** PO-Freigabe (2026-07-12)
**Stand:** 2026-07-12
**Autor:** PO/Architektur (Dogfooding-Erkenntnis Kairo-Jinkendo)
**Auslöser:** Gate-Graph modelliert **Zielzustände**, nicht **Ausführungsreihenfolge** von Arbeitspaketen; Verfeinerung über Micro-Gates würde den Zielzustands-Designer entwerten
**Bezug:** `ADP_PO_Product_Layering_and_IA_Reset_v0.1.md`, `ADP_Roadmap_Graph_and_Gate_Checklist_v0.1.md`, `ADP_Recursive_Containers_and_Execution_Granularity_v0.1.md`, `ADP_Archetype_and_Method_Catalog_v0.2.md`, `Kairo_Plan_Mode_Design_v0.1.md`, `Kairo_Method_Design_Principles_v0.1.md`
---
## Problem
Für einen **Durchführungsplan** (Projektplan) müssen Arbeitspakete in eine **sinnvolle Reihenfolge** gebracht werden — inklusive Abhängigkeiten wie „Foundation vor Feature“, „AP1.4 vor AP1.7“. Heute existiert dafür praktisch nur der **Zielzustands-Designer** (Gate-Graph auf `RoadmapItem`).
Das führt zu zwei Fehlmustern:
| Fehlmuster | Folge |
|------------|--------|
| **Gates als Micro-APs** | Unendlich viele Detail-Gates; Plan und Ist vermischen sich |
| **Flache Action-Listen** | Keine enforced Abhängigkeiten; Reihenfolge nur in Köpfen/Docs |
Gleichzeitig gilt produktlich:
- **Nicht alles vorab ausplanen** — je nach Fortschritt wird die **nächste Planungsebene** erst relevant (rollende Planung).
- **Granularität steigt** mit Tiefe: Vorhaben → Phase/Stream → Arbeitspaket/Sprint → ToDos — optional auch **zeitlich** (Monat/Woche/Tag) statt nur strukturell.
- **Archetyp bestimmt**, welche Ebenen im **Basisstrukturplan** vorgesehen sind und ob fehlende Planung **automatisch** (Structure Builder) oder als **explizites ToDo** (Planning Action) ausgelöst wird.
**Konkrete Lücke (Ist):**
| Fähigkeit | Gate-Graph | Execution-Plan |
|-----------|------------|----------------|
| Kanten zwischen Plan-Knoten | ✓ `roadmap_item_dependencies` | ✗ |
| `ready` / `blocked` Read Model | ✓ Gates | ✗ Actions |
| Designer-UI | ✓ AP1.15 | ✗ |
| `sort_order` | ✓ Gates, Projects | ✗ Actions |
| Abhängigkeit AP → AP | ✗ | ✗ |
---
## Betroffene Regeln
| Dokument | Regel |
|----------|--------|
| Vision v0.2 §57 | Plan (Roadmap/Gates) vs. Ist (Action/Task) — getrennt halten |
| ADP PO Layering | „Plan zwischen Gates“ = Project → Action → Task; **getrennt** vom Gate-Graph |
| ADP Roadmap Graph | Graph = **Zielzustands-Modell**, kein Workflow, keine Ausführungs-Engine |
| Method Design §14.4 | `wbs_driven`: Work Package Items + **Dependencies** — Konzept, nicht implementiert |
| Recursive Containers | Project-Baum + Task-Baum — **Organisation**, nicht automatisch Abhängigkeitsgraph |
| Archetype Catalog §3 | Archetyp liefert Default-Methode und Plan-Ebenen — heute ohne Execution-Graph |
---
## Leitentscheidung (Empfehlung)
### 1. Drei getrennte Planungsdimensionen
```text
┌─────────────────────────────────────────────────────────────────────────┐
│ A. ZIEL-HORIZONT (Plan, grob, optional) │
│ RoadmapItem: milestone, review_gate, maturity_stage, phase, … │
│ Kanten: Gate-Graph — „Foundation ✓ vor IA ✓ vor Release …“ │
│ UI: Zielzustands-Designer, Kontrolle, Journey │
├─────────────────────────────────────────────────────────────────────────┤
│ B. STRUKTUR-HORIZONT (Organisation, optional) │
│ Project-Baum (parent_project_id), container_kind: phase/stream/… │
│ Sequenz zwischen Projects: sort_order + optional project_dependencies │
│ UI: Plan-Outline „Struktur“ │
├─────────────────────────────────────────────────────────────────────────┤
│ C. DURCHFÜHRUNGSPLAN (Ist-Plan, rollend verfeinert) │
│ Action (Arbeitspaket) + Task (ToDo) │
│ Kanten: action_dependencies — „AP X requires AP Y“ │
│ Zeitbox: work_cycle (Sprint/Woche) — Priorisierung, kein Ersatz für C │
│ UI: Plan-Outline „Arbeit“, Ausführen, Steering Surface │
└─────────────────────────────────────────────────────────────────────────┘
```
**PO-Regel:** Dimension **A** beantwortet *Was muss erreicht sein?***C** beantwortet *In welcher Reihenfolge committen wir Arbeit?***B** beantwortet *Wo im Programm hängt die Arbeit?*
Der Gate-Designer bleibt **ausschließlich** für **A** (und ggf. grobe `feature`-Knoten als Zwischenziele, nicht als AP-Ersatz).
### 2. Progressive Planungsgranularität („Rollende Planung“)
Planung ist **kein einmaliger Vollplan**, sondern eine **Kette von Planungsschritten**, die ausgelöst werden, wenn der aktuelle Horizont **aktiv** oder **erreicht** ist und die nächste Ebene noch **leer** ist.
```text
Ebene 0 Initiative — Ziel benennen, Methode wählen
▼ [on_method_selected]
Ebene 1 Ziel-Horizont — Gates / Phasen grob (015 Knoten)
│ (optional, archetypabhängig)
▼ [on_roadmap_required] — wenn Gate aktiv & Unterplan fehlt
Ebene 2 Struktur — Phase / Stream / Project-Baum
│ (optional)
▼ [on_structure_required]
Ebene 3 Durchführung — Arbeitspakete + Abhängigkeiten
│ (Action-Graph unter aktivem Gate/Project)
▼ [on_plan_required | on_wbs_required | on_dependency_analysis_required]
Ebene 4 Zeitbox — Sprint / Woche / Iteration (work_cycle)
│ (optional, B3)
▼ [cycle_structure_builder | on_replan_required]
Ebene 5 Ausführung — Tasks / ToDos unter Action
│ (AP1.5d)
▼ [bei Bedarf: explizites „Zerlegen“-ToDo oder Agent-Step]
```
**Horizont-Regel:** Unter einem **aktiven Gate G** sind nur Actions sinnvoll planbar, die **auf G einzahlen** (`action.roadmap_item_id`) oder einem **Project unter G** zugeordnet sind. Der Execution-Graph gilt **pro Horizont-Scope** (Initiative, Gate oder Project), nicht global über alle Gates.
**Nicht alles vorplanen:** Ebenen 35 dürfen für spätere Gates **bewusst leer** bleiben. Steuerung erzeugt dann **Planning Debt** (Attention), nicht stille Lücken.
### 3. Zeitgesteuerte vs. strukturgetriebene Ebenen
| Modus | Primäre Achse | Typische Archetypen | Beispiel-Ebenen |
|-------|---------------|---------------------|-----------------|
| **Strukturgetrieben** | Project → WP → Task | program, product, linear_project | Phase → AP → ToDo |
| **Zeitgetrieben** | work_cycle / Recurring | recurring_program, agile_iteration | Monat → Woche → Tag |
| **Hybrid** | Gates + Sprint | product + B3 | Release-Gate + Sprint-Backlog |
| **Minimal** | flach | generic | Vorhaben → Action/Task |
Zeitplanung nutzt **`RoadmapItem(type=work_cycle)`** und **`actions.work_cycle_id`** (AP2.0f) — **kein** zweites Sprint-OM. Persönliche Monats-/Wochenpläne = `work_cycle` mit `cycle_kind` im Profil (Tag/Woche/Monat) oder `RecurringElement` für Routinen.
---
## Planungsschritte: automatisch vs. explizites ToDo
Ein **Planungsschritt** ist **Meta-Arbeit**: Struktur anlegen, WPs schätzen, Abhängigkeiten klären — **nicht** die fachliche Ausführung selbst.
### Auslösung
| Mechanismus | Wann | Ergebnis |
|-------------|------|----------|
| **Structure Builder** (automatisch) | Hook `on_*_required` + Profil `planning_mode=auto` | Seed-Struktur (Projects, Gates, initiale Actions) |
| **Planning Action** (explizit) | Profil `planning_mode=explicit` | Committetes AP z. B. „Phase 2 detaillieren“, „Sprint 12 planen“ |
| **Signal / Attention** | Horizont erreicht, nächste Ebene leer | „Planung für G5 ausstehend“ — kein stiller Zustand |
| **Skip** | Profil `planning_mode=skip` für Ebene | Ebene entfällt (z. B. generic: kein Gate-Graph) |
### Hook-Zuordnung (bestehend, hier semantisch geschärft)
| Hook | Planungsebene | Typisches Deliverable |
|------|---------------|------------------------|
| `on_structure_required` | 2 Struktur | Project-Baum, container_kind |
| `on_roadmap_required` | 1 Ziel-Horizont | Gate-Graph grob |
| `on_plan_required` | 3 Durchführung | AP-Liste + Reihenfolge |
| `on_wbs_required` | 3 Durchführung (WBS) | WP-Hierarchie + Dependencies |
| `on_dependency_analysis_required` | 3 | Kanten validieren, kritischer Pfad |
| `on_replan_required` | 4+ | Sprint-/Zyklus-Neuplanung |
| `on_dod_definition_required` | 1 | Gate-Kriterien |
**PO-Regel:** Structure Builder **dürfen** initiale Actions vorschlagen, **dürfen aber nicht** ungeprüft produktive APs committen, wenn ein Gate mit Verify vorgesehen ist (Method Design §8).
### Planning Action — Kennzeichnung
MVP ohne neue Tabelle: Action mit **`action_kind`** (Registry/Enum, additiv):
| `action_kind` | Bedeutung |
|---------------|-----------|
| `delivery` | Default — fachliches Arbeitspaket |
| `planning` | Meta — nächste Planungsebene ausfüllen |
| `review` | Meta — Review/Retrospektive (optional, später) |
Zusätzlich optional: `planning_target_level` (int 15) und `planning_scope_id` (UUID Gate/Project) — rein deklarativ für UI und Next-Action.
---
## Archetyp — welche Ebenen, welcher Modus
Referenz-Matrix (PO-Vorschlag; Feinjustierung pro Method Profile):
| Archetyp | Ebenen im Basisplan | Gate-Graph | Project-Baum | Action-Deps | Zeitbox | Planungsschritte |
|----------|---------------------|------------|--------------|-------------|---------|------------------|
| `generic` | 0 → 5 (minimal) | optional | optional | soft (sort_order) | — | meist **skip** bis Action |
| `linear_project` (A2) | 1 → 3 → 5 | grob (Phasen) | optional flach | **hard** (Graph) | — | **explicit** WP-Planung |
| `program` (B2a) | 1 → 2 → 3 → 5 | **ja** | **ja** | **hard** | optional | Mix auto/explicit |
| `product` (B2b) | 1 → 3 → 4 → 5 | **ja** (Orientierung) | optional | **hard** (Foundation) | Sprint (B3) | explicit für Sprint |
| `recurring_program` (A3) | 0 → 4 → 5 | selten | Domains | soft | **Woche/Monat** | auto Zyklen |
| `maturity_journey` (A1) | 1 → 2 → 5 | Stufen-Graph | pro Fähigkeit | Stufen-Kanten | — | auto Pfad + explicit Übung |
| `content_project` (D1) | 1 → 3 → 5 | Kapitel-Gates | Lanes | Kapitel-Reihenfolge | — | explicit pro Kapitel |
**Kairo-Jinkendo (`product.kairo_dev`):** Ebenen 1 (G1G8 grob) + 3 (AP-Graph unter aktivem Gate) + 4 (optional Sprint) — **nicht** jedes Foundation-AP als Gate.
---
## Zielmodell Execution-Graph (Dimension C)
### Semantik
| Relation | Bedeutung | Verify? |
|----------|-----------|---------|
| `requires` | Nachfolger startet erst, wenn Vorgänger **done** (oder waived per Decision) | nein |
| `blocks` | Vorgänger offen → Nachfolger **blocked** (Attention) | nein |
| `relates` | Weiche Abhängigkeit — nur Anzeige, kein Block | nein |
**Abgrenzung Gate-Graph:**
| | Gate-Graph | Execution-Graph |
|---|------------|-----------------|
| Knoten | `RoadmapItem` | `Action` (später optional `Task`) |
| Status | planned/active/reached/… | open/in_progress/done/cancelled |
| Zweck | Quality / Zielzustand | Ausführungsreihenfolge |
| Verify | Kriterien + Evidence | Completion der Action |
| Granularität | grob (515) | fein (10100+) |
### Schema (additiv, Scope Lock Erweiterung)
**Tabelle `action_dependencies`** (empfohlen):
| Feld | Typ | Beschreibung |
|------|-----|--------------|
| id | uuid | |
| tenant_id | uuid | |
| initiative_id | uuid | Denormalisiert für Scope-Queries |
| predecessor_action_id | uuid | FK actions |
| successor_action_id | uuid | FK actions |
| dependency_kind | enum | `requires`, `blocks`, `relates` |
| created_at, created_by_actor_id | | Audit |
Constraints: kein Selbstbezug; Zyklen verboten (gleiche Engine-Pattern wie Gate-Graph); beide Actions **gleicher Initiative**; optional gleicher `roadmap_item_id`-Horizont.
**Ergänzung `actions`:**
| Feld | Typ | Beschreibung |
|------|-----|--------------|
| sort_order | int | Manuelle Reihenfolge innerhalb Project/Gate (Fallback ohne Kanten) |
| action_kind | enum | `delivery`, `planning`, `review` (Default delivery) |
**Optional später (nicht MVP):** `project_dependencies` analog für Struktur-Ebene B; `RoadmapItem(type=feature)` als Plan-Knoten zwischen Gate und AP (Zuordnung Action → feature).
### Engine & Read Models
Neues Modul **`backend/steering/graph/execution_engine.py`** (Analog `roadmap_engine.py`):
| Read Model | Bedeutung |
|------------|-----------|
| `ready_actions(scope)` | Keine offenen `requires`-Vorgänger |
| `blocked_actions(scope)` | Mindestens ein Vorgänger nicht done |
| `critical_path(scope)` | Längste Kette — für `sequential_dependency` |
| `planning_debt(scope)` | Aktiver Horizont ohne Actions oder ohne Planungs-AP |
Exposure: Steering-Snapshot, Plan-Outline API, Next-Action-Strategien (AP2.0d).
**Scope Lock:** Execution Engine steuert **nicht** Lifecycle-Übergänge automatisch — nur Read Models + Attention. (Gleiche Leitlinie wie Gate-Graph ≠ Workflow.)
---
## UI & IA
| Ort | Inhalt | Nicht |
|-----|--------|-------|
| **Plan-Outline „Arbeit“** | AP-Baum, Vorgänger/Kanten, `ready/blocked`, Planning Actions | Gate-Topologie |
| **Zielzustands-Designer** | Gate-Graph, Joins, Kriterien | AP-Abhängigkeiten |
| **Steering Surface / Ausführen** | 13 **ready** Next Actions im aktiven Horizont | Vollständige Roadmap |
| **Kontrolle Plan/Ist** | Gate-Diff (AP1.14) + optional „Execution vs. Plan“ (AP geplant vs. done) | — |
**Planning Debt** in Attention: z. B. „Gate G5 aktiv — Durchführungsplan leer — Planungsschritt ausstehend“.
---
## Optionen (Entscheidung)
| Option | Kurz | Pro | Contra |
|--------|------|-----|--------|
| **A — Nur sort_order auf Actions** | Liste + manuelles DnD | Minimal, schnell | Keine echten Deps, kein blocked |
| **B — Execution-Graph auf Actions** (Empfehlung) | `action_dependencies` + Engine | Foundation-Deps, ready/blocked, Archetyp A2/B2 | Schema + AP; UI-Kanten |
| **C — Alles über Gate-Graph** | Jedes AP = Gate | Ein Graph | Semantik-Bruch, Explosion, Verify-Unsinn |
| **D — Nur WBS-RoadmapItems** | WP als RoadmapItem(type=feature) | Plan/Ist an einem Ort | Schwere Pflege; Ist-Status an Plan-Knoten |
| **E — Hybrid B + D** | Gates grob + feature optional + Action-Graph | Skaliert für Mega-Programme | Zwei Plan-Knotentypen + Actions |
### Empfehlung
**Option E (stufenweise):**
1. **MVP-Slice:** **B**`action_dependencies`, `sort_order`, `action_kind`, Execution Engine, Plan-Outline-Kantenansicht, Planning Debt Signal.
2. **Post-MVP:** **D** selektiv — `feature`-RoadmapItems als Zwischenziele unter Gates, wenn Programm > ~30 APs.
3. **Nicht:** **C**.
---
## Implementierungspakete (Vorschlag)
| Paket | Inhalt | Abhängigkeit |
|-------|--------|--------------|
| **AP1.16a** | Migration `action_dependencies`, `actions.sort_order`, `action_kind` | AP1.5 |
| **AP1.16b** | `execution_engine`, API, pytest | AP1.16a |
| **AP1.16c** | Plan-Outline: Kanten + blocked/ready; Planning Action UX | AP1.12, AP1.16b |
| **AP1.16d** | Method Profile: `planning_levels`, `planning_mode` pro Ebene; Planning Debt in Attention | AP2.0a |
| **AP2.0f** | `work_cycle` + `actions.work_cycle_id` — Zeitbox-Ebene | unverändert |
| **AP2.0d** | Next-Action-Strategien nutzen `ready_actions` | AP1.16b |
**Parallel dokumentieren:** `Kairo_Plan_Mode_Design_v0.1.md` § „Arbeit“ um Execution-Graph ergänzen; Truth Table Zeile Action-Dependencies.
---
## Risiko
| Risiko | Mitigation |
|--------|------------|
| Zwei Graphen verwirren Nutzer | Klare IA: Designer = Ziele, Outline = Durchführung; Begriffe in UI |
| Graph-Pflege-Overhead | Rollende Planung — nur aktiver Horizont; sort_order als Fallback |
| Planning Actions als Ballast | Nur bei `planning_mode=explicit`; auto-Profile nutzen Builder |
| Scope Creep WBS | MVP = Action-Kanten; feature-Knoten später |
| Agent erzeugt ungültige Deps | Cycle-Detection API; Audit |
---
## Rückbaubarkeit
- `action_dependencies` optional — leere Tabelle, sort_order allein reicht für flache Vorhaben.
- `action_kind` Default `delivery` — bestehende Actions unverändert.
- Execution Engine isoliert in `backend/steering/` — abschaltbar per Method Profile.
- Gate-Graph unberührt.
---
## Auswirkung auf Sprint / Scope Lock
- **Erweitert Scope Lock** für **eine** neue OM-Relation (`action_dependencies`) + additive Action-Felder — analog AP1.4 Gate-Dependencies.
- **Kein** Ersatz für AP2.0f (`work_cycle`) oder Structure Builder — ergänzt die Ist-Schicht.
- **Blockiert nicht** Dogfooding R1: bis AP1.16 manuelle Gate-Zuordnung + Docs-Reihenfolge; danach AP-Graph im Seed/API.
---
## Freigabe-Checkliste PO
- [x] Drei Planungsdimensionen (Ziel / Struktur / Durchführung) akzeptiert
- [x] Progressive Planung: nicht alles vorab, Planning Debt sichtbar
- [x] Planungsschritte: auto (Builder) vs. explicit (Planning Action) vs. skip — archetypgesteuert
- [x] Gate-Designer **nicht** für AP-Abhängigkeiten
- [x] MVP = Action-Execution-Graph (Option B/E Phase 1)
- [x] Zeitplanung über `work_cycle` / Recurring, nicht über Gate-Microplanung
- [x] AP1.16ad in Roadmap v0.2 aufnehmen
---
## Referenz — Kairo-Jinkendo (Dogfooding)
```text
ZIEL-HORIZONT (A) — grob, 8 Gates
G1 Foundation ──requires──► G2 IA ──requires──► G3 Gates ── …
Unter G5 „Plan/Ist & Cockpit“ (aktiv):
DURCHFÜHRUNGSPLAN (C)
AP1.14 Plan-Snapshot ──requires──► AP1.9c Cockpit
AP1.9c ──requires──► AP2.0d Next-Action
AP2.0d ──requires──► AP1.7 Operational API
ZEITBOX (D) optional
work_cycle „Iteration R2“ → 13 ready Actions
PLANUNGSSCHRITT (explicit)
Action(kind=planning): „G6 Durchführungsplan detaillieren“ — erst wenn G5 reached
```
---
*Siehe auch: `ADP_Roadmap_Graph_and_Gate_Checklist_v0.1.md`, `Sprint1_AP1_5_Hierarchy_Assignment_v0.1.md`, `Kairo_Corrected_MVP_Roadmap_v0.2.md`*

View File

@ -2,7 +2,7 @@
## Corrected MVP Roadmap v0.2
**Status:** führende Produkt-Roadmap (AP-Historie + nächste Schritte)
**Stand:** 2026-07-11 (Review + DOC-Sync)
**Stand:** 2026-07-12 (Execution-Plan ADP + AP1.16)
**Ersetzt:** `Kairo_Corrected_MVP_Roadmap_v0.1.md`
**Vision:** `Kairo_Vision_and_Product_Direction_v0.2.md`
**MVP-Nordstern:** `Kairo_MVP_Definition_v0.3.md` (führend für Abnahfe)
@ -92,6 +92,25 @@ Siehe `Kairo_Plan_Mode_Design_v0.1.md`, `ADP_AP1_10_Initiative_Archetypes_and_En
---
## 1.5 PO-Entscheidung 2026-07-12 — Durchführungsplan & Progressive Planung
**Auslöser:** Dogfooding — AP-Reihenfolge (Foundation vor Features) nicht über Gate-Graph abbildbar; Micro-Gates würden Zielzustands-Designer entwerten.
| Entscheidung | |
|--------------|--|
| **Drei Planungsdimensionen** | Ziel-Horizont (Gate-Graph) ∥ Struktur (Project-Baum) ∥ **Durchführungsplan** (Action-Graph) |
| **Rollende Planung** | Nicht alles vorab — nächste Ebene planen, wenn Horizont aktiv und Unterplan leer |
| **Planungsschritte** | Archetyp: auto (Structure Builder) / explicit (`action_kind=planning`) / skip |
| **Gate-Designer** | **nur** Zielzustände — **keine** AP-Abhängigkeiten |
| **MVP-Slice** | `action_dependencies` + Execution Engine + Plan-Outline-Kanten (AP1.16) |
| **Zeitbox** | weiter über `work_cycle` (AP2.0f), nicht Gate-Microplanung |
Siehe `ADP_Execution_Plan_and_Work_Package_Dependencies_v0.1.md`, `Kairo_Plan_Mode_Design_v0.1.md` §67.
**Reihenfolge Code:** AP1.16ab **vor** AP2.0d (Strategien nutzen `ready_actions`); AP1.16cd parallel zu AP2.0a.
---
## 2. Phasenübersicht
```text
@ -109,6 +128,7 @@ Phase H2 PM Work Modes AP1.9 ✓ 9a / → 9b9e
Phase H3 Plan Outline AP1.12 + AP1.10 ◐ 12ad, 10c
Phase I Gate-Graph AP1.13 + AP1.15 ◐ 13a/b, 15ac
Phase I2 Plan/Ist Snapshots AP1.14 ✓
Phase I3 Execution-Plan AP1.16 ◐ 16ab Code, 16cd offen
Phase J Portfolio AP1.8 ◐ 8a ✓ / 8b deferred
Phase K Archetyp-Steuerung AP2.0 ◐ 2.0ac ✓ / → 2.0df
Phase L Agent Interface AP1.7 ○
@ -116,7 +136,7 @@ Phase M Validation AP0.10d + Dogfooding ← **NÄCHSTES (PO 2026-07-11)
Phase N Integration Gitea/MCP nach AP1.7
```
**Frontend-Version:** `0.18.0-ap2.0` · **Schema:** Migration 022
**Frontend-Version:** `0.18.0-ap2.0` · **Schema:** Migration 023 (AP1.16a)
---
@ -161,12 +181,14 @@ Siehe **`Kairo_Status_Review_and_Next_Steps_v0.1.md` §5** für vollständige Ro
| D0 | DOC-Sync (Truth Table, Gap, Review) | ✓ | eine Wahrheit |
| D1 | Dogfooding R1 — Kairo-Jinkendo in Kairo | **→ nächstes** | B2b-Validation |
| 1 | AP1.9c Cockpit-Signale | offen | MVP §5.7 |
| 2 | AP2.0d Next-Action-Strategien | offen | MVP Stufe A |
| 3 | Dogfooding R2 — Ist + Gitea-Evidence | offen | Fortschritt sichtbar |
| 4 | AP0.10d / AP2.1 Validation B2b | offen | MVP-Urteil |
| 5 | AP2.0f work_cycle (B3 minimal) | offen | Sprint-Zeitbox |
| 6 | AP1.7 Operational API | offen | MCP-Voraussetzung |
| 7 | Gitea-Webhook + MCP | deferred | Schicht 4 |
| 2 | AP1.16ab Execution-Graph (Schema + Engine) | **◐ Code** | **vor** AP2.0d; Remote-Verifikation nach Deploy |
| 3 | AP2.0d Next-Action-Strategien | offen | MVP Stufe A; nutzt `ready_actions` |
| 4 | AP1.16cd Plan-Outline-Kanten + Planning Debt | offen | nach 16ab |
| 5 | Dogfooding R2 — Ist + Gitea-Evidence | offen | Fortschritt sichtbar |
| 6 | AP0.10d / AP2.1 Validation B2b | offen | MVP-Urteil |
| 7 | AP2.0f work_cycle (B3 minimal) | offen | Sprint-Zeitbox |
| 8 | AP1.7 Operational API | offen | MCP-Voraussetzung |
| 9 | Gitea-Webhook + MCP | deferred | Schicht 4 |
**Erledigt (Review-Queue):** AP1.6b ✓
@ -275,6 +297,28 @@ Graph-UI für Gate-Abhängigkeiten (Mitai-**Pattern**, Kairo Zielzustands-Semant
---
### AP1.16 — Durchführungsplan & Work-Package-Abhängigkeiten ← **NÄCHSTES (nach 9c, vor 2.0d)**
**Ziel:** Ausführungsreihenfolge von Arbeitspaketen modellieren — **getrennt** vom Gate-Graph; rollende Planungsgranularität.
| Teil | Scope |
|------|--------|
| **16a** | Migration `action_dependencies`; `actions.sort_order`, `action_kind` |
| **16b** | `backend/steering/graph/execution_engine.py`, API, pytest (`ready`/`blocked`/critical path) |
| **16c** | Plan-Outline „Arbeit“: Vorgänger-Kanten, blocked/ready; **nicht** im Zielzustands-Designer |
| **16d** | Method Profile: `planning_levels`, `planning_mode`; Planning Debt in Attention |
**ADP:** `ADP_Execution_Plan_and_Work_Package_Dependencies_v0.1.md` (PO 2026-07-12)
**Assignment:** `Sprint1_AP1_16_Execution_Plan_v0.1.md`
**Design:** `Kairo_Plan_Mode_Design_v0.1.md` §67
**Abhängigkeit:** AP1.5, AP1.12d; **16b vor AP2.0d**
**Version (Ziel):** `0.19.0-ap1.16`
**Stand Code:** 16ab implementiert (Migration 023), 16cd offen
**Explizit nicht:** AP-Abhängigkeiten als Micro-Gates; Lifecycle aus Execution-Graph ableiten.
---
### AP1.5d — Rekursive Tasks & Roll-up
**Ziel:** Task-Baum (`parent_task_id`); Mensch UI max. ~3 Ebenen; Agent tiefer.
@ -419,10 +463,11 @@ MVP-nah wenn:
0.16.x ✓ AP1.9a, AP1.12, AP1.13 (teilweise)
0.17.x ✓ AP1.14, AP1.15 (teilweise)
0.18.0-ap2.0 ✓ Archetyp-Slice AP2.0
0.18.x → AP1.9c, Dogfooding, AP2.0d — NÄCHSTES
0.19.x → AP2.0f work_cycle, AP2.1 Validation
0.18.x → AP1.9c, Dogfooding R1 — NÄCHSTES
0.19.0-ap1.16 → Execution-Graph (Schema, Engine, Outline-Kanten)
0.19.x → AP2.0d Strategien, AP2.0f work_cycle, AP2.1 Validation
0.20.0-ap1.7 → Operational Actor Interface
```
---
*Siehe auch: `ADP_Recursive_Containers_and_Execution_Granularity_v0.1.md`, `ADP_PO_Product_Layering_and_IA_Reset_v0.1.md`, `Sprint1_AP1_5_Hierarchy_Assignment_v0.1.md`, `Kairo_MVP_Usability_Recovery_Plan_v0.2.md`*
*Siehe auch: `ADP_Execution_Plan_and_Work_Package_Dependencies_v0.1.md`, `ADP_Recursive_Containers_and_Execution_Granularity_v0.1.md`, `ADP_PO_Product_Layering_and_IA_Reset_v0.1.md`, `Sprint1_AP1_5_Hierarchy_Assignment_v0.1.md`, `Kairo_MVP_Usability_Recovery_Plan_v0.2.md`*

View File

@ -1,7 +1,7 @@
# Kairo — Implementation Truth Table v0.1
**Status:** living document — bei jedem AP aktualisieren
**Stand:** 2026-07-11 (Review + DOC-Sync; Code `develop` @ `b85a6ec`, Frontend `0.18.0-ap2.0`)
**Stand:** 2026-07-12 (Execution-Plan ADP, AP1.16 in Roadmap)
**Zweck:** Ehrliche Trennung von **implementiert**, **teilweise**, **nur API/Schema**, **nur Dokumentiert**
Verhindert, dass Zielbild-Dokumente als Ist-Stand gelesen werden.
@ -32,7 +32,7 @@ Verhindert, dass Zielbild-Dokumente als Ist-Stand gelesen werden.
| Capabilities | ✓ | Keine Objekt-Sichtbarkeit |
| Auth / Session | ✓ | |
| Audit (Auth/Admin) | ◐ | Nicht für alle OM-Events |
| Migrationen nummeriert | ✓ | Schema bis **022** (`portfolio_rank`) |
| Migrationen nummeriert | ✓ | Schema bis **023** (`action_execution_plan`) |
---
@ -42,8 +42,11 @@ Verhindert, dass Zielbild-Dokumente als Ist-Stand gelesen werden.
|---------|-------|-----------|
| Initiative | ✓ | inkl. `archetype_key`, `portfolio_rank` |
| Project | ◐ | AP1.5 + AP1.5c: Baum, Blatt-Regel; `archetype_key` Mirror AP2.0b |
| Action | ✓ | Gate-Zuordnung AP1.6, `due_at` |
| Action | ✓ | Gate-Zuordnung AP1.6, `due_at`; `sort_order` / Deps AP1.16 geplant |
| ActionAssignment | ✓ | |
| Action Dependencies (Execution-Graph) | ◐ | Migration 023, Engine AP1.16b; GUI AP1.16c offen |
| `action_kind` (planning/delivery) | ◐ | Migration 023, API AP1.16a |
| `actions.sort_order` | ◐ | Migration 023 |
| BacklogItem | ✓ | Gate-Zuordnung AP1.6, `sort_order` |
| Blocker | ✓ | `action_id` optional |
| Milestone | ◐ | Legacy-Tabelle MVP-Brücke; RoadmapItem bevorzugt |
@ -54,13 +57,15 @@ Verhindert, dass Zielbild-Dokumente als Ist-Stand gelesen werden.
| Task (unter Action) | ◐ | AP1.5d: `parent_task_id`, Baum-API, Outline; Roll-up-UI ◐ |
| Roadmap | ◐ | 1 pro Initiative; Migration 010 |
| RoadmapItem | ◐ | CRUD + Detail; Verify AP1.4b |
| RoadmapItem Dependencies | ◐ | requires/blocks/related; Graph AP1.4d |
| RoadmapItem Dependencies | ◐ | requires/blocks/related; Graph AP1.4d **nur Gates** |
| RoadmapItem Criteria | ◐ | Checkliste, waive/defer, Reopen AP1.4b/c |
| Plan-Ist-Verknüpfung | ◐ | Links, Kriterien, Journey AP1.6; Snapshots AP1.14 |
| Plan-Snapshots | ◐ | Migration 021, Diff-View Kontrolle AP1.14 |
| Entity Field System | ◐ | Migration 017, Seeds AP1.10b/AP2.0b |
| Initiative Archetypes | ◐ | Migration 016, Registry AP2.0b |
| Method Profiles (Code-Seeds) | ◐ | `product.kairo_dev`, Kumite, Buch — AP2.0b |
| Execution Graph Engine | ◐ | AP1.16b; API `/execution/graph-state` |
| Planning Debt (Attention) | ◐ | Read Model AP1.16b; Cockpit AP1.16d offen |
| `work_cycle` / Sprint | ✗ | 📄 MVP v0.3 B3; Migration AP2.0f geplant |
---
@ -109,7 +114,8 @@ Verhindert, dass Zielbild-Dokumente als Ist-Stand gelesen werden.
| Gate Map (read-only) | ✓ | AP1.13a |
| Gate Dependencies pflegen | ◐ | AP1.13b Detail |
| Zielzustands-Designer | ◐ | AP1.15ac; OR deferred |
| Plan-Outline | ◐ | AP1.12ad: Baum, Modal, Reorder, Actions |
| Plan-Outline | ◐ | AP1.12ad: Baum, Modal, Reorder, Actions; AP-Kanten AP1.16c offen |
| Execution-Plan (AP-Graph) | ✗ | 📄 AP1.16; Outline „Arbeit“, nicht Gate-Designer |
| Profil-Modal (Archetyp/EFS) | ◐ | AP1.10c |
| Modal-Bearbeitung (Gates/Profil) | ◐ | viele Sektionen noch Inline-CRUD |
| Admin-UI | ✗ | |
@ -123,7 +129,8 @@ Verhindert, dass Zielbild-Dokumente als Ist-Stand gelesen werden.
|---------------------------|-----|
| DoD / prüfbare Kriterien | ◐ | `roadmap_item_criteria`; UI AP1.4c |
| Zieltermin | ◐ | `target_date` |
| Abhängigkeiten | ◐ | API + Graph Engine AP1.4d; Designer AP1.15 |
| Abhängigkeiten (Gates) | ◐ | API + Graph Engine AP1.4d; Designer AP1.15 |
| Abhängigkeiten (Actions) | ✗ | 📄 AP1.16; Execution Engine |
| parallel vs. sequenziell | ◐ | Kanten + `parallel_group`; `sequencing_mode` deprecated |
| Plan-Graph vs. Ist-Overlay | ◐ | Designer AP1.15; Journey Ist-Graph AP1.6b; Diff AP1.14 |
| Zielzustands-Designer (Joint/Parallel) | ◐ | AP1.15ac; Join/OR AP1.15d deferred |
@ -195,6 +202,8 @@ Details: `Kairo_Status_Review_and_Next_Steps_v0.1.md` §2.1
| AP | Erwartung |
|----|-----------|
| AP1.16ab | Action deps, Execution Engine ✗→◐ (Deploy) |
| AP1.16cd | Outline-Kanten, Planning Debt ✗→◐ |
| AP1.9c | Cockpit-Signale ◐→✓ |
| AP2.0d | Strategien ○→◐ |
| Dogfooding R1 | Referenz-Vorhaben in Alltag ◐ |

View File

@ -1,8 +1,8 @@
# Kairo — Plan-Modus Design v0.1
**Status:** PO-Arbeitsentwurf (Entscheidung ausstehend)
**Stand:** 2026-07-10
**Bezug:** `Kairo_PM_Frontend_UI_Concept_v0.1.md` §4.3, `ADP_AP1_10_Initiative_Archetypes_and_Entity_Field_System_v0.1.md`
**Status:** PO-Arbeitsentwurf (Execution-Plan §67 PO 2026-07-12)
**Stand:** 2026-07-12
**Bezug:** `Kairo_PM_Frontend_UI_Concept_v0.1.md` §4.3, `ADP_AP1_10_Initiative_Archetypes_and_Entity_Field_System_v0.1.md`, `ADP_Execution_Plan_and_Work_Package_Dependencies_v0.1.md`
**Auslöser:** Planen soll von Tab-Fragmenten zu durchgängiger Programmgestaltung werden — bis Struktur, Gates, Eingang, Arbeitspakete und Tasks
---
@ -196,22 +196,78 @@ Baut auf AP1.4d (Graph Engine read models) auf — UI AP1.13 **nach** 4d oder pa
---
## 6. Ebenen bis Workpackage & Task
## 6. Progressive Planung — aufsteigende Granularität
Planen ist **kein einmaliger Vollplan**. Je nach Archetyp existieren unterschiedlich viele Ebenen; die **nächste Ebene** wird erst relevant, wenn der aktuelle Horizont **aktiv** ist und der Unterplan noch **leer** ist (rollende Planung).
### 6.1 Drei Planungsdimensionen (verbindlich)
| Dimension | Frage | OM / UI |
|-----------|--------|---------|
| **A — Ziel-Horizont** | Was muss erreicht sein? | `RoadmapItem`, Gate-Graph — §5, Zielzustands-Designer |
| **B — Struktur** | Wo hängt die Arbeit? | Project-Baum — Outline „Struktur“ |
| **C — Durchführung** | In welcher Reihenfolge committen wir APs? | `Action` + `action_dependencies` — Outline „Arbeit“ §6.3 |
| **D — Zeitbox** (optional) | Was in dieser Iteration/Woche? | `work_cycle` (AP2.0f), Recurring — nicht Gate-Microplan |
**PO-Regel:** Dimension **A****C**. AP-Abhängigkeiten (z. B. „Foundation vor Feature“) gehören in **C**, nicht in den Gate-Designer.
### 6.2 Planungsebenen (Referenzleiter)
```text
Ebene 0 Initiative / Vorhaben — Methode, Archetyp, Profil
Ebene 1 Ziel-Horizont — Gates, Phasen grob (optional)
Ebene 2 Struktur — Phase / Stream / Project-Baum (optional)
Ebene 3 Durchführungsplan — Arbeitspakete + Kanten (rollend unter aktivem Gate)
Ebene 4 Zeitbox — Sprint / Woche / Monat (optional, archetypabhängig)
Ebene 5 Ausführung — Tasks / ToDos unter Action
```
**Minimal-Vorhaben** (`generic_operating`): oft nur Ebene 0 → 3 → 5 (Vorhaben → Actions → Tasks).
**Product-Programm** (Kairo-Jinkendo): 1 (G1G8 grob) + 3 + optional 4 + 5.
**Persönliches Rhythmus-Vorhaben**: eher Ebene 4 zeitgetrieben (Woche/Monat) statt WBS-Tiefe.
### 6.3 Planungsschritte — auto vs. explizites ToDo
Meta-Arbeit (Struktur anlegen, WPs schätzen, Deps klären) wird archetypgesteuert ausgelöst:
| Modus | Mechanismus | Beispiel |
|-------|-------------|----------|
| **auto** | Structure Builder + Hook (`on_plan_required`, `on_wbs_required`) | Kumite-Pfad seeden |
| **explicit** | Committetes AP mit `action_kind=planning` | „G6 Durchführungsplan detaillieren“ |
| **skip** | Ebene entfällt | Generic ohne Gate-Graph |
**Planning Debt:** Wenn Gate aktiv und Durchführungsplan leer → Attention (AP1.16d), kein stiller Leerzustand.
### 6.4 Outline-Knoten „Arbeit“ — Durchführungsplan (Dimension C)
| Aspekt | Regel |
|--------|--------|
| Scope | Actions unter Initiative / Project / **aktivem Gate-Horizont** |
| Reihenfolge | `sort_order` (Fallback) + `action_dependencies` (requires/blocks) |
| Anzeige | Liste + optional Kanten-Ansicht; `ready` / `blocked` Badges |
| Bearbeitung | Modal (ActionForm); Kanten auf Detail oder Mini-Graph in Kontext |
| **Nicht** | Gate-Topologie, Verify-Kriterien, Join-Knoten |
**Plan-Baum „Arbeit“ (heute AP1.12d):** committete Actions + Tasks; **AP1.16c** ergänzt Vorgänger und blocked/ready.
---
## 7. Ebenen bis Workpackage & Task (OM-Referenz)
| Ebene | Typ | Plan-Outline-Knoten | Tiefe |
|-------|-----|---------------------|-------|
| 0 | Initiative | Wurzel | 1 |
| 1 | Project | rekursiv | max. 5 (bestehend) |
| 2 | RoadmapItem | Zielzustände (Graph/Liste) | parallel zur Struktur |
| 1 | RoadmapItem (Gate) | Zielzustände (Graph/Liste) | parallel; grob |
| 2 | Project | rekursiv | max. 5 (bestehend) |
| 3 | BacklogItem | Eingang | flach unter Initiative |
| 4 | Action | Arbeit | unter Project **oder** Initiative |
| 4 | Action | Arbeit / Durchführungsplan | unter Project, Gate oder Initiative |
| 5 | Task | (AP1.5d) | unter Action, UI max. ~3 |
**Plan-Baum „Arbeit“:** Zeigt committete Actions + Tasks (read-heavy); Anlage öffnet Modal. Filter: optional nur „ohne Project“ / „unter gewähltem Project“ (Scope `?project=`).
**Filter „Arbeit“:** optional nur „unter gewähltem Project“ / „unter aktivem Gate“ (`?project=`, Gate-Scope aus Kontrolle).
---
## 7. Scope-Breadcrumb (AP1.9b — Zuverlässigkeit)
## 8. Scope-Breadcrumb (AP1.9b — Zuverlässigkeit)
**Problem:** Klick auf Vorhaben im Breadcrumb führt immer nach `/control/status` — in Planen falsch.
@ -230,7 +286,7 @@ Portfolio-Klick: Scope leeren, Modus beibehalten.
---
## 8. Implementierungspakete
## 9. Implementierungspakete
```text
AP1.9b Scope-Breadcrumb modus-sensitiv; Scope-Sync bei Deep Links härten
@ -238,58 +294,70 @@ AP1.12a Plan-Outline Shell (Desktop Split / Mobile Drill-down)
AP1.12b Modal-Edit für Project, Backlog, Initiative-Profil (Standardfelder)
AP1.12c Reorder: sort_order API + DnD (Desktop) + ↑↓ (Mobile)
AP1.12d Outline-Knoten „Arbeit“ (Actions listen, Link zu Detail)
AP1.16a action_dependencies, actions.sort_order, action_kind
AP1.16b execution_engine (ready/blocked/critical_path)
AP1.16c Plan-Outline: AP-Kanten, blocked/ready (nicht Gate-Designer)
AP1.16d planning_levels / planning_mode; Planning Debt Attention
AP1.13a GateMapView read-only (Layout aus Dependencies)
AP1.13b Kanten-CRUD auf Gate-Detail (kein Designer)
AP1.4d Graph Engine: parallel_group, edge_kind, blocked/ready
AP1.14 Plan-Snapshot / Ist-Overlay / Plan-Revision (vorgeschlagen)
AP1.4d Graph Engine: parallel_group, edge_kind, blocked/ready (Gates)
AP1.14 Plan-Snapshot / Ist-Overlay / Plan-Revision
AP1.15 Zielzustands-Modellierungswerkzeug (Designer, Mitai-Pattern)
AP1.15a Designer MVP (Pan/Zoom, Kanten, Layout)
AP1.15b Designer UX (Drag-Kanten, Gate anlegen, Klick-Panel)
AP1.15c Join/Branch-Topologie (parallel_group, optional_branch, Engine) — deferred, vorbereitet
AP1.15c Join/Branch-Topologie (parallel_group, optional_branch, Engine) — deferred
AP1.5d Tasks in Outline-Knoten „Arbeit“
AP1.10 Archetyp + EFS für Initiative-Profil-Modal (parallel ab 10a)
AP2.0f work_cycle — Zeitbox-Ebene (nach Execution-Graph sinnvoll kombinierbar)
```
**Empfohlene Reihenfolge:**
```text
9b → 12a → 12b → 10a/10c (Profil) → 12c → 12d → 5d → 13a → 13b
9b → 12a → 12b → 10a/10c (Profil) → 12c → 12d → 16a → 16b → 16c → 2.0d → 5d → 13a → 13b
```
---
## 9. Nicht-Ziele (Scope Lock)
## 10. Nicht-Ziele (Scope Lock)
- Kein Sprint-/Capacity-Planning in Planen (später Team-Modus-Erweiterung)
- Kein Inline-CRUD aller Gates in der Outline-Liste
- Kein Mitai-Workflow-Clone (**auch kein „Workflow-Graph“** — nur Zielzustands-Graph)
- Kein Plan=Ist-Collapse (Status/Freitext ersetzt nicht Plan-Revision + Ist-Historie)
- Kein AP-Dependency-Graph im Zielzustands-Designer (→ AP1.16c in Outline „Arbeit“)
- Kein Micro-Gate pro Foundation-AP (→ Action-Graph unter grobem Gate)
- Kein Steering pro Project-Ebene (ADP Recursive Containers)
---
## 10. Erfolgskriterien (PO)
## 11. Erfolgskriterien (PO)
1. PO kann ein Vorhaben wählen und **in einer Ansicht** Struktur, Gates, Eingang und Arbeit sehen.
2. Bearbeitung läuft **nur im Modal** — Outline bleibt übersichtlich.
3. Project-Reihenfolge per DnD (Desktop) änderbar.
4. Gates mit Abhängigkeiten im **Zielzustands-Graph** darstellbar (AP1.13); Designer + Plan/Ist-Parallelität folgen (AP1.4d, AP1.14+).
5. Breadcrumb-Klick auf Vorhaben bleibt im Modus Planen.
5. Breadcrumb-Klick auf Vorhaben bleibt im Modus Planen.
6. Unter aktivem Gate: Durchführungsplan mit Vorgängern planbar — ohne Gate-Graph zu verfeinern (AP1.16).
7. Planning Debt sichtbar, wenn nächste Planungsebene fehlt (AP1.16d).
---
## 11. PO-Freigabe
## 12. PO-Freigabe
- [ ] Outline statt drei Tabs (AP1.12) angenommen
- [ ] Modal-Edit + DnD/↑↓ OK
- [ ] GateMap / Zielzustands-Designer (Mitai-**Pattern** only) Scope OK
- [ ] Plan-Graph vs. Ist-Overlay parallel (AP1.14) OK
- [ ] AP1.9b Breadcrumb-Regel OK
- [x] Progressive Planung + Execution-Graph getrennt vom Gate-Graph (§6, AP1.16) — PO 2026-07-12
---
## Referenzen
- `docs/architecture/ADP_Execution_Plan_and_Work_Package_Dependencies_v0.1.md`
- `docs/architecture/ADP_AP1_10_Initiative_Archetypes_and_Entity_Field_System_v0.1.md`
- `docs/architecture/ADP_AP1_9_PM_Work_Modes_Frontend_IA_v0.1.md`
- `docs/reference/design-principles/mitai/PROMPT_ENGINE_DESIGN_PRINCIPLES.md` (WorkflowEditor-Referenz)

View File

@ -113,8 +113,10 @@ Priorität nach MVP v0.3 §9, Review 2026-07-11 und Dogfooding-Vorbereitung:
| **D1** | **Dogfooding R1** | Vorhaben „Kairo-Jinkendo“ in Kairo anlegen (Spiegel, manuell) | D0 |
| **1** | **AP1.9c** | Cockpit: Signale auf Portfolio-Kacheln | — |
| **2** | **AP1.9b/d** | Scope-Breadcrumb, Kontrolle-Polish | 1 optional parallel |
| **3** | **AP2.0d** | Next-Action-Strategien: maturity, sequential, recurring, queue | — |
| **2b** | **AP1.16ab** | Execution-Graph Schema + Engine (`ready_actions`) | **◐ Code** — Deploy |
| **3** | **AP2.0d** | Next-Action-Strategien: maturity, sequential, recurring, queue | 2b |
| **4** | **Dogfooding R2** | Ist pflegen, Evidence mit Gitea-Links, Gate-Fortschritt | D1 |
| **4b** | **AP1.16cd** | Plan-Outline AP-Kanten, Planning Debt | 2b |
| **5** | **AP0.10d / AP2.1** | Validation B2b: Leitfrage-Test auf Pi | 3, 4 |
| **6** | **AP2.0f** | `work_cycle` / Sprint auf Product (B3 minimal) | 5 optional |
| **7** | **AP1.5d Rest** | Task-Detail, Roll-up-UI | parallel möglich |
@ -123,7 +125,7 @@ Priorität nach MVP v0.3 §9, Review 2026-07-11 und Dogfooding-Vorbereitung:
| **10** | **MCP-Adapter** | Cursor-Agent auf Operational API | 8 |
```text
D0 DOC ──► D1 Dogfooding anlegen ──► 1 AP1.9c ──► 3 AP2.0d ──► 4 Ist spiegeln
D0 DOC ──► D1 Dogfooding anlegen ──► 1 AP1.9c ──► 2b AP1.16 ──► 3 AP2.0d ──► 4 Ist spiegeln
└──► 5 Validation ──► 8 AP1.7 ──► 10 MCP
```
@ -153,7 +155,9 @@ Minimum für **Stufe-A-Abnahfe B2b**:
|----------|----------|
| `Kairo_Implementation_Truth_Table_v0.1.md` | Vollständig auf Stand 2026-07-11 |
| `Kairo_Current_State_Gap_Analysis_v0.2.md` | → v0.3, Gap-Matrix aktualisiert |
| `Kairo_Corrected_MVP_Roadmap_v0.2.md` | Phasen §2, Erledigt §3, Nächste §4, Versionen §7 |
| `Kairo_Corrected_MVP_Roadmap_v0.2.md` | Phasen §2, AP1.16 §1.5/§4, Versionen §7 |
| `ADP_Execution_Plan_and_Work_Package_Dependencies_v0.1.md` | **neu** — Durchführungsplan, progressive Planung |
| `Kairo_Plan_Mode_Design_v0.1.md` | §67 Progressive Planung, AP1.16 Pakete |
| `Kairo_Vision_and_Product_Direction_v0.2.md` | §10 Verweis auf Truth Table (Kurzstand) |
| `Kairo_Dogfooding_Mirror_Steering_v0.1.md` | **neu** — Spiegel-Steuerung + Gitea |

View File

@ -0,0 +1,129 @@
# AP1.16 — Durchführungsplan & Work-Package-Abhängigkeiten
**Stand:** 2026-07-12
**PO-Freigabe:** `ADP_Execution_Plan_and_Work_Package_Dependencies_v0.1.md` (2026-07-12)
**Version (Ziel):** `0.19.0-ap1.16`
**Voraussetzung:** AP1.5 (Actions), AP1.6 (Gate-Link), AP1.12d (Outline „Arbeit“)
---
## Ziel
Ausführungsreihenfolge von **Arbeitspaketen** modellieren — **getrennt** vom Gate-Graph. Rollende Planung: unter aktivem Gate-Horizont APs mit Vorgängern, `ready`/`blocked` Read Models, optional Planning Debt.
---
## PO-Regeln (verbindlich)
### Abgrenzung
| | Gate-Graph (AP1.4d/15) | Execution-Graph (AP1.16) |
|---|------------------------|---------------------------|
| Knoten | `RoadmapItem` | `Action` |
| UI | Zielzustands-Designer | Plan-Outline „Arbeit“ |
| Verify | Kriterien + Evidence | Action `done` |
| Granularität | grob (515) | fein (10100+) |
### Kanten-Semantik (`action_dependencies`)
| `dependency_kind` | Bedeutung |
|-------------------|-----------|
| `requires` | `successor` startet erst, wenn `predecessor` **done** |
| `blocks` | `predecessor` offen → `successor` **blocked** |
| `relates` | nur Anzeige, kein Block |
Kanten-Richtung in DB: `predecessor_action_id``successor_action_id` (Vorgänger muss erfüllt sein, damit Nachfolger ready).
### Scope
- Beide Actions **gleiche Initiative**
- Zyklen verboten (API lehnt ab)
- Execution Engine steuert **keinen** Lifecycle automatisch — nur Read Models
### Planning Actions
- `action_kind`: `delivery` (Default), `planning`, `review`
- Meta-APs für explizite Planungsschritte (Archetyp `planning_mode=explicit`)
---
## Lieferungen
### AP1.16a — Schema
| # | Inhalt |
|---|--------|
| 1 | Migration `023_action_execution_plan.sql` |
| 2 | `actions.sort_order INT NOT NULL DEFAULT 0` |
| 3 | `actions.action_kind` CHECK (`delivery`, `planning`, `review`) |
| 4 | Tabelle `action_dependencies` + Indizes |
| 5 | Action-Service: Felder in CRUD/SELECT |
### AP1.16b — Engine & API
| # | Inhalt |
|---|--------|
| 6 | `backend/steering/graph/execution_engine.py` |
| 7 | `services/execution_plan.py` — Deps CRUD, Cycle-Check |
| 8 | `GET /api/initiatives/{id}/execution/graph-state` |
| 9 | `GET/POST/DELETE …/execution/dependencies` |
| 10 | pytest: Engine + Cycle-Detection (Unit) |
### AP1.16c — Frontend (folgt)
| # | Inhalt |
|---|--------|
| 11 | Plan-Outline „Arbeit“: blocked/ready Badges |
| 12 | Vorgänger-Kanten (Liste oder Mini-Graph) |
| 13 | Dependency anlegen/löschen im Action-Kontext |
### AP1.16d — Steuerung (folgt)
| # | Inhalt |
|---|--------|
| 14 | Method Profile: `planning_levels`, `planning_mode` |
| 15 | Planning Debt in Attention / Cockpit |
| 16 | AP2.0d: Next-Action nutzt `ready_actions` |
---
## Explizit nicht in AP1.16ab
- Gate-Designer-Änderungen
- `project_dependencies` (Struktur-Ebene B)
- `RoadmapItem(type=feature)` als WP-Knoten (Post-MVP)
- Lifecycle-Automatik aus Graph
- Gantt / Kalender
---
## Abnahme
### 16ab (Backend)
- [ ] Migration 023 auf Pi angewendet
- [ ] Zwei Actions, `requires`-Kante: Nachfolger blocked bis Vorgänger `done`
- [ ] Zyklus A→B→A wird abgelehnt
- [ ] `sort_order`-Fallback ohne Kanten (sequenziell wie Gate-Graph)
- [ ] `graph-state` liefert `ready_actions`, `blocked_actions`, `critical_path`
- [ ] `planning_debt` wenn aktives Gate ohne verknüpfte Actions
- [ ] pytest grün (Pi)
### 16cd (später)
- [ ] Outline zeigt blocked/ready
- [ ] Planning Debt sichtbar in Attention
---
## Reihenfolge
```text
16a (Schema) → 16b (Engine + API) → 16c (UI) → 16d (Profile + Attention)
AP2.0d (Strategien)
```
---
*Siehe: `ADP_Execution_Plan_and_Work_Package_Dependencies_v0.1.md`, `Kairo_Plan_Mode_Design_v0.1.md` §6*