generated from coulomb/repo-seed
Some checks failed
Test / test (push) Has been cancelled
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>
496 lines
17 KiB
Markdown
496 lines
17 KiB
Markdown
---
|
||
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 1–9 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 1–4 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`.
|