Implements IHUB-WP-0009: closes four GAAF-2026 gaps before domain hub work begins. - TypeRegistry helper + controllers/views (hub_kind, hub_capability_manifest) - HubCapabilityManifest entity with validation and registry linkage - ARCHITECTURE-LAYERS.md + CI-enforced boundary contracts - Alembic migration 1743724800, fitness tests (Test/Architecture/) - GAAF spec, Operational Architecture spec, domain hub extension guide - Updates to CLAUDE.md, SCOPE.md, Schema.sql, Routes, FrontController, Types state_hub_sync: pending (tunnel was STALE at completion time; run fix-consistency) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
8.3 KiB
Domain Hub Extension Guide
Framework: IHF v0.2 + GAAF-2026 | Applies to: Phase 9+ domain hub implementations
This guide is for developers building a new domain hub (dev-hub, ops-hub, fin-hub, sec-hub) on top of inter-hub. It covers what the framework provides out of the box, how to register your domain's vocabulary, and how to stay compatible across framework upgrades.
What inter-hub Provides
Every domain hub built on inter-hub inherits the following services without any setup:
| Service | What it gives you |
|---|---|
| Event capture | POST /api/v1/interaction-events — record user interactions with a hub API key |
| Annotation | Structured feedback on any widget (friction, defects, wishes, policy concerns) |
| Requirement candidates | Automatic escalation from annotations; triage queue and lifecycle |
| Governance ledger | Decision records, rationale, and approved/rejected outcomes |
| AI assistance | Claude-powered summarization and requirement drafting via AgentProposal |
| Deployment + signals | DeploymentRecord → OutcomeSignal; regression detection |
| Observability | FrictionScore, BottleneckRecord, HubHealthSnapshot |
| Federation | CrossHubPropagation, WidgetOwnership, FederatedPolicyOverlay |
| Cross-framework adapters | EnvelopeEmissionContract, InteractionReportingContract |
| Archive + lineage | Soft-delete with full lineage inspector |
None of these require domain-specific configuration. They are activated as soon as you
create a Hub row and register Widget records.
Extension Registration in Three Steps
Domain hubs introduce vocabulary that the framework does not know about at installation
time: domain-specific widget types (e.g. dev-pipeline-run), event types
(e.g. fin-budget-alert-dismissed), annotation categories, and policy scopes.
You must register this vocabulary before widgets or events using these names can be validated by the framework.
Step 1 — Create a Hub row
-- Via the IHP UI at /hubs/new, or via a migration:
INSERT INTO hubs (id, name, slug, domain, hub_kind)
VALUES (uuid_generate_v4(), 'Dev Hub', 'dev-hub', 'dev.example.com', 'domain');
hub_kindmust be'domain'for bounded domain hubs, or'shared'for cross-domain service hubs.'framework'is reserved for inter-hub itself.- There is exactly one
frameworkhub (enforced by a unique partial index).
Step 2 — Create a HubCapabilityManifest in draft
Navigate to Extensions → New Manifest in the inter-hub UI, or via the API:
POST /HubCapabilityManifests (CreateHubCapabilityManifestAction)
hubId=<your hub id>
capabilityDescription=Developer toolchain interaction tracking
contact=platform-team@example.com
In the manifest Edit view, declare your type names as JSON arrays:
// Declared Widget Types
["dev-pipeline-run", "dev-pr-review", "dev-build-status"]
// Declared Event Types
["dev-pipeline-triggered", "dev-build-failed", "dev-pr-approved"]
// Declared Annotation Categories
["dev-flaky-test", "dev-merge-concern"]
// Declared Policy Scopes
["dev-ci-policy"]
Step 3 — Activate the manifest
Click Activate in the manifest UI, or:
GET /HubCapabilityManifests/{id}/activate (ActivateManifestAction)
On activation, the framework:
- Validates that each declared type name is either unregistered or already owned by your hub.
- If any name is owned by a different hub, activation is blocked with a conflict message.
- If all names are clear, each declared name is inserted into its registry table
(
widget_type_registry,event_type_registry, etc.) withowner_hub_id = your hub id. - The manifest
statustransitions fromdraft→active.
After activation, your types are:
- Validated by all IHF controllers (widgets, annotations, routing rules, API events)
- Enumerable by the Phase 9 OpenAPI specification
- Discoverable by other hubs via the Extensions page
Naming Your Types
| Context | Convention | Example |
|---|---|---|
| Framework-level types | No prefix, lowercase-hyphenated | chart, form, clicked |
| Domain-owned types | Prefixed with domain shortcode | dev-pipeline-run, fin-budget-alert |
| Shared hub types | Prefixed with service shortcode | state-workstream, gov-decision |
Rules:
- Names are permanent — once registered, they cannot be deleted or renamed.
Use
status = 'deprecated'with adeprecated_in_favour_ofpointer if you need to transition to a new name. - Names are globally unique per registry table. Two hubs cannot own the same name. Use domain prefixes to avoid collisions.
- Names must be lowercase. Hyphens are preferred over underscores for widget/event types; underscores are accepted for annotation categories and policy scopes.
Using Framework-Level Types
Framework-level types (those with owner_hub_id IS NULL) are available to all hubs without
any registration. You do not need to declare them in your manifest.
Examples of framework-level widget types: chart, form, table, action, panel, nav
Examples of framework-level event types: clicked, viewed, submitted, dismissed, errored
Check the Type Registries UI (/TypeRegistries/WidgetTypes) to see all active framework types
before creating domain-specific alternatives.
Cross-Hub Routing
If your domain hub should receive requirement candidates routed from other hubs, configure
HubRoutingRule entries:
POST /HubRoutingRules (CreateHubRoutingRuleAction)
sourceHubId=<framework hub>
targetHubId=<your domain hub id>
matchCategory=dev-flaky-test
priority=10
The matchCategory and matchWidgetType fields are validated against their registries.
You must activate your manifest before creating routing rules that reference your domain types.
Interpreting Maturity Labels
When depending on IHF modules in your domain hub, check docs/functional-modules.md
for the current maturity of each module:
| Maturity | What it means for your hub |
|---|---|
| Stable | Safe to depend on. Breaking changes require a major version bump with migration path. |
| Beta | Core functionality is solid but edge-case fields or computation details may change with a minor-version notice. Suitable for production with awareness. |
| Experimental | Internal prototyping only. May change without notice. Do not build production features on Experimental modules. |
| Deprecated | Will be removed in the next major version. A replacement is documented in the relevant contract file. |
The maturity badge is displayed on contract show pages in the inter-hub UI.
FAQ
Can two hubs declare the same type name? No. Type names are globally unique per registry. Activation order determines ownership. If you need a name that another hub owns, coordinate with that hub's contact to either share the type (they keep ownership, you use it) or use a domain-prefixed variant.
Can a type be renamed?
No. Names are permanent. To transition: deprecate the old name with deprecated_in_favour_of
pointing to the new name, then register the new name (via a new manifest entry or the
Type Registry UI).
Can a hub retire its manifest?
Yes. Retiring a manifest sets its status to retired; the hub's types remain in registries
and continue to validate. Retiring is appropriate when a hub is decommissioned or merged.
Orphaned types (owned by a retired hub's manifest) should be reassigned via the
Type Registry UI or deprecated.
Do I need a manifest if I only use framework-level types? No. A manifest is only needed if your hub introduces new type names. If you use only existing framework types, you can create widgets and events immediately.
What if my manifest activation fails with a conflict? The activation endpoint returns the conflicting type names and which hub owns them. Either rename your types (before activation; names in a draft manifest can still be changed) or coordinate with the owning hub.
Can I add new types to an active manifest?
Currently, activated manifests are read-only on the declared_* arrays. The amendment
workflow is: retire the active manifest, create a new draft, declare all types (old + new),
activate. A DraftAmendmentAction that merges types without retiring is planned for Phase 10.