feat(classification-spine): implement STATE-WP-0065 repo-anchored model

Replace the ad-hoc coordination-domain spine with the Repo Classification
Standard: 14 market domains, classification columns on managed_repos, and
workplans anchored by repo_id (topic_id optional).

- Add Alembic migration d8e9f0a1b2c3 with data backfill and workstream→workplan rename
- Add api/classification.py validation and register-from-classification tooling
- Expose workplan-first REST/MCP surface with legacy workstream aliases
- Add C-24 consistency rule and legacy domain frontmatter mapping
- Update dashboard repos page with category/capability/stake filters
- Update orientation docs; mark STATE-WP-0065 finished
This commit is contained in:
2026-06-22 13:52:13 +02:00
parent 279be4ffbd
commit 0949d4c0d8
84 changed files with 4494 additions and 1111 deletions

View File

@@ -40,6 +40,7 @@ def _needs_escalation(body: DecisionCreate) -> str | None:
@router.get("/", response_model=list[DecisionRead])
async def list_decisions(
topic_id: uuid.UUID | None = None,
workplan_id: uuid.UUID | None = None,
workstream_id: uuid.UUID | None = None,
status: DecisionStatus | None = None,
decision_type: DecisionType | None = None,
@@ -48,8 +49,9 @@ async def list_decisions(
q = select(Decision)
if topic_id:
q = q.where(Decision.topic_id == topic_id)
if workstream_id:
q = q.where(Decision.workstream_id == workstream_id)
scope_id = workplan_id or workstream_id
if scope_id:
q = q.where(Decision.workplan_id == scope_id)
if status:
q = q.where(Decision.status == status)
if decision_type:
@@ -139,7 +141,7 @@ async def resolve_decision_action(
event = ProgressEvent(
topic_id=decision.topic_id,
workstream_id=decision.workstream_id,
workplan_id=decision.workplan_id,
decision_id=decision.id,
event_type="decision_resolved",
summary=f"Decision resolved: {decision.title}",
@@ -159,7 +161,7 @@ async def resolve_decision_action(
"decision_id": str(decision.id),
"title": decision.title,
"topic_id": str(decision.topic_id) if decision.topic_id else None,
"workstream_id": str(decision.workstream_id) if decision.workstream_id else None,
"workstream_id": str(decision.workplan_id) if decision.workplan_id else None,
"decided_by": body.decided_by,
"rationale_snippet": (body.rationale or "")[:240],
},