Files
inter-hub/workplans/IHUB-WP-0011-ihf-phase10-hub-registry.md
Bernd Worsch 6e8972f828
Some checks failed
Test / test (push) Has been cancelled
feat(WP-0011): IHF Phase 10 — Hub Registry and Widget Marketplace
Delivers the hub registry discovery UI, widget pattern library,
governance template library, and marketplace dashboard.

Key changes:
- Schema: widget_patterns (widget_type FK to registry), widget_pattern_versions,
  pattern_adoptions, governance_templates (categories JSONB, validated at
  controller), governance_template_clones — all GAAF-compliant, no bare TEXT
  type discriminators
- Migration: 1743897600-ihf-phase10-hub-registry.sql
- HubRegistry controller + views: browsable view over hub_capability_manifests,
  hub_health_snapshots, hubs with per-hub GAAF compliance indicator
- WidgetPatterns controller + views: publish, version, adopt; adoption
  triggers manifest amendment draft when new types are introduced
- GovernanceTemplates controller + views: CRUD, clone with category
  validation against annotation_category_registry
- MarketplaceDashboard controller + view: full-text search, widget-type
  filter, sort, trending panel, autoRefresh
- API v2: /api/v2/hub-registry, /api/v2/widget-patterns (+ adopt endpoint)
- OpenAPI spec updated with Phase 10 paths
- GAAF scorecard: Customization 2.5 → 3.2; overall 3.41 → 3.56 (Strong)
- CLAUDE.md: Phase 10 complete; active workplan → Phase 11

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 20:14:43 +00:00

496 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: IHUB-WP-0011
type: workplan
title: "IHF Phase 10 — Hub Registry and Widget Marketplace"
domain: inter_hub
repo: inter-hub
status: done
owner: custodian
topic_slug: inter_hub
created: "2026-04-01"
updated: "2026-04-01"
state_hub_sync: done
state_hub_workstream_id: "bc81e097-e91c-4750-80b6-c809ebcf7ef9"
---
# IHF Phase 10 — Hub Registry and Widget Marketplace
## Goal
Enable reuse of proven widget patterns, governance templates, and hub
configurations across deployments. Phase 9 made the IHF externally consumable.
Phase 10 makes it composable: hubs and widgets can be discovered, rated,
adopted, and evolved as shared platform assets.
## Background
Phases 19 and IHUB-WP-0009 (GAAF Compliance Foundation) are complete. All
Phase 10 entry gates are satisfied:
- `HubCapabilityManifest` table and activation workflow operational ✓
- Four type registries seeded and validated ✓
- `/api/v2/` REST API with OpenAPI 3.1 spec live ✓
- `HubHealthSnapshot` data available ✓
- ARCHITECTURE-LAYERS.md scorecard at 3.41 (approaching Strong) ✓
- Architectural fitness functions in CI ✓
Phase 10's Hub Registry IS the `HubCapabilityManifest` table with a public-facing
discovery UI. No new hub registry table is required. The data already exists;
Phase 10 adds browsability, pattern publishing, and adoption mechanics.
Reference: `specs/InteractionHubFrameworkSpecification_v0.2.md` §Phase 10.
## GAAF Architectural Constraints
All new code in this workplan must comply with:
1. **No bare TEXT type discriminators** — any new type discriminator column must
FK to a registry table or carry a CHECK constraint.
2. **WidgetPattern.widget_type must FK to widget_type_registry** — no unregistered
widget types may be referenced by a pattern.
3. **GovernanceTemplate categories must FK to annotation_category_registry**
template categories are registered vocabulary, not free text.
4. **Core tables are frozen**`widgets`, `interaction_events`, `annotations`,
`hubs`, and Phase 14 dependents must not gain columns without a corresponding
`/contracts/core/` update.
5. **Append-only invariant is permanent** — no migration may add UPDATE or DELETE
capability to `interaction_events` or `outcome_signals`.
6. **Manifest amendment is the extension mechanism** — when a hub adopts a pattern
or governance template that introduces new types, those types must be added to
the hub's `HubCapabilityManifest` via a draft amendment, not inserted directly
into the registry.
## Data Artifacts Introduced
`WidgetPattern`, `WidgetPatternVersion`, `PatternAdoption`, `GovernanceTemplate`,
`GovernanceTemplateClone`
Note: No `HubRegistry` table — the hub registry is a view over existing
`hub_capability_manifests`, `hub_health_snapshots`, and `hubs` tables.
Schema additions:
```sql
-- widget_patterns: reusable widget definitions tied to registered types
CREATE TABLE widget_patterns (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
hub_id UUID NOT NULL REFERENCES hubs(id),
name TEXT NOT NULL,
description TEXT,
widget_type TEXT NOT NULL REFERENCES widget_type_registry(name),
is_cross_hub BOOLEAN NOT NULL DEFAULT FALSE,
is_published BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL
);
-- widget_pattern_versions: explicit version history
CREATE TABLE widget_pattern_versions (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
widget_pattern_id UUID NOT NULL REFERENCES widget_patterns(id) ON DELETE CASCADE,
version_number INTEGER NOT NULL,
definition JSONB NOT NULL, -- widget envelope definition snapshot
changelog TEXT,
published_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,
UNIQUE (widget_pattern_id, version_number)
);
-- pattern_adoptions: which hubs have adopted which patterns
CREATE TABLE pattern_adoptions (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
widget_pattern_id UUID NOT NULL REFERENCES widget_patterns(id),
adopting_hub_id UUID NOT NULL REFERENCES hubs(id),
pinned_version_id UUID REFERENCES widget_pattern_versions(id),
is_version_pinned BOOLEAN NOT NULL DEFAULT FALSE,
is_anonymous BOOLEAN NOT NULL DEFAULT FALSE, -- opt-out of aggregate feedback
adopted_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,
UNIQUE (widget_pattern_id, adopting_hub_id)
);
-- governance_templates: requirement distillation and decision templates
CREATE TABLE governance_templates (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
hub_id UUID NOT NULL REFERENCES hubs(id),
name TEXT NOT NULL,
description TEXT,
-- categories: array of annotation_category_registry names this template uses
-- Stored as JSONB array; each element must exist in annotation_category_registry
categories JSONB NOT NULL DEFAULT '[]',
template_body JSONB NOT NULL, -- structured template definition
is_published BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL
);
-- governance_template_clones: adoption record for governance templates
CREATE TABLE governance_template_clones (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
governance_template_id UUID NOT NULL REFERENCES governance_templates(id),
cloning_hub_id UUID NOT NULL REFERENCES hubs(id),
cloned_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,
UNIQUE (governance_template_id, cloning_hub_id)
);
```
---
## Tasks
### T01 — Schema: WidgetPattern, WidgetPatternVersion, PatternAdoption, GovernanceTemplate, GovernanceTemplateClone
```task
id: IHUB-WP-0011-T01
status: done
priority: high
state_hub_task_id: "9c26859d-d910-4c5d-a684-3d94ea8019d9"
```
Add all Phase 10 tables to `Application/Schema.sql` and write a migration.
**GAAF constraints:**
- `widget_patterns.widget_type` must FK to `widget_type_registry(name)` — not
TEXT, not CHECK, but a true FK to the registry
- `governance_templates.categories` is JSONB; validate each element against
`annotation_category_registry` in the controller layer (array FK not expressible
in SQL; controller validates at write time)
- No `HubRegistry` table — registry is a query over existing tables
**Migration file:** `Application/Migration/<timestamp>-ihf-phase10-hub-registry.sql`
Run `migrate` after writing.
---
### T02 — Hub Registry UI: browsable view over manifests + health snapshots + GAAF compliance indicator
```task
id: IHUB-WP-0011-T02
status: done
priority: high
state_hub_task_id: "718b93a7-4e0a-4f79-af15-53af13ef9a92"
```
**Controller:** `Web/Controller/HubRegistry.hs`
**Query:**
```sql
SELECT h.id, h.name, h.hub_kind, h.domain,
m.id AS manifest_id, m.status AS manifest_status,
m.declared_widget_types, m.declared_event_types,
m.declared_annotation_categories, m.declared_policy_scopes,
s.overall_score, s.snapshot_taken_at
FROM hubs h
LEFT JOIN hub_capability_manifests m
ON m.hub_id = h.id AND m.status = 'active'
LEFT JOIN LATERAL (
SELECT overall_score, snapshot_taken_at
FROM hub_health_snapshots
WHERE hub_id = h.id
ORDER BY snapshot_taken_at DESC LIMIT 1
) s ON TRUE
ORDER BY h.name
```
**GAAF compliance indicator per hub:**
- Has active manifest: `manifest_status = 'active'`
- Registered type count: length of each `declared_*` JSONB array
- Fitness function status: derive from `m.declared_widget_types` completeness
(flag if hub has widgets not in registry — approximate; full check is in CI)
**Views:** `Web/View/HubRegistry/Index.hs`, `Web/View/HubRegistry/Show.hs`
(Show renders full manifest vocabulary + health history + adopted patterns)
Add route and nav link.
---
### T03 — Widget Pattern Library: publish, list, show with version history
```task
id: IHUB-WP-0011-T03
status: done
priority: high
state_hub_task_id: "5d2ce269-25de-4251-afae-0478901f85f6"
```
**Controller:** `Web/Controller/WidgetPatterns.hs`
Actions:
- `WidgetPatternsAction` — list all published patterns with adopter count
- `ShowWidgetPatternAction { widgetPatternId }` — detail with version history
- `NewWidgetPatternAction` — form (hub selector, widget_type from registry)
- `CreateWidgetPatternAction` — validate `widget_type` exists in
`widget_type_registry`; if owned by a different hub, set `is_cross_hub = True`
- `EditWidgetPatternAction { widgetPatternId }` / `UpdateWidgetPatternAction`
- `PublishWidgetPatternAction { widgetPatternId }` — set `is_published = True`,
creates first `WidgetPatternVersion` with `version_number = 1`
**Cross-hub type check:**
```haskell
typeOwner <- query @WidgetTypeRegistry
|> filterWhere (#name, pattern.widgetType)
|> fetchOne
let isCrossHub = typeOwner.hubId /= Just pattern.hubId
```
**Views:** `Web/View/WidgetPatterns/{Index,Show,New,Edit}.hs`
---
### T04 — Pattern versioning: WidgetPatternVersion with pin/follow-latest per adoption
```task
id: IHUB-WP-0011-T04
status: done
priority: medium
state_hub_task_id: "33003835-48fd-45d1-addd-75db85340968"
```
**Actions to add to `WidgetPatterns` controller:**
- `PublishNewVersionAction { widgetPatternId }` — form asks for `definition`
(JSONB) and `changelog`; increments `version_number`; creates
`WidgetPatternVersion` record
- Show page lists all versions in descending order
**Adoption version choice:**
In `AdoptPatternAction` (T05), offer two options:
1. Follow latest — `is_version_pinned = False`, `pinned_version_id = Nothing`
2. Pin to current — `is_version_pinned = True`, `pinned_version_id = currentVersionId`
Show pinned vs follow-latest status in the adopter hub's pattern list.
---
### T05 — Pattern adoption workflow: adopt pattern, trigger manifest amendment draft
```task
id: IHUB-WP-0011-T05
status: done
priority: high
state_hub_task_id: "44b354ac-b94a-4c71-9c43-79f5e67f671f"
```
**Action:** `AdoptPatternAction { widgetPatternId }` in `WidgetPatterns` controller.
```haskell
-- 1. Resolve adopting hub (from session user's hub)
-- 2. Create PatternAdoption record
adoption <- newRecord @PatternAdoption
|> set #widgetPatternId widgetPatternId
|> set #adoptingHubId hubId
|> set #isAnonymous (param "isAnonymous")
|> createRecord
-- 3. Check if pattern's widget_type is in the hub's manifest
manifest <- query @HubCapabilityManifest
|> filterWhere (#hubId, hubId)
|> filterWhere (#status, "active")
|> fetchOneOrNothing
let needsAmendment = case manifest of
Nothing -> True
Just m -> not (pattern.widgetType `elem` m.declaredWidgetTypes)
-- 4. If amendment needed, create draft manifest
when needsAmendment $ do
let newTypes = maybe [pattern.widgetType]
(\m -> m.declaredWidgetTypes ++ [pattern.widgetType])
manifest
_draft <- newRecord @HubCapabilityManifest
|> set #hubId hubId
|> set #status "draft"
|> set #declaredWidgetTypes newTypes
-- ... carry over other declared types from existing manifest
|> createRecord
setSuccessMessage "Pattern adopted. A manifest amendment draft has been created — please review and activate it."
redirectTo (ShowHubCapabilityManifestAction draftId)
```
When no amendment is needed, redirect to hub's pattern list with success message.
---
### T06 — Governance Template Library: CRUD and clone with manifest amendment
```task
id: IHUB-WP-0011-T06
status: done
priority: medium
state_hub_task_id: "f31b86d3-573e-4a87-b179-609872565b0c"
```
**Controller:** `Web/Controller/GovernanceTemplates.hs`
Actions:
- `GovernanceTemplatesAction` — list published templates with clone count
- `ShowGovernanceTemplateAction { governanceTemplateId }` — detail with clones
- `NewGovernanceTemplateAction` / `CreateGovernanceTemplateAction` — validate
each category in the `categories` JSONB array exists in
`annotation_category_registry`
- `CloneGovernanceTemplateAction { governanceTemplateId }` — creates
`GovernanceTemplateClone`; checks each template category against the cloning
hub's `declared_annotation_categories`; if any missing, creates manifest
amendment draft with the new categories added
**Category validation helper:**
```haskell
validateCategories :: [Text] -> IO (Either [Text] ())
validateCategories cats = do
registered <- query @AnnotationCategoryRegistry
|> filterWhere (#isActive, True)
|> fetch
let known = map (.name) registered
let unknown = filter (`notElem` known) cats
pure $ if null unknown then Right () else Left unknown
```
**Views:** `Web/View/GovernanceTemplates/{Index,Show,New}.hs`
---
### T07 — Adoption tracking and aggregate friction/outcome view per pattern
```task
id: IHUB-WP-0011-T07
status: done
priority: medium
state_hub_task_id: "5642dd12-4255-42d7-9411-63e032cc2b57"
```
**On pattern Show page**, add aggregate panel (only for non-anonymous adopters):
```sql
SELECT
COUNT(pa.id) AS adopter_count,
AVG(fs.score) AS mean_friction_score,
COUNT(os.id) AS outcome_signal_count
FROM pattern_adoptions pa
JOIN widgets w
ON w.hub_id = pa.adopting_hub_id
AND w.widget_type = ? -- pattern's widget_type
LEFT JOIN friction_scores fs
ON fs.widget_id = w.id
LEFT JOIN outcome_signals os
ON os.widget_id = w.id
WHERE pa.widget_pattern_id = ?
AND pa.is_anonymous = FALSE
```
Show:
- Adopter count (total, including anonymous)
- Mean friction score across non-anonymous adopter hubs (NULL if none)
- Outcome signal count
- "N hubs opted out of aggregate feedback" note if `is_anonymous` count > 0
**Hub's adopted patterns list** (`Web/View/HubRegistry/Show.hs`): list all
`PatternAdoption` records for the hub with pinned/follow-latest status.
---
### T08 — Marketplace dashboard: search and browse patterns and templates
```task
id: IHUB-WP-0011-T08
status: done
priority: medium
state_hub_task_id: "01ea4d7d-cbd3-4149-b772-7e131f4f7e9c"
```
**Controller:** `Web/Controller/MarketplaceDashboard.hs`
Use `autoRefresh`. Two sections:
**Widget Patterns:**
- Full-text search: `WHERE to_tsvector('english', wp.name || ' ' || COALESCE(wp.description,'')) @@ plainto_tsquery(?)`
- Filter by `widget_type` (dropdown from `widget_type_registry`)
- Sort: Most adopted (default) | Recently published | Alphabetical
- Trending: most `pattern_adoptions` created in last 30 days
**Governance Templates:**
- Same search pattern over `governance_templates.name + description`
- Filter by category
- Sort: Most cloned | Recent | Alphabetical
**Layout:** Two-column tab layout (Widget Patterns | Governance Templates).
Hub Registry link at top of page.
**Views:** `Web/View/MarketplaceDashboard/Show.hs`
Add route and nav link ("Marketplace").
---
### T09 — API v2: hub registry and widget pattern endpoints
```task
id: IHUB-WP-0011-T09
status: done
priority: medium
state_hub_task_id: "34d3339a-cf17-4475-b848-eeb077ede8e6"
```
Add to `/api/v2/` surface (all behind `requireApiConsumer` + `checkRateLimitAndLog`):
**Endpoints:**
| Method | Path | Description |
|---|---|---|
| GET | `/api/v2/hub-registry` | List hubs with active manifest summary + GAAF indicator |
| GET | `/api/v2/hub-registry/:hubId` | Single hub detail (manifest vocabulary, health) |
| GET | `/api/v2/widget-patterns` | List published patterns (paginated) |
| GET | `/api/v2/widget-patterns/:id` | Pattern detail with version history |
| POST | `/api/v2/widget-patterns/:id/adopt` | Create PatternAdoption for authenticated consumer's hub |
**New controllers:**
- `Web/Controller/Api/V2/HubRegistry.hs`
- `Web/Controller/Api/V2/WidgetPatterns.hs`
**OpenAPI update:** Add paths to `Web/Controller/Api/V2/OpenApi.hs`. The
`widget_type` enum in widget pattern responses continues to reference the live
registry query (no hardcoding).
**Routes and Types:** Update `Web/Routes.hs` and `Web/Types.hs` for new actions.
---
### T10 — ARCHITECTURE-LAYERS.md scorecard update + exit criteria validation
```task
id: IHUB-WP-0011-T10
status: done
priority: medium
state_hub_task_id: "9af8cd05-7864-438d-92a2-052d0af3bcbc"
```
**Exit criteria checklist (from v0.2 spec §Phase 10):**
- [ ] Hub registry renders all registered hubs with active manifest vocabulary
and current health score
- [ ] Widget pattern library lists published patterns with version history;
each pattern's widget_type links to its registry entry
- [ ] A pattern can be published from one hub and adopted into another;
adoption triggers manifest amendment draft when new types are introduced
- [ ] Adoption tracking shows which hubs use which patterns
- [ ] Governance template cloning works end-to-end; new categories appear in
the adopting hub's manifest amendment
- [ ] Marketplace dashboard renders search and browse
- [ ] Hub registry GAAF compliance indicator renders correctly for all hubs
**Scorecard updates:**
Phase 10's primary improvement target is the **Customization layer** (currently
2.5 → target ≥3.0): the manifest amendment workflow (T05, T06) constitutes a
formal per-hub configuration contract with migration support, which is the
specific criterion for Customization improvement.
Overall target: ≥3.5 (Strong).
**CLAUDE.md updates:**
- Move IHUB-WP-0011 to completed list
- Set active workplan to IHUB-WP-0012 (Phase 11 — Advanced AI Federation)
- Update "Current state" description
**Commit all changes** and mark workplan `status: done`.