2026-04-29 14:53:19 +02:00
|
|
|
|
# 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.
|
|
|
|
|
|
|
2026-05-19 02:40:21 +02:00
|
|
|
|
**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.
|
2026-04-29 14:53:19 +02:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 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
|
|
|
|
|
|
|
2026-05-19 02:40:21 +02:00
|
|
|
|
### 1. Start with an operator API key
|
2026-04-29 14:53:19 +02:00
|
|
|
|
|
2026-05-19 02:40:21 +02:00
|
|
|
|
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.
|
2026-04-29 14:53:19 +02:00
|
|
|
|
|
2026-05-19 02:40:21 +02:00
|
|
|
|
```bash
|
|
|
|
|
|
export IHUB_BASE="http://127.0.0.1:8000"
|
|
|
|
|
|
export IHUB_OPERATOR_KEY="<existing-operator-api-key>"
|
|
|
|
|
|
```
|
2026-04-29 14:53:19 +02:00
|
|
|
|
|
2026-05-19 02:40:21 +02:00
|
|
|
|
### 2. Register the VSM Operations hub
|
2026-04-29 14:53:19 +02:00
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-05-19 02:40:21 +02:00
|
|
|
|
curl -s -X POST "$IHUB_BASE/api/v2/hubs" \
|
|
|
|
|
|
-H "Authorization: Bearer $IHUB_OPERATOR_KEY" \
|
2026-04-29 14:53:19 +02:00
|
|
|
|
-H "Content-Type: application/json" \
|
|
|
|
|
|
-d '{
|
2026-05-19 02:40:21 +02:00
|
|
|
|
"name": "Operations Hub",
|
|
|
|
|
|
"slug": "ops-hub",
|
|
|
|
|
|
"domain": "operations",
|
|
|
|
|
|
"hubKind": "domain",
|
|
|
|
|
|
"hubFamily": "vsm",
|
|
|
|
|
|
"vsmFunction": "operations",
|
|
|
|
|
|
"vsmSystem": "1"
|
2026-04-29 14:53:19 +02:00
|
|
|
|
}'
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Save the returned `id` — this is your `hubId` for all subsequent calls.
|
|
|
|
|
|
|
2026-05-19 02:40:21 +02:00
|
|
|
|
### 3. Register and activate the ops-hub manifest
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
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"
|
|
|
|
|
|
}'
|
|
|
|
|
|
```
|
2026-04-29 14:53:19 +02:00
|
|
|
|
|
2026-05-19 02:40:21 +02:00
|
|
|
|
Then activate the returned manifest:
|
2026-04-29 14:53:19 +02:00
|
|
|
|
|
2026-05-19 02:40:21 +02:00
|
|
|
|
```bash
|
|
|
|
|
|
curl -s -X POST "$IHUB_BASE/api/v2/hub-capability-manifests/<manifest-id>/activate" \
|
|
|
|
|
|
-H "Authorization: Bearer $IHUB_OPERATOR_KEY"
|
2026-04-29 14:53:19 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-05-19 02:40:21 +02:00
|
|
|
|
Activation registers the declared vocabulary. Domain-owned widget types,
|
|
|
|
|
|
event types, annotation categories, and policy scopes must be declared here
|
|
|
|
|
|
before use.
|
2026-04-29 14:53:19 +02:00
|
|
|
|
|
2026-05-19 02:40:21 +02:00
|
|
|
|
### 4. Create an ops-hub API consumer and key
|
2026-04-29 14:53:19 +02:00
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-05-19 02:40:21 +02:00
|
|
|
|
curl -s -X POST "$IHUB_BASE/api/v2/api-consumers" \
|
|
|
|
|
|
-H "Authorization: Bearer $IHUB_OPERATOR_KEY" \
|
2026-04-29 14:53:19 +02:00
|
|
|
|
-H "Content-Type: application/json" \
|
|
|
|
|
|
-d '{
|
2026-05-19 02:40:21 +02:00
|
|
|
|
"name": "ops-hub",
|
|
|
|
|
|
"description": "Operations hub runtime client",
|
|
|
|
|
|
"hubCapabilityManifestId": "<active-manifest-id>",
|
|
|
|
|
|
"rateLimitPerMinute": 120,
|
|
|
|
|
|
"quotaPerDay": 50000
|
2026-04-29 14:53:19 +02:00
|
|
|
|
}'
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-05-19 02:40:21 +02:00
|
|
|
|
Create the static key for the returned consumer:
|
2026-04-29 14:53:19 +02:00
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-05-19 02:40:21 +02:00
|
|
|
|
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:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
export OPS_HUB_KEY="<fullKey-from-create-api-key-response>"
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 5. Register widgets
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
curl -s -X POST "$IHUB_BASE/api/v2/interaction-events" \
|
|
|
|
|
|
-H "Authorization: Bearer $OPS_HUB_KEY" \
|
2026-04-29 14:53:19 +02:00
|
|
|
|
-H "Content-Type: application/json" \
|
|
|
|
|
|
-d '{
|
|
|
|
|
|
"widgetId": "<widget-id>",
|
2026-05-19 02:40:21 +02:00
|
|
|
|
"eventType": "ops-endpoint-verified",
|
|
|
|
|
|
"viewContext": "registry-readiness",
|
|
|
|
|
|
"metadata": {
|
|
|
|
|
|
"service": "gitea",
|
|
|
|
|
|
"endpoint": "https://gitea.coulomb.social/v2/",
|
|
|
|
|
|
"result": "auth-challenge-ok"
|
|
|
|
|
|
}
|
2026-04-29 14:53:19 +02:00
|
|
|
|
}'
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-05-19 02:40:21 +02:00
|
|
|
|
### 7. Verify the bootstrap
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
2026-05-19 02:49:14 +02:00
|
|
|
|
The same path is available as a smoke script:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
IHUB_BASE="$IHUB_BASE" IHUB_OPERATOR_KEY="$IHUB_OPERATOR_KEY" \
|
|
|
|
|
|
scripts/ops-hub-bootstrap-smoke.py
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-05-19 02:40:21 +02:00
|
|
|
|
### 8. What you get for free
|
2026-04-29 14:53:19 +02:00
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
2026-05-19 02:40:21 +02:00
|
|
|
|
Your hub only needs to register its vocabulary, seed meaningful widgets, and
|
|
|
|
|
|
POST events. Everything downstream is managed by inter-hub.
|
2026-04-29 14:53:19 +02:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Pattern B — IHP Extension Hub (Haskell)
|
|
|
|
|
|
|
|
|
|
|
|
### Prerequisites
|
|
|
|
|
|
|
|
|
|
|
|
The same build infrastructure used for inter-hub works directly:
|
2026-06-14 16:47:24 +02:00
|
|
|
|
- haskelseed VM (`192.168.178.135`) as the CI/Nix build runner with GHC 9.10.3
|
|
|
|
|
|
in the Nix store
|
2026-04-29 14:53:19 +02:00
|
|
|
|
- `devenv` for reproducible environments
|
|
|
|
|
|
- The painful one-time Nix setup is already done — a new IHP project reuses
|
2026-06-14 16:47:24 +02:00
|
|
|
|
the same Nix store when built on the runner
|
2026-04-29 14:53:19 +02:00
|
|
|
|
|
|
|
|
|
|
### Bootstrap a new hub repo
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
# 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
|
2026-06-14 16:47:24 +02:00
|
|
|
|
dependencies. On haskelseed, most dependencies are already in the Nix store,
|
|
|
|
|
|
which is why it is useful as a build runner. It is not the production runtime
|
|
|
|
|
|
host for inter-hub.
|
2026-04-29 14:53:19 +02:00
|
|
|
|
|
|
|
|
|
|
### Connect to inter-hub's API
|
|
|
|
|
|
|
|
|
|
|
|
Add the inter-hub API client to your hub. The simplest approach:
|
|
|
|
|
|
|
|
|
|
|
|
```haskell
|
|
|
|
|
|
-- Application/Helper/InterHubClient.hs
|
|
|
|
|
|
module Application.Helper.InterHubClient where
|
|
|
|
|
|
|
|
|
|
|
|
import IHP.Prelude
|
|
|
|
|
|
import Network.HTTP.Simple
|
|
|
|
|
|
|
|
|
|
|
|
postEvent :: Text -> Text -> Text -> Value -> IO ()
|
2026-05-19 02:40:21 +02:00
|
|
|
|
postEvent apiKey widgetId eventType metadata = do
|
2026-04-29 14:53:19 +02:00
|
|
|
|
let req = setRequestMethod "POST"
|
|
|
|
|
|
$ setRequestHeader "Authorization" ["Bearer " <> cs apiKey]
|
|
|
|
|
|
$ setRequestHeader "Content-Type" ["application/json"]
|
|
|
|
|
|
$ setRequestBodyJSON (object
|
|
|
|
|
|
[ "widgetId" .= widgetId
|
|
|
|
|
|
, "eventType" .= eventType
|
2026-05-19 02:40:21 +02:00
|
|
|
|
, "metadata" .= metadata
|
2026-04-29 14:53:19 +02:00
|
|
|
|
])
|
2026-05-19 02:40:21 +02:00
|
|
|
|
$ parseRequest_ "http://127.0.0.1:8000/api/v2/interaction-events"
|
2026-04-29 14:53:19 +02:00
|
|
|
|
void $ httpLBS req
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### Shared database (optional)
|
|
|
|
|
|
|
2026-06-14 16:47:24 +02:00
|
|
|
|
Production inter-hub runs on Railiance01 K3s and uses PostgreSQL inside the
|
|
|
|
|
|
Railiance cluster. Do not connect new hubs to a haskelseed database. Prefer the
|
|
|
|
|
|
API boundary for extension hubs; request a governed read model or dedicated
|
|
|
|
|
|
service account if a hub truly needs database-level integration.
|
2026-04-29 14:53:19 +02:00
|
|
|
|
|
|
|
|
|
|
### 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-core` shared 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:
|
|
|
|
|
|
|
|
|
|
|
|
- `HubRegistration` typeclass — register with inter-hub on startup
|
|
|
|
|
|
- `WidgetEnvelope` helpers — consistent widget wrapping across hubs
|
|
|
|
|
|
- `InterHubClient` — typed API client with retry and auth built in
|
|
|
|
|
|
- `HubCapabilityManifest` bootstrap — auto-activate manifest on startup
|
2026-05-19 02:40:21 +02:00
|
|
|
|
(planned; use the API recipe above today)
|
2026-04-29 14:53:19 +02:00
|
|
|
|
- Shared `defaultLayout` with 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
|
|
|
|
|
|
|
2026-05-19 02:40:21 +02:00
|
|
|
|
- [ ] Start with an existing operator API key
|
|
|
|
|
|
- [ ] Create ApiConsumer + ApiKey through `/api/v2/api-consumers`
|
2026-04-29 14:53:19 +02:00
|
|
|
|
- [ ] Record your hub ID and API key in the new hub's `.env`
|
2026-05-19 02:40:21 +02:00
|
|
|
|
- [ ] Register HubCapabilityManifest with domain type vocabulary through `/api/v2/hub-capability-manifests`
|
|
|
|
|
|
- [ ] Activate the manifest through `/api/v2/hub-capability-manifests/<id>/activate`
|
2026-04-29 14:53:19 +02:00
|
|
|
|
- [ ] 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`
|
2026-05-19 02:49:14 +02:00
|
|
|
|
- [ ] Run `scripts/ops-hub-bootstrap-smoke.py` against a disposable or staging
|
|
|
|
|
|
environment before adapting the recipe for another VSM hub
|
2026-04-29 14:53:19 +02:00
|
|
|
|
- [ ] (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 |
|
|
|
|
|
|
|----------|----------|
|
2026-05-19 02:40:21 +02:00
|
|
|
|
| API reference (OpenAPI) | `$IHUB_BASE/api/v2/openapi.json` |
|
|
|
|
|
|
| Swagger UI | `$IHUB_BASE/api/v2/docs` |
|
|
|
|
|
|
| Type registry browser | `$IHUB_BASE/TypeRegistries/WidgetTypes` |
|
2026-04-29 14:53:19 +02:00
|
|
|
|
| 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` |
|