12 KiB
New Domain Hub — Quickstart Guide
Audience: A developer starting a new domain hub (dev-hub, ops-hub, fin-hub, etc.) that will live in its own repository and use inter-hub as the governance substrate.
Current state: inter-hub v0.2.0-alpha.1 exposes its supported integration
surface under /api/v2. The examples below use $IHUB_BASE; point it at the
environment you are bootstrapping against.
Two Patterns — Choose One
Pattern A: API Consumer Hub (any language, start today)
Your hub is a standalone application that talks to inter-hub via REST API. No Haskell required. Full framework services available from day one.
Best for: Hubs that already have a tech stack (Node, Python, Go, etc.), prototypes, or teams that want zero build overhead.
Pattern B: IHP Extension Hub (Haskell, shares build infra)
Your hub is a separate IHP project that runs alongside inter-hub, sharing the same Nix/GHC installation on haskelseed and optionally the same PostgreSQL cluster (different schema or database).
Best for: Hubs that need server-rendered UI, deep governance integration, or type-safe access to inter-hub's data model.
Pattern A — API Consumer Hub
1. Start with an operator API key
Every write call below requires Authorization: Bearer <key>. Use an existing
operator/admin API key for the first bootstrap call. New hub-specific keys can
then be created through the API and should replace the operator key for normal
runtime traffic.
export IHUB_BASE="http://127.0.0.1:8000"
export IHUB_OPERATOR_KEY="<existing-operator-api-key>"
2. Register the VSM Operations hub
curl -s -X POST "$IHUB_BASE/api/v2/hubs" \
-H "Authorization: Bearer $IHUB_OPERATOR_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Operations Hub",
"slug": "ops-hub",
"domain": "operations",
"hubKind": "domain",
"hubFamily": "vsm",
"vsmFunction": "operations",
"vsmSystem": "1"
}'
Save the returned id — this is your hubId for all subsequent calls.
3. Register and activate the ops-hub manifest
curl -s -X POST "$IHUB_BASE/api/v2/hub-capability-manifests" \
-H "Authorization: Bearer $IHUB_OPERATOR_KEY" \
-H "Content-Type: application/json" \
-d '{
"hubId": "<ops-hub-id>",
"manifestVersion": "1.0",
"declaredWidgetTypes": ["ops-endpoint-card"],
"declaredEventTypes": ["ops-endpoint-verified"],
"declaredAnnotationCategories": ["ops-risk"],
"declaredPolicyScopes": ["ops-internal"],
"capabilityDescription": "Operations inventory and endpoint verification",
"contact": "ops@example.com"
}'
Then activate the returned manifest:
curl -s -X POST "$IHUB_BASE/api/v2/hub-capability-manifests/<manifest-id>/activate" \
-H "Authorization: Bearer $IHUB_OPERATOR_KEY"
Activation registers the declared vocabulary. Domain-owned widget types, event types, annotation categories, and policy scopes must be declared here before use.
4. Create an ops-hub API consumer and key
curl -s -X POST "$IHUB_BASE/api/v2/api-consumers" \
-H "Authorization: Bearer $IHUB_OPERATOR_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "ops-hub",
"description": "Operations hub runtime client",
"hubCapabilityManifestId": "<active-manifest-id>",
"rateLimitPerMinute": 120,
"quotaPerDay": 50000
}'
Create the static key for the returned consumer:
curl -s -X POST "$IHUB_BASE/api/v2/api-consumers/<api-consumer-id>/api-keys" \
-H "Authorization: Bearer $IHUB_OPERATOR_KEY" \
-H "Content-Type: application/json" \
-d '{"scopes": "ops:write"}'
The response contains fullKey exactly once. Store it in the hub runtime
secret store and use it for all following calls:
export OPS_HUB_KEY="<fullKey-from-create-api-key-response>"
5. Register widgets
curl -s -X POST "$IHUB_BASE/api/v2/widgets" \
-H "Authorization: Bearer $OPS_HUB_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "CoulombCore Gitea Registry",
"widgetType": "ops-endpoint-card",
"hubId": "<ops-hub-id>",
"viewContext": "operations-inventory",
"policyScope": "ops-internal"
}'
6. Record interaction events
curl -s -X POST "$IHUB_BASE/api/v2/interaction-events" \
-H "Authorization: Bearer $OPS_HUB_KEY" \
-H "Content-Type: application/json" \
-d '{
"widgetId": "<widget-id>",
"eventType": "ops-endpoint-verified",
"viewContext": "registry-readiness",
"metadata": {
"service": "gitea",
"endpoint": "https://gitea.coulomb.social/v2/",
"result": "auth-challenge-ok"
}
}'
7. Verify the bootstrap
curl -s "$IHUB_BASE/api/v2/interaction-events?widgetId=<widget-id>&eventType=ops-endpoint-verified" \
-H "Authorization: Bearer $OPS_HUB_KEY"
The event should appear with the submitted metadata. If the API returns
event_type_not_in_manifest, check that the API consumer is bound to the
active ops-hub manifest and that the event type was declared before activation.
The same path is available as a smoke script:
IHUB_BASE="$IHUB_BASE" IHUB_OPERATOR_KEY="$IHUB_OPERATOR_KEY" \
scripts/ops-hub-bootstrap-smoke.py
8. What you get for free
Once events are flowing, the inter-hub framework automatically provides:
- Annotation collection on any widget
- Requirement candidate escalation from annotations
- Triage queue and governance lifecycle (Requirement → Decision → Deployment)
- AI-assisted requirement drafting (if AgentRegistration is configured)
- Outcome signals and regression detection
- Widget marketplace discovery
Your hub only needs to register its vocabulary, seed meaningful widgets, and POST events. Everything downstream is managed by inter-hub.
Pattern B — IHP Extension Hub (Haskell)
Prerequisites
The same build infrastructure used for inter-hub works directly:
- haskelseed VM (
192.168.178.135) with GHC 9.10.3 in the Nix store devenvfor reproducible environments- The painful one-time Nix setup is already done — a new IHP project reuses the same Nix store
Bootstrap a new hub repo
# On your workstation (Nix must be installed)
nix profile install nixpkgs#ihp-new
ihp-new dev-hub
cd dev-hub
# Edit devenv.nix to pin to the same IHP version as inter-hub (1.5.0)
# Then:
devenv up
The first devenv up on a fresh machine takes 20–40 min to fetch Nix
dependencies. On haskelseed, most dependencies are already in the Nix store
and the setup takes ~2 minutes.
Connect to inter-hub's API
Add the inter-hub API client to your hub. The simplest approach:
-- Application/Helper/InterHubClient.hs
module Application.Helper.InterHubClient where
import IHP.Prelude
import Network.HTTP.Simple
postEvent :: Text -> Text -> Text -> Value -> IO ()
postEvent apiKey widgetId eventType metadata = do
let req = setRequestMethod "POST"
$ setRequestHeader "Authorization" ["Bearer " <> cs apiKey]
$ setRequestHeader "Content-Type" ["application/json"]
$ setRequestBodyJSON (object
[ "widgetId" .= widgetId
, "eventType" .= eventType
, "metadata" .= metadata
])
$ parseRequest_ "http://127.0.0.1:8000/api/v2/interaction-events"
void $ httpLBS req
Shared database (optional)
If your hub needs read access to inter-hub's tables (e.g., to join against
requirements or decision_records), connect to the same PostgreSQL:
# In your hub's .env:
DATABASE_URL=postgresql://ihp:ihp@192.168.178.135/interhub
Your IHP app can then use query @DecisionRecord directly without going
through the API. This is appropriate for tightly-coupled hubs that are
part of the same operational boundary.
For loosely-coupled hubs (separate teams, separate deploy cadence), use the API only — do not share the database.
How fast is the Haskell build for a new hub?
A fresh IHP project with 10 controllers and 20 views compiles to ~150 modules (vs inter-hub's 616). With the Nix store already populated on haskelseed:
| Stage | Time |
|---|---|
First devenv up (Nix fetch) |
~2 min (store populated) |
| First GHCi load (150 modules) | ~3–5 min |
| Incremental reload (1 module changed) | ~5–15 s |
| Adding a new controller+view pair | ~10–30 s compile time |
This is practical for active development. The painful build experience with inter-hub was caused by its scale (616 modules, 12 phases worth of code) and the Alpine setup being done from scratch. A new hub starts small.
Honest Assessment: Is IHP a Good Framework for Domain Hubs?
Yes, with caveats.
Strengths:
- Type safety catches integration errors at compile time, not at 2am
- Server-rendered HSX views are fast to write once you know IHP conventions
- The query builder and auto-generated types eliminate a whole class of SQL bugs
- IHP's code generator scaffolds a controller+4 views in seconds
- Once the Nix environment is set up, it is reproducible — no "works on my machine"
Caveats:
- The initial Nix setup is still painful on a new machine (~1h)
- GHC error messages for type inference failures are dense
- No hot-reload for Haskell (GHCi restart is fast, but not instant)
- The
hub-coreshared library is planned but not yet implemented — each new hub currently duplicates boilerplate for API client setup, hub registration, and event posting
Bottom line: If you are already comfortable with Haskell and IHP, building domain hubs in the same stack is efficient and the type safety pays dividends quickly. If your team is not Haskell-native, Pattern A (API consumer) is the pragmatic choice — the API surface is stable and well-documented, and you can add a lightweight web layer in whatever language fits your team.
What hub-core Would Provide
The planned hub-core Haskell library (not yet implemented) would give
every domain hub:
HubRegistrationtypeclass — register with inter-hub on startupWidgetEnvelopehelpers — consistent widget wrapping across hubsInterHubClient— typed API client with retry and auth built inHubCapabilityManifestbootstrap — auto-activate manifest on startup (planned; use the API recipe above today)- Shared
defaultLayoutwith inter-hub navigation integration
Until hub-core exists, copy the client helper above and the 3-step
registration pattern into your new hub. It is ~50 lines of boilerplate.
Checklist for a New Hub
- Start with an existing operator API key
- Create ApiConsumer + ApiKey through
/api/v2/api-consumers - Record your hub ID and API key in the new hub's
.env - Register HubCapabilityManifest with domain type vocabulary through
/api/v2/hub-capability-manifests - Activate the manifest through
/api/v2/hub-capability-manifests/<id>/activate - Create at least one Widget per meaningful UI surface
- Instrument interactions with POST to
/api/v2/interaction-events - Verify events appear in inter-hub at
/InteractionEvents - Run
scripts/ops-hub-bootstrap-smoke.pyagainst a disposable or staging environment before adapting the recipe for another VSM hub - (Optional) Configure AgentRegistration and ModelRoutingPolicy for AI-assisted requirement drafting
- (Optional) Set up HubRoutingRules to route annotations to your hub's triage queue
Reference
| Resource | Location |
|---|---|
| API reference (OpenAPI) | $IHUB_BASE/api/v2/openapi.json |
| Swagger UI | $IHUB_BASE/api/v2/docs |
| Type registry browser | $IHUB_BASE/TypeRegistries/WidgetTypes |
| Domain hub extension guide | docs/domain-hub-extension-guide.md |
| IHP data and queries | docs/ihp-data-and-queries.md |
| IHP controllers and views | docs/ihp-controllers-views-forms.md |
| Functional module maturity | docs/functional-modules.md |
| IHF v0.2 specification | specs/InteractionHubFrameworkSpecification_v0.2.md |