diff --git a/workplans/CUST-WP-0031-capability-registry.md b/workplans/CUST-WP-0031-capability-registry.md new file mode 100644 index 0000000..63f7ce5 --- /dev/null +++ b/workplans/CUST-WP-0031-capability-registry.md @@ -0,0 +1,267 @@ +--- +id: CUST-WP-0031 +type: workplan +title: "Domain Capability Registry" +domain: custodian +repo: the-custodian +status: active +owner: custodian +topic_slug: custodian +created: "2026-03-31" +updated: "2026-03-31" +state_hub_workstream_id: "7b480543-b18a-4d79-a30b-7b34bce27ee5" +--- + +# Domain Capability Registry + +## Goal + +Give worker agents a single efficient MCP call to understand what each +domain/repo does and what capabilities it exposes — without having to +explore source repositories directly. + +The immediate driver is that inter-hub workers on CoulombCore need to +discuss operational setup and security architecture (e.g. key-cape's SSH CA +role, net-kingdom's SSO platform) but can only see repos checked out locally. +Key-cape and net-kingdom are not checked out there; the worker must fall back +to the state-hub for this information. + +## Background + +### What exists + +- 25 `CapabilityCatalog` entries across four domains (railiance: 11, + netkingdom: 6, custodian: 5, markitect: 3). +- `CapabilityCatalog` is domain-scoped only — no `repo_id` FK, so the + SSH CA capability in netkingdom cannot be attributed to key-cape vs + net-kingdom. +- `get_domain_summary` is the cheap worker orientation call (~10% token + cost of full state summary) but returns **no capability information**. +- `list_capabilities` works but returns verbose full records; workers + must call it per domain and get unstructured JSON. +- `ManagedRepo` has a `description` field but 8 of 17 repos have it empty. +- personhood, foerster_capabilities, and coulomb_social have zero capabilities + registered. + +### Design + +Three complementary changes: + +1. **Schema** — add nullable `repo_id` FK to `CapabilityCatalog` so each + capability can be attributed to the repo that provides it. +2. **MCP orientation** — extend `get_domain_summary` to include a compact + `capabilities` list (type + title + repo_slug only, no keywords/description) + so the standard worker orientation call already surfaces what the domain + provides. +3. **Full-detail tool** — new `get_capability_profile(domain_slug?)` MCP + tool that returns a complete registry: domain → repos (with description) + → capabilities (with description). Designed for deep-dive or + cross-domain architectural discussion. + +Data tasks: populate missing repo descriptions and back-fill `repo_id` on +existing catalog entries; register capabilities for the three empty domains. + +## Implementation Plan + +### Phase 1 — Schema + MCP + +- Add nullable `repo_id` FK on `CapabilityCatalog`; update `register_capability` + to accept an optional `repo_slug` that resolves to `repo_id`. +- Add `capabilities` compact list to `get_domain_summary` response. +- New MCP tool `get_capability_profile`. + +### Phase 2 — Data Population + +- Populate `description` for the 8 repos that lack it. +- Back-fill `repo_id` on existing 25 catalog entries. +- Register capabilities for personhood, foerster_capabilities, coulomb_social. + +## Tasks + +```task +id: T01 +title: "Add repo_id FK to CapabilityCatalog" +status: todo +priority: high +description: > + 1. Write Alembic migration: add `repo_id` UUID nullable FK column to + `capability_catalog` referencing `managed_repos.id` (ON DELETE SET NULL). + Add index on `repo_id`. Down revision from latest head. + 2. Add `repo_id: uuid.UUID | None` to the CapabilityCatalog model + (managed_repo.py FK relationship, nullable). + 3. Update CapabilityCatalog schema (CapabilityRead, CapabilityCreate) to + include `repo_id: uuid.UUID | None` and `repo_slug: str | None` + (computed via relationship). + 4. Update the capability-catalog router: if `repo_slug` is provided on + POST, resolve it to `repo_id` (404 if not found in the same domain). + 5. Update `register_capability` MCP tool to accept optional `repo_slug` + parameter and forward it. + 6. Run `make test` — no regressions. +state_hub_task_id: "8d5e3e37-c753-4cdc-9211-83ee39f6b0f2" +``` + +```task +id: T02 +title: "Include compact capabilities in get_domain_summary" +status: todo +priority: high +description: > + Extend the `get_domain_summary` MCP tool response to include a + `capabilities` list alongside `repos` and `workstreams`. Each entry + should be compact: {type, title, repo_slug} only (no description or + keywords, to keep token cost low). + Implementation: in mcp_server/server.py, after building the summary dict, + call GET /capability-catalog/?domain=&status=active and append + `capabilities: [{capability_type, title, repo_slug}]` to the response. + The `repo_slug` field is nullable (domain-level capabilities have null). + Cap at 20 entries to protect token budget; add a `capabilities_truncated` + boolean flag if there are more. +state_hub_task_id: "ac47994c-efe9-404f-8065-9adf2e923d4c" +``` + +```task +id: T03 +title: "New MCP tool: get_capability_profile" +status: todo +priority: high +description: > + Add a new MCP tool `get_capability_profile(domain_slug: str | None = None)` + to mcp_server/server.py. + + Behaviour: + - If domain_slug is provided: return the profile for that one domain. + - If omitted: return profiles for all active domains. + + Response structure per domain: + { + "slug": "netkingdom", + "title": "", + "repos": [ + { + "slug": "key-cape", + "name": "...", + "description": "...", + "capabilities": [ + {"type": "security", "title": "SSH CA", "description": "...", "keywords": [...]} + ] + }, + // domain-level capabilities (repo_slug null) listed at the end under + // a synthetic repo entry {"slug": null, "name": "(domain-level)", ...} + ] + } + + Implementation: call GET /repos?domain= and + GET /capability-catalog/?domain=&status=active, then assemble. + For the all-domains case iterate over GET /domains/ (active only). +state_hub_task_id: "b380fd2b-28fa-4c47-96d6-4c65a0300c44" +``` + +```task +id: T04 +title: "Populate missing repo descriptions" +status: todo +priority: medium +description: > + Use PATCH /repos/{slug}/ to set `description` for the 8 repos that + currently have none. Write concise scope descriptions (2-3 sentences each) + by reading the repo's CLAUDE.md or README if available; otherwise infer + from the slug and domain context. + + Repos needing descriptions: + - the-custodian (custodian domain) + - activity-core (custodian domain) + - kaizen-agentic (custodian domain) + - llm-connect (custodian domain) — already has description, skip + - markitect-project (markitect domain) + - railiance-bootstrap (railiance domain) + - railiance-hosts (railiance domain) + - ops-bridge (custodian domain) — already has description, skip + + Confirm final list by re-checking GET /repos/ at task start. +state_hub_task_id: "00d44110-fcb4-45cc-8bc8-454af2629d2f" +``` + +```task +id: T05 +title: "Back-fill repo_id on existing capability catalog entries" +status: todo +priority: medium +description: > + After T01 lands, update the 25 existing catalog entries to set repo_id + where the capability is clearly from a specific repo. + Use PATCH /capability-catalog/{id} (add this endpoint if missing) or + re-register with repo_slug. + + Key attributions: + netkingdom domain: + - "Container image build and publish (GHCR)" → net-kingdom + - "Bootstrap local identity service" → key-cape + - "SSO/MFA platform (Keycloak)" → key-cape + - "NetKingdom IAM Profile specification" → net-kingdom (spec lives there) + - "Identity migration tooling" → key-cape + - "OIDC/PKCE authentication (lightweight mode)" → key-cape + railiance domain: + - All 11 entries → railiance-platform or railiance-cluster or railiance-infra + (review titles to assign; most go to railiance-platform) + custodian domain: + - "SSH reverse tunnel connectivity" → ops-bridge + - "Durable event-triggered task factory" → activity-core + - "SBOM and licence reporting" → the-custodian (state-hub) + - "Cross-domain state tracking" → the-custodian (state-hub) + - "MCP tool registration" → the-custodian (state-hub) + markitect domain: + - All 3 → markitect-project + + Note: a PATCH endpoint on /capability-catalog/{id} does not currently + exist. Add a minimal one in T01 or add it here: PATCH /capability-catalog/{id} + accepting {repo_slug?, description?, keywords?, status?}. +state_hub_task_id: "7f0748c7-bdee-4801-b870-d4940a5a2e63" +``` + +```task +id: T06 +title: "Register capabilities for personhood, foerster_capabilities, coulomb_social" +status: todo +priority: medium +description: > + Register at least 3 capabilities per missing domain using + register_capability (MCP tool or POST /capability-catalog/). + + personhood domain: + - "Rights and obligations framework modelling" (type: governance) + - "Entity consent and data-subject lifecycle" (type: governance) + - "Privacy-by-design compliance checking" (type: security) + + foerster_capabilities domain: + - "Agency capability taxonomy" (type: governance) + - "Capability gap analysis" (type: data) + - "Agent persona specification" (type: governance) + + coulomb_social domain: + - "Co-creation marketplace workflow" (type: api) + - "Contribution matching and brokerage" (type: data) + - "Contributor reputation tracking" (type: data) + + Write descriptions and keywords that would route capability requests + correctly to these domains. +state_hub_task_id: "c6e1be52-961c-4e34-96b1-e450d64298df" +``` + +```task +id: T07 +title: "Consistency gate and smoke test" +status: todo +priority: low +description: > + 1. Run `make test` — all existing tests must pass. + 2. Run `make fix-consistency REPO=the-custodian`. + 3. Manual smoke test via Python MCP client: + - Call get_domain_summary("netkingdom") and verify capabilities list + appears with at least the 6 netkingdom entries. + - Call get_capability_profile("netkingdom") and verify key-cape entries + have repo_slug set. + - Call get_capability_profile() (no arg) and verify all 7 active domains + appear. + 4. Add a note to TOOLS.md about the new get_capability_profile tool. +state_hub_task_id: "7e07e32c-683d-47a1-951c-beace9f245a6" +```