# 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 ```sql -- 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_kind` must be `'domain'` for bounded domain hubs, or `'shared'` for cross-domain service hubs. `'framework'` is reserved for inter-hub itself. - There is exactly one `framework` hub (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= capabilityDescription=Developer toolchain interaction tracking contact=platform-team@example.com ``` In the manifest **Edit** view, declare your type names as JSON arrays: ```json // 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: 1. Validates that each declared type name is either unregistered or already owned by your hub. 2. If any name is owned by a different hub, activation is blocked with a conflict message. 3. If all names are clear, each declared name is inserted into its registry table (`widget_type_registry`, `event_type_registry`, etc.) with `owner_hub_id = your hub id`. 4. The manifest `status` transitions from `draft` → `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 a `deprecated_in_favour_of` pointer 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= targetHubId= 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.