Files
inter-hub/docs/new-hub-quickstart.md
tegwick 4381768045
Some checks failed
Build and Deploy / build-push-deploy (push) Has been cancelled
test: add ops hub bootstrap smoke script
2026-05-19 02:49:14 +02:00

356 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.
```bash
export IHUB_BASE="http://127.0.0.1:8000"
export IHUB_OPERATOR_KEY="<existing-operator-api-key>"
```
### 2. Register the VSM Operations hub
```bash
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
```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"
}'
```
Then activate the returned manifest:
```bash
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
```bash
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:
```bash
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" \
-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
```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.
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
```
### 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
- `devenv` for 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
```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 2040 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:
```haskell
-- 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:
```bash
# 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) | ~35 min |
| Incremental reload (1 module changed) | ~515 s |
| Adding a new controller+view pair | ~1030 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
(planned; use the API recipe above today)
- 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
- [ ] 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.py` against 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` |