2026-02-28 11:47:44 +01:00
---
id: CUST-WP-0005
type: workplan
title: "State Hub v0.5 — Dynamic Domains & Multi-Repo"
2026-06-22 23:16:28 +02:00
domain: infotech
feat(state-hub): implement v0.5 — dynamic domains & multi-repo
Replaces the hardcoded 6-domain PostgreSQL ENUM with a first-class
`domains` DB table, and adds a `managed_repos` table for multi-repo
support per domain.
P1 — Domain as a DB entity:
- Migration b1c2d3e4f5a6: creates `domains` table, migrates topics.domain
ENUM column to domain_id FK, drops the domain ENUM type
- Domain ORM model (api/models/domain.py) + Pydantic schemas
- Domain API router: GET/POST /domains/, GET/PATCH /domains/{slug}/,
rename and archive endpoints with EP/TD cascade on rename
- Topic model updated: domain_id FK + @property domain_slug for
backwards-compatible JSON serialization (field renamed domain → domain_slug)
- TopicCreate/TopicRead updated; seed.py rewritten to use FK lookup
P2 — Multi-repo support:
- ManagedRepo ORM model (api/models/managed_repo.py) + schemas
- Repo API router: GET/POST /repos/, GET/PATCH /repos/{slug}/, archive
- Makefile: add-domain, rename-domain, add-repo, list-repos targets
- register_project.sh: verify domain via /domains/ API + POST /repos/
P3 — MCP tools & live validation:
- 6 new MCP tools: list_domains, create_domain, rename_domain,
archive_domain, list_domain_repos, register_repo
- EP/TD routers: replace hardcoded VALID_DOMAINS set with per-request
DB lookup — returns 422 with list of valid slugs on unknown domain
- State summary: adds domains: list[DomainSummary] (slug, name,
repo_count, active_workstream_count, ep_count, td_count)
- TOOLS.md updated with domain management section
P4 — Dashboard:
- New domains.md page with KPI row + domain cards + repo lists
- domains.json.py + repos.json.py data loaders
- Domains page added to observablehq.config.js nav
- workstreams.md, extensions.md, techdept.md: domain_slug fix +
dynamic domain list loaded from /domains/ API (no longer hardcoded)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 15:20:15 +01:00
status: completed
2026-02-28 11:47:44 +01:00
owner: custodian
topic_slug: custodian
state_hub_workstream_id: 2271eb55-62ca-4bcc-8d9f-f9aacd6922d6
created: "2026-02-28"
updated: "2026-02-28"
feat(state-hub): implement v0.5 — dynamic domains & multi-repo
Replaces the hardcoded 6-domain PostgreSQL ENUM with a first-class
`domains` DB table, and adds a `managed_repos` table for multi-repo
support per domain.
P1 — Domain as a DB entity:
- Migration b1c2d3e4f5a6: creates `domains` table, migrates topics.domain
ENUM column to domain_id FK, drops the domain ENUM type
- Domain ORM model (api/models/domain.py) + Pydantic schemas
- Domain API router: GET/POST /domains/, GET/PATCH /domains/{slug}/,
rename and archive endpoints with EP/TD cascade on rename
- Topic model updated: domain_id FK + @property domain_slug for
backwards-compatible JSON serialization (field renamed domain → domain_slug)
- TopicCreate/TopicRead updated; seed.py rewritten to use FK lookup
P2 — Multi-repo support:
- ManagedRepo ORM model (api/models/managed_repo.py) + schemas
- Repo API router: GET/POST /repos/, GET/PATCH /repos/{slug}/, archive
- Makefile: add-domain, rename-domain, add-repo, list-repos targets
- register_project.sh: verify domain via /domains/ API + POST /repos/
P3 — MCP tools & live validation:
- 6 new MCP tools: list_domains, create_domain, rename_domain,
archive_domain, list_domain_repos, register_repo
- EP/TD routers: replace hardcoded VALID_DOMAINS set with per-request
DB lookup — returns 422 with list of valid slugs on unknown domain
- State summary: adds domains: list[DomainSummary] (slug, name,
repo_count, active_workstream_count, ep_count, td_count)
- TOOLS.md updated with domain management section
P4 — Dashboard:
- New domains.md page with KPI row + domain cards + repo lists
- domains.json.py + repos.json.py data loaders
- Domains page added to observablehq.config.js nav
- workstreams.md, extensions.md, techdept.md: domain_slug fix +
dynamic domain list loaded from /domains/ API (no longer hardcoded)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 15:20:15 +01:00
completed: "2026-02-28"
2026-02-28 11:47:44 +01:00
---
# State Hub v0.5 — Dynamic Domains & Multi-Repo
## Summary
Replace the hardcoded 6-domain Python enum with a first-class `domains` DB
table, enabling new domains to be added and existing ones to be renamed
without schema migrations. Alongside: introduce a `managed_repos` table so
multiple git repositories can be registered per domain, with a corresponding
registration workflow update.
## Context
Domains are currently baked into a PostgreSQL ENUM type (`domain` with 6
fixed values). Adding a domain requires a schema migration; renaming is
impossible at runtime. The registration workflow (`make register-project` )
assumes one repository per domain. Neither constraint fits the actual project
structure, where a domain may span multiple repos and the domain set itself
should be open.
## Dependencies
- Blocks: `CUST-WP-0003` (v0.3 Contribution Tracking) — `managed_repos`
table, `/repos/` router, `register_repo` MCP tool, and base registration
workflow in v0.3 are superseded by P2.1– P2.3 here.
- state_hub_dependency_id: 2033172d-2462-4253-acb7-cb64c7432480
## Phase 1 — Domain as a DB Entity
### P1.1 — Create `domains` table + Alembic migration
```task
id: CUST-WP-0005-T01
state_hub_task_id: 456a0252-9a34-43a6-8244-c3a2caf95d51
feat(state-hub): implement v0.5 — dynamic domains & multi-repo
Replaces the hardcoded 6-domain PostgreSQL ENUM with a first-class
`domains` DB table, and adds a `managed_repos` table for multi-repo
support per domain.
P1 — Domain as a DB entity:
- Migration b1c2d3e4f5a6: creates `domains` table, migrates topics.domain
ENUM column to domain_id FK, drops the domain ENUM type
- Domain ORM model (api/models/domain.py) + Pydantic schemas
- Domain API router: GET/POST /domains/, GET/PATCH /domains/{slug}/,
rename and archive endpoints with EP/TD cascade on rename
- Topic model updated: domain_id FK + @property domain_slug for
backwards-compatible JSON serialization (field renamed domain → domain_slug)
- TopicCreate/TopicRead updated; seed.py rewritten to use FK lookup
P2 — Multi-repo support:
- ManagedRepo ORM model (api/models/managed_repo.py) + schemas
- Repo API router: GET/POST /repos/, GET/PATCH /repos/{slug}/, archive
- Makefile: add-domain, rename-domain, add-repo, list-repos targets
- register_project.sh: verify domain via /domains/ API + POST /repos/
P3 — MCP tools & live validation:
- 6 new MCP tools: list_domains, create_domain, rename_domain,
archive_domain, list_domain_repos, register_repo
- EP/TD routers: replace hardcoded VALID_DOMAINS set with per-request
DB lookup — returns 422 with list of valid slugs on unknown domain
- State summary: adds domains: list[DomainSummary] (slug, name,
repo_count, active_workstream_count, ep_count, td_count)
- TOOLS.md updated with domain management section
P4 — Dashboard:
- New domains.md page with KPI row + domain cards + repo lists
- domains.json.py + repos.json.py data loaders
- Domains page added to observablehq.config.js nav
- workstreams.md, extensions.md, techdept.md: domain_slug fix +
dynamic domain list loaded from /domains/ API (no longer hardcoded)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 15:20:15 +01:00
status: done
2026-02-28 11:47:44 +01:00
priority: high
```
New table: `domains` (id UUID PK, slug VARCHAR(50) UNIQUE NOT NULL,
name VARCHAR(200) NOT NULL, description TEXT, status VARCHAR(20) DEFAULT
'active' CHECK IN ('active','archived'), created_at, updated_at).
Migration (down_revision: a3f1c2d4e5b6):
1. Create `domains` table.
2. INSERT the 6 canonical rows from the existing enum values.
3. ADD COLUMN `domain_id UUID REFERENCES domains(id)` to `topics` .
4. UPDATE topics SET domain_id = (SELECT id FROM domains WHERE slug = domain::text).
5. ALTER TABLE topics ALTER COLUMN domain_id SET NOT NULL.
6. DROP COLUMN `domain` (old enum column) from topics.
7. DROP TYPE `domain` (the PostgreSQL enum type).
8. Leave EP and TD `domain` columns as String(50) — validated at API layer
against live domains list, not via FK. Document as acceptable loose
coupling (avoids cascading complexity).
Downgrade: reverse all steps (recreate enum, repopulate, drop domain_id).
### P1.2 — Domain ORM model + Pydantic schemas
```task
id: CUST-WP-0005-T02
state_hub_task_id: eddff1ae-b263-4d3e-af95-91b4c3c2ecea
feat(state-hub): implement v0.5 — dynamic domains & multi-repo
Replaces the hardcoded 6-domain PostgreSQL ENUM with a first-class
`domains` DB table, and adds a `managed_repos` table for multi-repo
support per domain.
P1 — Domain as a DB entity:
- Migration b1c2d3e4f5a6: creates `domains` table, migrates topics.domain
ENUM column to domain_id FK, drops the domain ENUM type
- Domain ORM model (api/models/domain.py) + Pydantic schemas
- Domain API router: GET/POST /domains/, GET/PATCH /domains/{slug}/,
rename and archive endpoints with EP/TD cascade on rename
- Topic model updated: domain_id FK + @property domain_slug for
backwards-compatible JSON serialization (field renamed domain → domain_slug)
- TopicCreate/TopicRead updated; seed.py rewritten to use FK lookup
P2 — Multi-repo support:
- ManagedRepo ORM model (api/models/managed_repo.py) + schemas
- Repo API router: GET/POST /repos/, GET/PATCH /repos/{slug}/, archive
- Makefile: add-domain, rename-domain, add-repo, list-repos targets
- register_project.sh: verify domain via /domains/ API + POST /repos/
P3 — MCP tools & live validation:
- 6 new MCP tools: list_domains, create_domain, rename_domain,
archive_domain, list_domain_repos, register_repo
- EP/TD routers: replace hardcoded VALID_DOMAINS set with per-request
DB lookup — returns 422 with list of valid slugs on unknown domain
- State summary: adds domains: list[DomainSummary] (slug, name,
repo_count, active_workstream_count, ep_count, td_count)
- TOOLS.md updated with domain management section
P4 — Dashboard:
- New domains.md page with KPI row + domain cards + repo lists
- domains.json.py + repos.json.py data loaders
- Domains page added to observablehq.config.js nav
- workstreams.md, extensions.md, techdept.md: domain_slug fix +
dynamic domain list loaded from /domains/ API (no longer hardcoded)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 15:20:15 +01:00
status: done
2026-02-28 11:47:44 +01:00
priority: high
```
New file: `state-hub/api/models/domain.py`
- `Domain(Base)` : id, slug, name, description, status, created_at, updated_at
- Relationship: `topics: list[Topic]`
Update `state-hub/api/models/topic.py` :
- Remove `Domain(str, enum.Enum)` class
- Change `domain` column → `domain_id: Mapped[uuid.UUID]` (FK to domains.id)
- Add `domain: relationship(Domain)` backref
New file: `state-hub/api/schemas/domain.py`
- `DomainCreate` : slug, name, description?
- `DomainRead` : id, slug, name, description, status, created_at, updated_at
- `DomainUpdate` : name?, description?, status?
- `DomainRename` : new_slug, new_name (cascades EP/TD string columns)
### P1.3 — Domain API router: list, get, create, rename, archive
```task
id: CUST-WP-0005-T03
state_hub_task_id: b91cd0b5-c647-41e5-abf5-98f6f5eb3333
feat(state-hub): implement v0.5 — dynamic domains & multi-repo
Replaces the hardcoded 6-domain PostgreSQL ENUM with a first-class
`domains` DB table, and adds a `managed_repos` table for multi-repo
support per domain.
P1 — Domain as a DB entity:
- Migration b1c2d3e4f5a6: creates `domains` table, migrates topics.domain
ENUM column to domain_id FK, drops the domain ENUM type
- Domain ORM model (api/models/domain.py) + Pydantic schemas
- Domain API router: GET/POST /domains/, GET/PATCH /domains/{slug}/,
rename and archive endpoints with EP/TD cascade on rename
- Topic model updated: domain_id FK + @property domain_slug for
backwards-compatible JSON serialization (field renamed domain → domain_slug)
- TopicCreate/TopicRead updated; seed.py rewritten to use FK lookup
P2 — Multi-repo support:
- ManagedRepo ORM model (api/models/managed_repo.py) + schemas
- Repo API router: GET/POST /repos/, GET/PATCH /repos/{slug}/, archive
- Makefile: add-domain, rename-domain, add-repo, list-repos targets
- register_project.sh: verify domain via /domains/ API + POST /repos/
P3 — MCP tools & live validation:
- 6 new MCP tools: list_domains, create_domain, rename_domain,
archive_domain, list_domain_repos, register_repo
- EP/TD routers: replace hardcoded VALID_DOMAINS set with per-request
DB lookup — returns 422 with list of valid slugs on unknown domain
- State summary: adds domains: list[DomainSummary] (slug, name,
repo_count, active_workstream_count, ep_count, td_count)
- TOOLS.md updated with domain management section
P4 — Dashboard:
- New domains.md page with KPI row + domain cards + repo lists
- domains.json.py + repos.json.py data loaders
- Domains page added to observablehq.config.js nav
- workstreams.md, extensions.md, techdept.md: domain_slug fix +
dynamic domain list loaded from /domains/ API (no longer hardcoded)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 15:20:15 +01:00
status: done
2026-02-28 11:47:44 +01:00
priority: high
```
New file: `state-hub/api/routers/domains.py`
Endpoints:
- `GET /domains/` — list all (filter: ?status=active|archived|all)
2026-07-08 16:35:37 +02:00
- `GET /domains/{slug}/` — get by slug with counts (topics, workplans, EPs, TDs, repos)
2026-02-28 11:47:44 +01:00
- `POST /domains/` — create new (slug: unique, lowercase, underscored)
- `PATCH /domains/{slug}/rename` — update slug + name; cascade EP/TD string columns
- `PATCH /domains/{slug}/archive` — soft-delete; 409 if active topics exist
Register in `state-hub/api/main.py` .
### P1.4 — Update seed.py + TopicCreate schema for new domain model
```task
id: CUST-WP-0005-T04
state_hub_task_id: 0825fa45-ec3a-43b5-8482-4a819db75c6a
feat(state-hub): implement v0.5 — dynamic domains & multi-repo
Replaces the hardcoded 6-domain PostgreSQL ENUM with a first-class
`domains` DB table, and adds a `managed_repos` table for multi-repo
support per domain.
P1 — Domain as a DB entity:
- Migration b1c2d3e4f5a6: creates `domains` table, migrates topics.domain
ENUM column to domain_id FK, drops the domain ENUM type
- Domain ORM model (api/models/domain.py) + Pydantic schemas
- Domain API router: GET/POST /domains/, GET/PATCH /domains/{slug}/,
rename and archive endpoints with EP/TD cascade on rename
- Topic model updated: domain_id FK + @property domain_slug for
backwards-compatible JSON serialization (field renamed domain → domain_slug)
- TopicCreate/TopicRead updated; seed.py rewritten to use FK lookup
P2 — Multi-repo support:
- ManagedRepo ORM model (api/models/managed_repo.py) + schemas
- Repo API router: GET/POST /repos/, GET/PATCH /repos/{slug}/, archive
- Makefile: add-domain, rename-domain, add-repo, list-repos targets
- register_project.sh: verify domain via /domains/ API + POST /repos/
P3 — MCP tools & live validation:
- 6 new MCP tools: list_domains, create_domain, rename_domain,
archive_domain, list_domain_repos, register_repo
- EP/TD routers: replace hardcoded VALID_DOMAINS set with per-request
DB lookup — returns 422 with list of valid slugs on unknown domain
- State summary: adds domains: list[DomainSummary] (slug, name,
repo_count, active_workstream_count, ep_count, td_count)
- TOOLS.md updated with domain management section
P4 — Dashboard:
- New domains.md page with KPI row + domain cards + repo lists
- domains.json.py + repos.json.py data loaders
- Domains page added to observablehq.config.js nav
- workstreams.md, extensions.md, techdept.md: domain_slug fix +
dynamic domain list loaded from /domains/ API (no longer hardcoded)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 15:20:15 +01:00
status: done
2026-02-28 11:47:44 +01:00
priority: medium
```
Update `state-hub/scripts/seed.py` :
- Insert 6 domains before topics (idempotent: ON CONFLICT DO NOTHING).
- Topic insertion uses `domain_id` FK (lookup by slug) instead of enum.
- Remove Domain enum import.
Update `state-hub/api/schemas/topic.py` :
- `TopicCreate.domain` : `Domain` enum → `str` (validated at router via DB lookup).
- `TopicRead` : expose `domain_slug: str` from FK relationship.
Update state router: any `Domain.custodian` etc. references → slug string.
---
## Phase 2 — Multi-Repo Support
### P2.1 — Create `managed_repos` table + migration
```task
id: CUST-WP-0005-T05
state_hub_task_id: 50dc7eec-2b45-4a0d-b99e-27aca8a55a67
feat(state-hub): implement v0.5 — dynamic domains & multi-repo
Replaces the hardcoded 6-domain PostgreSQL ENUM with a first-class
`domains` DB table, and adds a `managed_repos` table for multi-repo
support per domain.
P1 — Domain as a DB entity:
- Migration b1c2d3e4f5a6: creates `domains` table, migrates topics.domain
ENUM column to domain_id FK, drops the domain ENUM type
- Domain ORM model (api/models/domain.py) + Pydantic schemas
- Domain API router: GET/POST /domains/, GET/PATCH /domains/{slug}/,
rename and archive endpoints with EP/TD cascade on rename
- Topic model updated: domain_id FK + @property domain_slug for
backwards-compatible JSON serialization (field renamed domain → domain_slug)
- TopicCreate/TopicRead updated; seed.py rewritten to use FK lookup
P2 — Multi-repo support:
- ManagedRepo ORM model (api/models/managed_repo.py) + schemas
- Repo API router: GET/POST /repos/, GET/PATCH /repos/{slug}/, archive
- Makefile: add-domain, rename-domain, add-repo, list-repos targets
- register_project.sh: verify domain via /domains/ API + POST /repos/
P3 — MCP tools & live validation:
- 6 new MCP tools: list_domains, create_domain, rename_domain,
archive_domain, list_domain_repos, register_repo
- EP/TD routers: replace hardcoded VALID_DOMAINS set with per-request
DB lookup — returns 422 with list of valid slugs on unknown domain
- State summary: adds domains: list[DomainSummary] (slug, name,
repo_count, active_workstream_count, ep_count, td_count)
- TOOLS.md updated with domain management section
P4 — Dashboard:
- New domains.md page with KPI row + domain cards + repo lists
- domains.json.py + repos.json.py data loaders
- Domains page added to observablehq.config.js nav
- workstreams.md, extensions.md, techdept.md: domain_slug fix +
dynamic domain list loaded from /domains/ API (no longer hardcoded)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 15:20:15 +01:00
status: done
2026-02-28 11:47:44 +01:00
priority: high
```
New table: `managed_repos`
Columns: id UUID PK, domain_id UUID FK domains(id) NOT NULL, slug
VARCHAR(100) UNIQUE NOT NULL, name VARCHAR(200) NOT NULL, local_path TEXT,
remote_url TEXT, description TEXT, status VARCHAR(20) DEFAULT 'active',
topic_id UUID FK topics(id) NULLABLE, created_at, updated_at.
Design: `domain_id` is the primary association (not `topic_id` ) — a domain
can have multiple repos without a topic for each. `topic_id` is optional,
2026-07-08 16:35:37 +02:00
for workplan-level linkage.
2026-02-28 11:47:44 +01:00
Coordinate with v0.3 P2.2: if v0.3 runs first, extend rather than replace.
Add to `models/__init__.py` and register router.
### P2.2 — Repo API router: register, list, update, archive
```task
id: CUST-WP-0005-T06
state_hub_task_id: daca1636-0187-4fb7-a515-8a2cf84f89d9
feat(state-hub): implement v0.5 — dynamic domains & multi-repo
Replaces the hardcoded 6-domain PostgreSQL ENUM with a first-class
`domains` DB table, and adds a `managed_repos` table for multi-repo
support per domain.
P1 — Domain as a DB entity:
- Migration b1c2d3e4f5a6: creates `domains` table, migrates topics.domain
ENUM column to domain_id FK, drops the domain ENUM type
- Domain ORM model (api/models/domain.py) + Pydantic schemas
- Domain API router: GET/POST /domains/, GET/PATCH /domains/{slug}/,
rename and archive endpoints with EP/TD cascade on rename
- Topic model updated: domain_id FK + @property domain_slug for
backwards-compatible JSON serialization (field renamed domain → domain_slug)
- TopicCreate/TopicRead updated; seed.py rewritten to use FK lookup
P2 — Multi-repo support:
- ManagedRepo ORM model (api/models/managed_repo.py) + schemas
- Repo API router: GET/POST /repos/, GET/PATCH /repos/{slug}/, archive
- Makefile: add-domain, rename-domain, add-repo, list-repos targets
- register_project.sh: verify domain via /domains/ API + POST /repos/
P3 — MCP tools & live validation:
- 6 new MCP tools: list_domains, create_domain, rename_domain,
archive_domain, list_domain_repos, register_repo
- EP/TD routers: replace hardcoded VALID_DOMAINS set with per-request
DB lookup — returns 422 with list of valid slugs on unknown domain
- State summary: adds domains: list[DomainSummary] (slug, name,
repo_count, active_workstream_count, ep_count, td_count)
- TOOLS.md updated with domain management section
P4 — Dashboard:
- New domains.md page with KPI row + domain cards + repo lists
- domains.json.py + repos.json.py data loaders
- Domains page added to observablehq.config.js nav
- workstreams.md, extensions.md, techdept.md: domain_slug fix +
dynamic domain list loaded from /domains/ API (no longer hardcoded)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 15:20:15 +01:00
status: done
2026-02-28 11:47:44 +01:00
priority: medium
```
New file: `state-hub/api/routers/repos.py`
Endpoints:
- `GET /repos/` — list all (filter: ?domain=slug)
- `GET /repos/{slug}/` — get detail
- `POST /repos/` — register (domain_slug or domain_id required)
- `PATCH /repos/{slug}/` — update (name, local_path, remote_url, description, topic_id)
- `PATCH /repos/{slug}/archive` — soft-delete
Include repos list in `GET /domains/{slug}/` response.
Schemas: `RepoCreate` , `RepoRead` , `RepoUpdate` .
### P2.3 — Update registration workflow: multi-repo + dynamic domains
```task
id: CUST-WP-0005-T07
state_hub_task_id: 930fd3fa-2e1d-401c-9e33-0378f33cb14d
feat(state-hub): implement v0.5 — dynamic domains & multi-repo
Replaces the hardcoded 6-domain PostgreSQL ENUM with a first-class
`domains` DB table, and adds a `managed_repos` table for multi-repo
support per domain.
P1 — Domain as a DB entity:
- Migration b1c2d3e4f5a6: creates `domains` table, migrates topics.domain
ENUM column to domain_id FK, drops the domain ENUM type
- Domain ORM model (api/models/domain.py) + Pydantic schemas
- Domain API router: GET/POST /domains/, GET/PATCH /domains/{slug}/,
rename and archive endpoints with EP/TD cascade on rename
- Topic model updated: domain_id FK + @property domain_slug for
backwards-compatible JSON serialization (field renamed domain → domain_slug)
- TopicCreate/TopicRead updated; seed.py rewritten to use FK lookup
P2 — Multi-repo support:
- ManagedRepo ORM model (api/models/managed_repo.py) + schemas
- Repo API router: GET/POST /repos/, GET/PATCH /repos/{slug}/, archive
- Makefile: add-domain, rename-domain, add-repo, list-repos targets
- register_project.sh: verify domain via /domains/ API + POST /repos/
P3 — MCP tools & live validation:
- 6 new MCP tools: list_domains, create_domain, rename_domain,
archive_domain, list_domain_repos, register_repo
- EP/TD routers: replace hardcoded VALID_DOMAINS set with per-request
DB lookup — returns 422 with list of valid slugs on unknown domain
- State summary: adds domains: list[DomainSummary] (slug, name,
repo_count, active_workstream_count, ep_count, td_count)
- TOOLS.md updated with domain management section
P4 — Dashboard:
- New domains.md page with KPI row + domain cards + repo lists
- domains.json.py + repos.json.py data loaders
- Domains page added to observablehq.config.js nav
- workstreams.md, extensions.md, techdept.md: domain_slug fix +
dynamic domain list loaded from /domains/ API (no longer hardcoded)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 15:20:15 +01:00
status: done
2026-02-28 11:47:44 +01:00
priority: medium
```
Update `state-hub/scripts/register_project.sh` :
- Verify domain via `GET /domains/{slug}/` (not just topic lookup).
- POST to `/repos/` after writing CLAUDE.md.
- Support second repo for existing domain without overwriting CLAUDE.md
(`--force` flag or existence check).
Update `state-hub/Makefile` :
- `make add-domain DOMAIN=<slug> NAME=<name>` — POST /domains/
- `make rename-domain DOMAIN=<old> NEW_SLUG=<s> NEW_NAME=<n>` — PATCH rename
- `make add-repo DOMAIN=<slug> REPO_PATH=<path>` — POST /repos/ + CLAUDE.md
- `make list-repos DOMAIN=<slug>` — GET /repos/?domain=slug
Update `state-hub/scripts/project_claude_md.template` :
- Add section listing other known repos in the same domain.
---
## Phase 3 — MCP & Validation
### P3.1 — MCP tools: domain lifecycle + repo registration
```task
id: CUST-WP-0005-T08
state_hub_task_id: e99e6bd7-f6f2-496f-acbf-d2b013f37c73
feat(state-hub): implement v0.5 — dynamic domains & multi-repo
Replaces the hardcoded 6-domain PostgreSQL ENUM with a first-class
`domains` DB table, and adds a `managed_repos` table for multi-repo
support per domain.
P1 — Domain as a DB entity:
- Migration b1c2d3e4f5a6: creates `domains` table, migrates topics.domain
ENUM column to domain_id FK, drops the domain ENUM type
- Domain ORM model (api/models/domain.py) + Pydantic schemas
- Domain API router: GET/POST /domains/, GET/PATCH /domains/{slug}/,
rename and archive endpoints with EP/TD cascade on rename
- Topic model updated: domain_id FK + @property domain_slug for
backwards-compatible JSON serialization (field renamed domain → domain_slug)
- TopicCreate/TopicRead updated; seed.py rewritten to use FK lookup
P2 — Multi-repo support:
- ManagedRepo ORM model (api/models/managed_repo.py) + schemas
- Repo API router: GET/POST /repos/, GET/PATCH /repos/{slug}/, archive
- Makefile: add-domain, rename-domain, add-repo, list-repos targets
- register_project.sh: verify domain via /domains/ API + POST /repos/
P3 — MCP tools & live validation:
- 6 new MCP tools: list_domains, create_domain, rename_domain,
archive_domain, list_domain_repos, register_repo
- EP/TD routers: replace hardcoded VALID_DOMAINS set with per-request
DB lookup — returns 422 with list of valid slugs on unknown domain
- State summary: adds domains: list[DomainSummary] (slug, name,
repo_count, active_workstream_count, ep_count, td_count)
- TOOLS.md updated with domain management section
P4 — Dashboard:
- New domains.md page with KPI row + domain cards + repo lists
- domains.json.py + repos.json.py data loaders
- Domains page added to observablehq.config.js nav
- workstreams.md, extensions.md, techdept.md: domain_slug fix +
dynamic domain list loaded from /domains/ API (no longer hardcoded)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 15:20:15 +01:00
status: done
2026-02-28 11:47:44 +01:00
priority: medium
```
Add to `state-hub/mcp_server/server.py` :
1. `list_domains(status="active")` — GET /domains/?status=…
2. `create_domain(slug, name, description?)` — POST /domains/
3. `rename_domain(slug, new_slug, new_name)` — PATCH /domains/{slug}/rename
4. `archive_domain(slug)` — PATCH /domains/{slug}/archive
5. `list_domain_repos(domain_slug)` — GET /repos/?domain=…
6. `register_repo(domain_slug, name, local_path?, remote_url?, description?)` — POST /repos/
Update `state-hub/mcp_server/TOOLS.md` to document all 6 new tools.
### P3.2 — Live domain validation for EP/TD + domain stats in state summary
```task
id: CUST-WP-0005-T09
state_hub_task_id: f2b352b6-4b9e-47b3-b629-48485245390e
feat(state-hub): implement v0.5 — dynamic domains & multi-repo
Replaces the hardcoded 6-domain PostgreSQL ENUM with a first-class
`domains` DB table, and adds a `managed_repos` table for multi-repo
support per domain.
P1 — Domain as a DB entity:
- Migration b1c2d3e4f5a6: creates `domains` table, migrates topics.domain
ENUM column to domain_id FK, drops the domain ENUM type
- Domain ORM model (api/models/domain.py) + Pydantic schemas
- Domain API router: GET/POST /domains/, GET/PATCH /domains/{slug}/,
rename and archive endpoints with EP/TD cascade on rename
- Topic model updated: domain_id FK + @property domain_slug for
backwards-compatible JSON serialization (field renamed domain → domain_slug)
- TopicCreate/TopicRead updated; seed.py rewritten to use FK lookup
P2 — Multi-repo support:
- ManagedRepo ORM model (api/models/managed_repo.py) + schemas
- Repo API router: GET/POST /repos/, GET/PATCH /repos/{slug}/, archive
- Makefile: add-domain, rename-domain, add-repo, list-repos targets
- register_project.sh: verify domain via /domains/ API + POST /repos/
P3 — MCP tools & live validation:
- 6 new MCP tools: list_domains, create_domain, rename_domain,
archive_domain, list_domain_repos, register_repo
- EP/TD routers: replace hardcoded VALID_DOMAINS set with per-request
DB lookup — returns 422 with list of valid slugs on unknown domain
- State summary: adds domains: list[DomainSummary] (slug, name,
repo_count, active_workstream_count, ep_count, td_count)
- TOOLS.md updated with domain management section
P4 — Dashboard:
- New domains.md page with KPI row + domain cards + repo lists
- domains.json.py + repos.json.py data loaders
- Domains page added to observablehq.config.js nav
- workstreams.md, extensions.md, techdept.md: domain_slug fix +
dynamic domain list loaded from /domains/ API (no longer hardcoded)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 15:20:15 +01:00
status: done
2026-02-28 11:47:44 +01:00
priority: low
```
Replace hardcoded `VALID_DOMAINS` set in EP/TD routers with a per-request
DB lookup: `get_valid_domain_slugs(db: AsyncSession) -> set[str]` . Return
422 with a helpful message for unknown domains.
Add to `/state/summary` response:
- `domains: list[DomainSummary]` — slug, name, repo_count,
active_workstream_count, ep_count, td_count.
Update global CLAUDE.md and project CLAUDE.md template to note
`list_domains()` is now available.
---
## Phase 4 — Dashboard
### P4.1 — Dashboard: domains.md page
```task
id: CUST-WP-0005-T10
state_hub_task_id: ddb91669-5297-45d9-8b5e-10213135a96d
feat(state-hub): implement v0.5 — dynamic domains & multi-repo
Replaces the hardcoded 6-domain PostgreSQL ENUM with a first-class
`domains` DB table, and adds a `managed_repos` table for multi-repo
support per domain.
P1 — Domain as a DB entity:
- Migration b1c2d3e4f5a6: creates `domains` table, migrates topics.domain
ENUM column to domain_id FK, drops the domain ENUM type
- Domain ORM model (api/models/domain.py) + Pydantic schemas
- Domain API router: GET/POST /domains/, GET/PATCH /domains/{slug}/,
rename and archive endpoints with EP/TD cascade on rename
- Topic model updated: domain_id FK + @property domain_slug for
backwards-compatible JSON serialization (field renamed domain → domain_slug)
- TopicCreate/TopicRead updated; seed.py rewritten to use FK lookup
P2 — Multi-repo support:
- ManagedRepo ORM model (api/models/managed_repo.py) + schemas
- Repo API router: GET/POST /repos/, GET/PATCH /repos/{slug}/, archive
- Makefile: add-domain, rename-domain, add-repo, list-repos targets
- register_project.sh: verify domain via /domains/ API + POST /repos/
P3 — MCP tools & live validation:
- 6 new MCP tools: list_domains, create_domain, rename_domain,
archive_domain, list_domain_repos, register_repo
- EP/TD routers: replace hardcoded VALID_DOMAINS set with per-request
DB lookup — returns 422 with list of valid slugs on unknown domain
- State summary: adds domains: list[DomainSummary] (slug, name,
repo_count, active_workstream_count, ep_count, td_count)
- TOOLS.md updated with domain management section
P4 — Dashboard:
- New domains.md page with KPI row + domain cards + repo lists
- domains.json.py + repos.json.py data loaders
- Domains page added to observablehq.config.js nav
- workstreams.md, extensions.md, techdept.md: domain_slug fix +
dynamic domain list loaded from /domains/ API (no longer hardcoded)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 15:20:15 +01:00
status: done
2026-02-28 11:47:44 +01:00
priority: low
```
New file: `state-hub/dashboard/src/domains.md`
Layout:
- KPI row: total domains, total repos, newest domain.
2026-07-08 16:35:37 +02:00
- Domain cards: name, slug, status badge, repo count, workplan count,
2026-02-28 11:47:44 +01:00
EP count, TD count.
- Repos sub-list per card: name, local_path, remote_url (linked), date.
- Click → entity modal (extend `entity-modal.js` for domain type).
Data loaders:
- `src/data/domains.json.py` — GET /domains/?status=all
- `src/data/repos.json.py` — GET /repos/
Add to `observablehq.config.js` nav.
2026-07-08 16:35:37 +02:00
### P4.2 — Dashboard: domain filter on workplans, EP, TD pages
2026-02-28 11:47:44 +01:00
```task
id: CUST-WP-0005-T11
state_hub_task_id: 3fec152c-00e1-4cdb-b6e5-26136c8cb6c1
feat(state-hub): implement v0.5 — dynamic domains & multi-repo
Replaces the hardcoded 6-domain PostgreSQL ENUM with a first-class
`domains` DB table, and adds a `managed_repos` table for multi-repo
support per domain.
P1 — Domain as a DB entity:
- Migration b1c2d3e4f5a6: creates `domains` table, migrates topics.domain
ENUM column to domain_id FK, drops the domain ENUM type
- Domain ORM model (api/models/domain.py) + Pydantic schemas
- Domain API router: GET/POST /domains/, GET/PATCH /domains/{slug}/,
rename and archive endpoints with EP/TD cascade on rename
- Topic model updated: domain_id FK + @property domain_slug for
backwards-compatible JSON serialization (field renamed domain → domain_slug)
- TopicCreate/TopicRead updated; seed.py rewritten to use FK lookup
P2 — Multi-repo support:
- ManagedRepo ORM model (api/models/managed_repo.py) + schemas
- Repo API router: GET/POST /repos/, GET/PATCH /repos/{slug}/, archive
- Makefile: add-domain, rename-domain, add-repo, list-repos targets
- register_project.sh: verify domain via /domains/ API + POST /repos/
P3 — MCP tools & live validation:
- 6 new MCP tools: list_domains, create_domain, rename_domain,
archive_domain, list_domain_repos, register_repo
- EP/TD routers: replace hardcoded VALID_DOMAINS set with per-request
DB lookup — returns 422 with list of valid slugs on unknown domain
- State summary: adds domains: list[DomainSummary] (slug, name,
repo_count, active_workstream_count, ep_count, td_count)
- TOOLS.md updated with domain management section
P4 — Dashboard:
- New domains.md page with KPI row + domain cards + repo lists
- domains.json.py + repos.json.py data loaders
- Domains page added to observablehq.config.js nav
- workstreams.md, extensions.md, techdept.md: domain_slug fix +
dynamic domain list loaded from /domains/ API (no longer hardcoded)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 15:20:15 +01:00
status: done
2026-02-28 11:47:44 +01:00
priority: low
```
Add `Inputs.select` domain filter to:
2026-07-08 16:35:37 +02:00
- `workplans.md` — filter by domain via topic.domain_slug
2026-02-28 11:47:44 +01:00
- `extensions.md` — wire existing API domain filter to UI
- `techdept.md` — wire existing API domain filter to UI
Each filter: load domain list from `/domains/?status=active` , default "All",
client-side reactive re-filter, URL hash persistence (`?domain=railiance` ).
Check if `WorkstreamRead` already exposes `domain_slug` ; add if missing.
---
## Notes
- v0.5 P2.1 supersedes the `managed_repos` design in v0.3 P2.2.
v0.3 is annotated accordingly and depends on this workplan.
- EP/TD `domain` columns remain `String(50)` (not FK) for simplicity.
The rename endpoint cascades updates to these columns.
- `sync_workplans.py` (future, v0.3 Phase 4) will be able to ingest this
file and reconcile it with the DB rows created during the planning session.
2026-03-02 00:32:35 +01:00
---
## Closure Review — 2026-03-02
**Outcome:** All 11 tasks completed. No carry-forwards. No dropped tasks.
**Context:** This workplan was created DB-first on 2026-02-28, before ADR-001 was formalised. The workplan file correctly recorded all tasks as `status: done` , but the DB rows were never synced from the file — they remained in their initial `todo` state in the database. The daily stale-task cleanup script (`scripts/cleanup_stale_tasks.py` ) detected these 11 stale DB rows and cancelled them on 2026-03-02. No actual work was lost: all deliverables in Phase 1– 4 were shipped as part of State Hub v0.5.
### Completed (DB updated at delivery time; file status = done)
- CUST-WP-0005-T01 — Create `domains` table + Alembic migration
- CUST-WP-0005-T02 — Domain ORM model + Pydantic schemas
- CUST-WP-0005-T03 — Domain API router: list, get, create, rename, archive
- CUST-WP-0005-T04 — Update seed.py + TopicCreate schema for new domain model
- CUST-WP-0005-T05 — Create `managed_repos` table + migration
- CUST-WP-0005-T06 — Repo API router: register, list, update, archive
- CUST-WP-0005-T07 — Update registration workflow: multi-repo + dynamic domains
- CUST-WP-0005-T08 — MCP tools: domain lifecycle + repo registration
- CUST-WP-0005-T09 — Live domain validation for EP/TD + domain stats in state summary
- CUST-WP-0005-T10 — Dashboard: domains.md page
2026-07-08 16:35:37 +02:00
- CUST-WP-0005-T11 — Dashboard: domain filter on workplans, EP, TD pages
2026-03-02 00:32:35 +01:00
### Cancelled (DB records only — legacy stale rows, not real cancellations)
All 11 DB task rows were cancelled by the cleanup script. The workplan file was authoritative; the DB rows were artefacts of the pre-ADR-001 DB-first creation pattern. This does not reflect a change in work outcome.