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

@@ -102,3 +102,62 @@ async def client(test_engine):
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac:
yield ac
app.dependency_overrides.clear()
# ---------------------------------------------------------------------------
# Shared entity helpers (workplan-first; legacy workstream names retained)
# ---------------------------------------------------------------------------
async def create_test_domain(client, slug="infotech", name="Infotech"):
r = await client.post("/domains/", json={"slug": slug, "name": name})
assert r.status_code == 201, r.text
return r.json()
async def create_test_topic(client, domain_slug="infotech", slug="testtopic", title="Test Topic"):
r = await client.post("/topics/", json={
"slug": slug, "title": title, "domain": domain_slug,
})
assert r.status_code == 201, r.text
return r.json()
async def create_test_repo(client, domain_slug="infotech", slug="test-repo", **extra):
payload = {
"domain_slug": domain_slug,
"slug": slug,
"name": "Test Repo",
**extra,
}
r = await client.post("/repos/", json=payload)
assert r.status_code == 201, r.text
return r.json()
async def create_test_workplan(
client,
repo_id,
topic_id=None,
slug="test-wp",
title="Test Workplan",
status="active",
**extra,
):
payload = {"repo_id": repo_id, "slug": slug, "title": title, "status": status, **extra}
if topic_id is not None:
payload["topic_id"] = topic_id
r = await client.post("/workplans/", json=payload)
assert r.status_code == 201, r.text
return r.json()
async def create_test_workstream(client, topic_id=None, repo_id=None, slug="test-wp", **kwargs):
"""Legacy helper name — creates a repo-anchored workplan."""
if repo_id is None:
domain = await create_test_domain(client)
if topic_id is None:
topic = await create_test_topic(client, domain_slug=domain["slug"])
topic_id = topic["id"]
repo = await create_test_repo(client, domain_slug=domain["slug"], slug=f"{slug}-repo")
repo_id = repo["id"]
return await create_test_workplan(client, repo_id=repo_id, topic_id=topic_id, slug=slug, **kwargs)