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

17 KiB
Raw Blame History

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 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 frozenwidgets, 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:

-- 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_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

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_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

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:

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 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

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 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:

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_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

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

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

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.