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>
17 KiB
id, type, title, domain, repo, status, owner, topic_slug, created, updated, state_hub_sync, state_hub_workstream_id
| id | type | title | domain | repo | status | owner | topic_slug | created | updated | state_hub_sync | state_hub_workstream_id |
|---|---|---|---|---|---|---|---|---|---|---|---|
| IHUB-WP-0011 | workplan | IHF Phase 10 — Hub Registry and Widget Marketplace | inter_hub | inter-hub | done | custodian | inter_hub | 2026-04-01 | 2026-04-01 | done | 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:
HubCapabilityManifesttable and activation workflow operational ✓- Four type registries seeded and validated ✓
/api/v2/REST API with OpenAPI 3.1 spec live ✓HubHealthSnapshotdata 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:
- No bare TEXT type discriminators — any new type discriminator column must FK to a registry table or carry a CHECK constraint.
- WidgetPattern.widget_type must FK to widget_type_registry — no unregistered widget types may be referenced by a pattern.
- GovernanceTemplate categories must FK to annotation_category_registry — template categories are registered vocabulary, not free text.
- Core tables are frozen —
widgets,interaction_events,annotations,hubs, and Phase 1–4 dependents must not gain columns without a corresponding/contracts/core/update. - Append-only invariant is permanent — no migration may add UPDATE or DELETE
capability to
interaction_eventsoroutcome_signals. - 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
HubCapabilityManifestvia 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:
-- 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
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_typemust FK towidget_type_registry(name)— not TEXT, not CHECK, but a true FK to the registrygovernance_templates.categoriesis JSONB; validate each element againstannotation_category_registryin the controller layer (array FK not expressible in SQL; controller validates at write time)- No
HubRegistrytable — 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
id: IHUB-WP-0011-T02
status: done
priority: high
state_hub_task_id: "718b93a7-4e0a-4f79-af15-53af13ef9a92"
Controller: Web/Controller/HubRegistry.hs
Query:
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_typescompleteness (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
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 countShowWidgetPatternAction { widgetPatternId }— detail with version historyNewWidgetPatternAction— form (hub selector, widget_type from registry)CreateWidgetPatternAction— validatewidget_typeexists inwidget_type_registry; if owned by a different hub, setis_cross_hub = TrueEditWidgetPatternAction { widgetPatternId }/UpdateWidgetPatternActionPublishWidgetPatternAction { widgetPatternId }— setis_published = True, creates firstWidgetPatternVersionwithversion_number = 1
Cross-hub type check:
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
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 fordefinition(JSONB) andchangelog; incrementsversion_number; createsWidgetPatternVersionrecord- Show page lists all versions in descending order
Adoption version choice:
In AdoptPatternAction (T05), offer two options:
- Follow latest —
is_version_pinned = False,pinned_version_id = Nothing - 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
id: IHUB-WP-0011-T05
status: done
priority: high
state_hub_task_id: "44b354ac-b94a-4c71-9c43-79f5e67f671f"
Action: AdoptPatternAction { widgetPatternId } in WidgetPatterns controller.
-- 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
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 countShowGovernanceTemplateAction { governanceTemplateId }— detail with clonesNewGovernanceTemplateAction/CreateGovernanceTemplateAction— validate each category in thecategoriesJSONB array exists inannotation_category_registryCloneGovernanceTemplateAction { governanceTemplateId }— createsGovernanceTemplateClone; checks each template category against the cloning hub'sdeclared_annotation_categories; if any missing, creates manifest amendment draft with the new categories added
Category validation helper:
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
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):
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_anonymouscount > 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
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 fromwidget_type_registry) - Sort: Most adopted (default) | Recently published | Alphabetical
- Trending: most
pattern_adoptionscreated 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
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.hsWeb/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
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.