Compare commits

..

24 Commits

Author SHA1 Message Date
98ed061649 Add registry/NO_CAPABILITIES.md (reuse-surface REUSE-WP-0017-T03)
Reviewed for the reuse-surface capability registry coverage campaign;
no reusable capability to register at this time. See file for rationale.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-06 18:50:37 +02:00
27970faa0a chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-06-22:
  - update .custodian-brief.md for helix-forge
2026-06-22 23:22:13 +02:00
071f861a65 Normalize agent instructions and workplan frontmatter (STATE-WP-0067)
- Align agent files with on-disk workplan prefixes (infer from workplan ids)
- Set workplan domain to registered domain_slug; add topic_slug where applicable
- Repair frontmatter delimiter formatting; migrate legacy task status literals
- Regenerate AGENTS.md, CLAUDE.md, and .claude/rules from State Hub templates
2026-06-22 23:16:25 +02:00
a1e3963002 Human-review .repo-classification.yaml (CUST-WP-0050 follow-up) 2026-06-22 17:56:16 +02:00
94793f6544 Add .repo-classification.yaml (CUST-WP-0050 T11 agent first-pass) 2026-06-22 17:47:36 +02:00
0b1ea48352 chore(workplan): sync HF-WP-0003 state hub task IDs
State hub consistency writeback after OpenBao KeyCape login overlay
implementation and follow-up OIDC callback fixes in railiance-platform.
2026-06-19 21:22:05 +02:00
0d6d76f230 Finish HF-WP-0003 OpenBao KeyCape login overlay workplan
Mark all tasks done after live deployment of the railiance-platform overlay
gateway and update the phased checklist in OpenBaoIntroduction.md.
2026-06-19 20:28:16 +02:00
02f11f8a9b Add HF-WP-0003 workplan for OpenBao KeyCape login overlay
Define upgrade-safe overlay strategy to streamline the bao.coulomb.social
login mask as "Sign in with KeyCape" with hidden netkingdom/platform-admin
presets.
2026-06-19 20:21:53 +02:00
32c552809b Link OpenBao custody direction in INTENT and SCOPE
Add a platform secret custody section and machine-readable anchor in INTENT.md,
extend SCOPE discovery guidance, and point both to docs/OpenBaoIntroduction.md
without duplicating railiance-platform or net-kingdom operational detail.
2026-06-19 20:13:11 +02:00
9579848c45 Add OpenBao introduction anchor for platform custody model
Document why OpenBao exists across HelixForge, NetKingdom, and Coulomb,
how login fields and multi-tenant secret ownership map to paths and policies,
and the phased rollout from platform bootstrap to tenant-scoped custody.
2026-06-19 20:11:07 +02:00
632afffc91 chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-06-19:
  - update .custodian-brief.md for helix-forge
2026-06-19 19:11:38 +02:00
023eb20a14 Finish HF-WP-0001: custodied runtime key and production API verification
Close T04 after storing the ops-hub runtime key in OpenBao and verifying token
exchange plus hub-registry access. Close T10 after confirming production
Inter-Hub image eed4322 fixes COUNT decode failures for widget creation and
hub-registry reads.
2026-06-19 19:11:28 +02:00
215e62a221 Record ops-hub bootstrap progress and add API bootstrap helper
Document the 2026-06-15 attended Inter-Hub bootstrap: hub row, active manifest,
widget seeding, and runtime API key creation. Add scripts/ops-hub-bootstrap-api.py,
extend the SQL fallback for widget versions and the first Gitea event, refresh
OpsHubBootstrapRunbook, and inline credential-routing guidance for agents.
2026-06-19 19:09:21 +02:00
3638ee14ad Finish HF-WP-0002: live netkingdom OIDC mount and platform-admin roles
Mark the OpenBao browser UI workplan finished after applying the pending T04
live configuration: enabled auth/netkingdom, aligned platform-admin redirect
URIs on both netkingdom and keycape mounts, and recorded verification evidence.
2026-06-19 19:05:29 +02:00
7becd45eea chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-06-19:
  - update .custodian-brief.md for helix-forge
2026-06-19 16:10:21 +02:00
468b1386fa Add capability registry scaffold (REUSE-WP-0014-T04 B02)
Empty helix_forge registry layout for federation publishing.
2026-06-16 01:52:40 +02:00
081ef10bc5 chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-06-15:
  - update .custodian-brief.md for helix-forge
2026-06-15 23:05:14 +02:00
e8492f153a chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-06-15:
  - update .custodian-brief.md for helix-forge
2026-06-15 23:03:57 +02:00
0057176b31 chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-06-15:
  - update .custodian-brief.md for helix-forge
2026-06-15 16:35:54 +02:00
d7821c8c62 chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-06-15:
  - update .custodian-brief.md for helix-forge
2026-06-15 14:44:20 +02:00
5eda99f120 chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-06-15:
  - update .custodian-brief.md for helix-forge
2026-06-15 14:11:48 +02:00
426a943bb9 chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-06-15:
  - update .custodian-brief.md for helix-forge
2026-06-15 13:30:51 +02:00
386036afb1 chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-06-15:
  - update .custodian-brief.md for helix-forge
2026-06-15 02:03:30 +02:00
071b4f2bbe chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-06-15:
  - HF-WP-0002-T05: wait → progress
2026-06-15 02:03:24 +02:00
22 changed files with 1898 additions and 102 deletions

View File

@@ -0,0 +1,50 @@
# Credential and access routing
**Audience:** Codex, Claude Code, Grok, and custodian agents that call **llm-connect**
for inference. Run this check **before** requesting secrets, API keys, SSH access,
login tokens, or database passwords — in any repo, not only `ops-warden`.
ops-warden **issues SSH certificates only** (`warden sign`, `cert_command`). Every
other credential need belongs to another subsystem. **Do not** message
`ops-warden` on State Hub expecting a secret value; the reply is a pointer, not a key.
### Lookup (do this first)
```bash
warden route find "<describe your need>" --json
warden route show <catalog-id> --json
```
Requires the `warden` CLI from `~/ops-warden` (`uv tool install .` or `uv run warden`).
| Agent runtime | How to orient |
| --- | --- |
| **Codex / Grok** (shell, HTTP State Hub) | `warden route` commands above; inbox `to_agent=helix-forge` is for coordination, not secret vending |
| **Claude Code** (MCP when available) | `get_domain_summary("custodian")` for workstreams; **still** use `warden route` for credential ownership |
| **llm-connect** (inference service) | Never put secret retrieval in prompts; route custody to OpenBao/operator paths surfaced by `warden route` |
### Quick routing table
| I need… | Owner | ops-warden executes? |
| --- | --- | --- |
| SSH cert (`adm`/`agt`/`atm`) | ops-warden | **Yes**`warden sign` |
| API key, DB password, provider token | OpenBao (`railiance-platform`) | No — route only |
| Login / OIDC / MFA | key-cape / Keycloak | No — route only |
| Authorization decision | flex-auth | No — route only |
| activity-core → issue-core emission | activity-core + issue-core | No — `warden route show activity-core-issue-sink` |
| SSH tunnel | ops-bridge (+ `cert_command` from warden) | No — route only |
### Anti-patterns (do not do these)
- `POST /messages/` to `ops-warden` asking for `ISSUE_CORE_API_KEY`, `OPENROUTER_API_KEY`, etc.
- Inventing `warden secret`, `warden login`, `warden bao`, `warden tunnel` — they do not exist
- Pasting secrets into Git, State Hub, workplans, logs, or chat
### Other capabilities (reuse-surface)
Non-credential capabilities are usually discovered through **reuse-surface** federation
(`reuse-surface` registry / `capability.*` indexes). Credential routing is inlined in
every repo's agent instructions because it is high-frequency, high-risk, and easy to
get wrong.
**Canon:** `~/ops-warden/wiki/CredentialRouting.md` · catalog `~/ops-warden/registry/routing/catalog.yaml`

View File

@@ -1,11 +1,11 @@
## First Session Protocol
Triggered when `get_domain_summary("helix_forge")` shows **no workstreams**.
Triggered when `get_domain_summary("infotech")` shows **no workstreams**.
The project is registered but work has not yet been structured.
**Step 1 — Read, don't write**
- `~/the-custodian/canon/projects/helix_forge/project_charter_v0.1.md` — purpose, scope
- `~/the-custodian/canon/projects/helix_forge/roadmap_v0.1.md` — planned phases
- `~/the-custodian/canon/projects/infotech/project_charter_v0.1.md` — purpose, scope
- `~/the-custodian/canon/projects/infotech/roadmap_v0.1.md` — planned phases
- Scan repo root: README, directory structure, existing code or docs
**Step 2 — Survey in-progress work**
@@ -17,7 +17,7 @@ roadmap phase. **Wait for approval before creating.**
**Step 4 — Create workplan file first, then DB record (ADR-001)**
```
workplans/helix-forge-WP-NNNN-<slug>.md ← write this first
workplans/HF-WP-NNNN-<slug>.md ← write this first
```
Then register in the hub:
```
@@ -28,7 +28,7 @@ create_task(workstream_id="<id>", title="...", priority="high|medium|low")
**Step 5 — Record the setup**
```
add_progress_event(
summary="First session: structured helix_forge into N workstreams, M tasks",
summary="First session: structured infotech into N workstreams, M tasks",
event_type="milestone",
topic_id="f39fa2a3-c491-414c-a91b-b4c5fcc6139c",
detail={"workstreams": [...], "tasks_created": M}

View File

@@ -1,5 +1,5 @@
**Purpose:** Capability-first development ecosystem for evolving reusable software capabilities from natural-language intent through architecture-aware discovery, validation, implementation, feedback, and reuse.
**Domain:** helix_forge
**Domain:** infotech
**Repo slug:** helix-forge
**Topic ID:** f39fa2a3-c491-414c-a91b-b4c5fcc6139c

View File

@@ -1,6 +1,7 @@
## Session Protocol
State Hub: http://127.0.0.1:8000
Dev Hub (State Hub API): http://127.0.0.1:8000
MCP server name in `~/.claude.json`: `dev-hub`
**Step 1 — Orient**
@@ -10,7 +11,7 @@ cat .custodian-brief.md
```
Then call the MCP tool for richer cross-domain context when MCP tools are exposed:
```
get_domain_summary("helix_forge")
get_domain_summary("infotech")
```
If MCP tools are unavailable in the current agent session, use the REST API:
```bash
@@ -39,11 +40,11 @@ curl -s -X PATCH "http://127.0.0.1:8000/messages/<id>/read" \
ls workplans/
```
For each file with `status: ready`, `active`, or `blocked`, note pending
`todo`/`in_progress` tasks.
`wait`/`todo`/`progress` tasks.
**Step 4 — Present brief**
1. **Active workstreams** for `helix_forge` — title, task counts, blocking decisions
1. **Active workstreams** for `infotech` — title, task counts, blocking decisions
2. **Pending tasks** from `workplans/` + any `[repo:helix-forge]` hub tasks
3. **Goal guidance** — if `goal_guidance` in summary:
- `needs_workplan`: surface as top action — *"Repo goal '{title}' has no workplan yet"*

View File

@@ -1,7 +1,7 @@
## Workplan Convention (ADR-001)
File location: `workplans/helix-forge-WP-NNNN-<slug>.md`
ID prefix: `HELIX-WP`
File location: `workplans/HF-WP-NNNN-<slug>.md`
ID prefix: `HF-WP-`
Work items originate as files in this repo **before** being registered in the hub.
@@ -12,7 +12,7 @@ repo state, and `finished` when implementation is complete. `stalled` and
`needs_review` are derived health labels, not stored statuses.
Closed workplans may be moved to `workplans/archived/` with a completion-date
prefix: `YYMMDD-helix-forge-WP-NNNN-<slug>.md`. The frontmatter id remains
prefix: `YYMMDD-HF-WP-NNNN-<slug>.md`. The frontmatter id remains
unchanged; the prefix is only for quick visual reference.
Small opportunistic tasks discovered during another session use **Ad Hoc Tasks**:
@@ -25,4 +25,16 @@ Ecosystem todos from other agents arrive as `[repo:helix-forge]` hub tasks —
visible at session start. Pick one up by creating the workplan file, then registering
the workstream.
Task blocks use this shape:
```task
id: HF-WP-NNNN-T01
status: wait | todo | progress | done | cancel
priority: high | medium | low
state_hub_task_id: "<uuid>" # written by fix-consistency — do not edit
```
Status progression is `todo``progress``done`; use `wait` for waiting or
blocked work and `cancel` for stopped work.
<!-- Ralph Loop rules and HEUREKA sequence: ~/.claude/CLAUDE.md — do not duplicate here -->

View File

@@ -1,40 +1,18 @@
<!-- custodian-brief: generated by fix-consistency — do not edit manually -->
# Custodian Brief — helix-forge
**Domain:** helix_forge
**Last synced:** 2026-06-14 23:00 UTC
**Domain:** infotech
**Last synced:** 2026-06-22 21:22 UTC
**State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)*
## Active Workstreams
### Establish ops-hub as the First VSM Inter-Hub Extension
Progress: 5/10 done | workstream_id: `48d91935-197e-4ad4-be07-7bbcd535847c`
**Open tasks:**
- ! T02 — Register ops-hub in Inter-Hub `8e9bd9b2`
*(wait: Requires authenticated Inter-Hub admin session or deployment-side migration execution.)*
- ! T03 — Activate the ops-hub capability manifest `55f5aeed`
*(wait: Requires ops-hub row and authenticated UI or migration execution.)*
- ! T04 — Create ops-hub API consumer and key `ad08e729`
*(wait: Requires authenticated operator API key creation.)*
- ! T05 — Seed first governed ops widgets `d303884d`
*(wait: Requires ops-hub row and active manifest before widgets can be seeded.)*
- ! T07 — Instrument the current Gitea registry work as the first ops-hub signal `ed3e0396`
*(wait: Requires ops widget, active ops event type, and API key before event submission.)*
### Expose OpenBao Browser UI at bao.coulomb.social
Progress: 2/6 done | workstream_id: `c1b5f54d-2f26-453d-966c-6353df0b6aec`
**Open tasks:**
- ! T05 - Verify Browser Login And Metadata-Only Secret Inspection `31d1da2d`
- ► T02 - Expose OpenBao UI at bao.coulomb.social `41e52213`
- ► T03 - Add KeyCape UI Redirect URI `fc1d5850`
- ► T04 - Add OpenBao UI Redirect URI To platform-admin Role `4f69cacb`
*(none — repo may need first-session setup)*
---
## MCP Orientation (when available)
If the state-hub MCP server is reachable, call:
`get_domain_summary("helix_forge")`
`get_domain_summary("infotech")`
This provides richer cross-domain context.
If the MCP call fails, use this file as your orientation source.

27
.repo-classification.yaml Normal file
View File

@@ -0,0 +1,27 @@
repo_classification:
standard: Repo Classification Standard
version: '1.0'
classified_at: '2026-06-22'
classified_by: human
category: product
domain: infotech
secondary_domains:
- agents
capability_tags:
- platform
- capability-registry
- coordination
- knowledge
- product-development
business_stake:
- product
- technology
- execution
- automation
- intelligence
business_mechanics:
- intention
- coordination
- operation
- adaptation
notes: Capability development platform; standard §13.1 — human confirmed.

View File

@@ -4,10 +4,10 @@
**Purpose:** Capability-first development ecosystem for evolving reusable software capabilities from natural-language intent through architecture-aware discovery, validation, implementation, feedback, and reuse.
**Domain:** helix_forge
**Domain:** infotech
**Repo slug:** helix-forge
**Topic ID:** `f39fa2a3-c491-414c-a91b-b4c5fcc6139c`
**Workplan prefix:** `HELIX-WP-`
**Workplan prefix:** `HF-WP-`
---
@@ -101,6 +101,63 @@ curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
---
## Credential and access routing
**Audience:** Codex, Claude Code, Grok, and custodian agents that call **llm-connect**
for inference. Run this check **before** requesting secrets, API keys, SSH access,
login tokens, or database passwords — in any repo, not only `ops-warden`.
ops-warden **issues SSH certificates only** (`warden sign`, `cert_command`). Every
other credential need belongs to another subsystem. **Do not** message
`ops-warden` on State Hub expecting a secret value; the reply is a pointer, not a key.
### Lookup (do this first)
```bash
warden route find "<describe your need>" --json
warden route show <catalog-id> --json
```
Requires the `warden` CLI from `~/ops-warden` (`uv tool install .` or `uv run warden`).
| Agent runtime | How to orient |
| --- | --- |
| **Codex / Grok** (shell, HTTP State Hub) | `warden route` commands above; inbox `to_agent=helix-forge` is for coordination, not secret vending |
| **Claude Code** (MCP when available) | `get_domain_summary("custodian")` for workstreams; **still** use `warden route` for credential ownership |
| **llm-connect** (inference service) | Never put secret retrieval in prompts; route custody to OpenBao/operator paths surfaced by `warden route` |
### Quick routing table
| I need… | Owner | ops-warden executes? |
| --- | --- | --- |
| SSH cert (`adm`/`agt`/`atm`) | ops-warden | **Yes** — `warden sign` |
| API key, DB password, provider token | OpenBao (`railiance-platform`) | No — route only |
| Login / OIDC / MFA | key-cape / Keycloak | No — route only |
| Authorization decision | flex-auth | No — route only |
| activity-core → issue-core emission | activity-core + issue-core | No — `warden route show activity-core-issue-sink` |
| SSH tunnel | ops-bridge (+ `cert_command` from warden) | No — route only |
### Anti-patterns (do not do these)
- `POST /messages/` to `ops-warden` asking for `ISSUE_CORE_API_KEY`, `OPENROUTER_API_KEY`, etc.
- Inventing `warden secret`, `warden login`, `warden bao`, `warden tunnel` — they do not exist
- Pasting secrets into Git, State Hub, workplans, logs, or chat
### Other capabilities (reuse-surface)
Non-credential capabilities are usually discovered through **reuse-surface** federation
(`reuse-surface` registry / `capability.*` indexes). Credential routing is inlined in
every repo's agent instructions because it is high-frequency, high-risk, and easy to
get wrong.
**Canon:** `~/ops-warden/wiki/CredentialRouting.md` · catalog `~/ops-warden/registry/routing/catalog.yaml`
<!-- REPO-AGENTS-EXTENSIONS -->
<!-- Append repo-specific agent instructions below this marker.
The state-hub template sync preserves content after this line. -->
---
## Workplan Convention (ADR-001)
Work items originate as files in this repo — not in the hub. The hub is a
@@ -124,7 +181,7 @@ anything needing analysis, design, approval, dependencies, or multiple phases.
id: HELIX-WP-NNNN
type: workplan
title: "..."
domain: helix_forge
domain: infotech
repo: helix-forge
status: proposed | ready | active | blocked | backlog | finished | archived
owner: codex

View File

@@ -8,4 +8,5 @@
@.claude/rules/stack-and-commands.md
@.claude/rules/architecture.md
@.claude/rules/repo-boundary.md
@.claude/rules/credential-routing.md
@.claude/rules/agents.md

View File

@@ -345,6 +345,33 @@ AI must not bypass the control plane. Agentic behavior must be observable, polic
6. **Reuse over accumulation.** The repository should grow by strengthening composable capabilities, not by collecting disconnected artifacts.
7. **Evidence over assertion.** Runtime behavior, telemetry, tests, contracts, and validation reports should back claims of readiness.
8. **Environment matters.** Users, builders, investors, external systems, markets, and real-world problems are part of the viable system boundary.
9. **Security from inception.** Secret custody, tenant isolation, and workload identity are architectural commitments, not production afterthoughts. Capabilities that handle sensitive material should declare path ownership, policy scope, and delivery boundaries early.
## 11.1 Platform secret custody (HelixForge × NetKingdom × Coulomb)
HelixForge does not operate OpenBao, but it **must model and govern** how
capabilities use secrets across a multi-tenant platform.
The direction:
* **OpenBao** is the platform secrets service (custody, policy, audit,
delivery) — deployed and operated through `railiance-platform`.
* **NetKingdom** owns the security architecture: identity (KeyCape), MFA,
tenant semantics, and the path/policy model that keeps secrets private to
their owners.
* **Coulomb** is tenant zero; HelixForge workplans and capabilities should
prove the pattern before customer tenants arrive.
Secret ownership is expressed through **KV path convention, identity claims,
and least-privilege policies** — not through confusing OpenBao login fields.
Humans sign in with KeyCape; **workloads** authenticate as themselves
(Kubernetes auth, scoped tokens).
Anchor document: [`docs/OpenBaoIntroduction.md`](docs/OpenBaoIntroduction.md)
Canonical deployment and operator detail remain in adjacent repos
(`railiance-platform/docs/openbao.md`,
`net-kingdom/docs/platform-identity-security-architecture.md`).
## 12\. Contribution intent
@@ -385,6 +412,7 @@ The following knowledge areas should be represented in the repository or linked
|Capability contracts|Defines interfaces, events, SLOs, ownership, and boundaries.|
|Validators|Turns architectural intent into executable checks.|
|Intelligence governance|Defines how automation, copilots, and agents are placed, governed, and audited.|
|Platform secret custody|Defines how multi-tenant secret ownership, workload delivery, and operator custody align across HelixForge, NetKingdom, and Coulomb. See `docs/OpenBaoIntroduction.md`.|
## 14\. Definition of done
@@ -470,6 +498,15 @@ intent:
- semantic\_validation
- governance\_for\_intelligence
- observability\_for\_runtime\_behavior
platform\_custody:
anchor: docs/OpenBaoIntroduction.md
custody\_service: openbao
identity\_plane: keycape
reference\_tenant: coulomb
intent: >
Secrets are private to their owners through path layout, identity
binding, and policy — from capability inception through production.
```
## 16\. Source inputs for this intent file
@@ -479,6 +516,8 @@ This file was drafted from the following project inputs:
* **HelixForgeVision** — project vision statement provided for this repository.
* **OrthogonalArchitectureSchema** — Orthogonal Architecture Standard Schema v1.0.1 and semantic validation profile.
* **260525-schema-orthogonalArchitecture.xlsx** — current OAD and VSM controlled vocabulary workbook.
* **docs/OpenBaoIntroduction.md** — platform secret custody anchor for the
HelixForge / NetKingdom / Coulomb direction.
When these materials are moved into the repository wiki, this file should be updated with stable wiki links.

View File

@@ -55,6 +55,9 @@ and operating model. It is not yet an application implementation.
capability, service, platform, policy, automation, or intelligence elements.
- You are shaping an Inter-Hub extension pattern, such as the initial ops-hub
workplan.
- You need the HelixForge view of platform secret custody, multi-tenant secret
ownership, or OpenBao's role across NetKingdom and Coulomb. Start with
`docs/OpenBaoIntroduction.md`.
---

275
docs/OpenBaoIntroduction.md Normal file
View File

@@ -0,0 +1,275 @@
# OpenBao in the HelixForge / NetKingdom / Coulomb Universe
This document anchors **why** OpenBao exists in the platform, **what** it is
responsible for, and **where** we are headed. It is written for operators,
architects, and builders working across HelixForge intent, NetKingdom security
architecture, and Coulomb as the first reference tenant.
Operational runbooks and deployment detail live in adjacent repos. This file
is the conceptual home in `helix-forge`.
| Topic | Canonical detail |
| --- | --- |
| Deployment, Helm, break-glass | `railiance-platform/docs/openbao.md` |
| Platform vs tenant security model | `net-kingdom/docs/platform-identity-security-architecture.md` |
| KeyCape OIDC wiring | `net-kingdom/sso-mfa/k8s/keycape/README.md` |
| Credential routing for agents | `helix-forge/AGENTS.md` (Credential and access routing) |
---
## Purpose
OpenBao is the **platform secrets service**: the place where sensitive material
is stored, accessed under policy, audited, and delivered to trusted actors.
HelixForge describes *how capabilities evolve*. NetKingdom describes *who may
act and under what trust model*. Coulomb is *tenant zero* — the first internal
tenant on that platform. OpenBao sits in the **custody plane**:
- It holds API keys, database passwords, signing material, operator credentials,
and workload secrets.
- It does **not** replace identity (KeyCape), application authorization
(flex-auth), or general configuration in Git.
The goal is **IT security from inception to production**: secrets enter custody
early, move through governed paths, and reach production workloads without
living in Git, chat, State Hub, or ad hoc env files.
---
## What OpenBao is — and is not
| OpenBao is | OpenBao is not |
| --- | --- |
| A centralized secret store with policy and audit | An identity provider |
| A broker for dynamic credentials (DB, PKI, SSH, …) | An application authorization engine |
| A delivery point for workloads (API, CSI, ESO) | A tenant registry or user directory |
| The system of record for **secret values** | A substitute for SOPS/age in Git-at-rest bootstrap |
**Privacy of secrets is achieved through path layout, identity binding, and
least-privilege policies** — not merely by storing data inside OpenBao.
---
## Place in the stack
```text
Human / service / agent principal
|
v
NetKingdom IAM Profile (identity semantics)
|
+-- KeyCape (+ privacyIDEA MFA) --> humans, OIDC login
|
+-- flex-auth (+ Topaz) --> application authorization
|
v
OpenBao (custody) --> secret paths, policies, audit
|
v
Workloads (K8s pods, jobs, operators) --> consume secrets via scoped auth
```
- **KeyCape** answers: who is this principal, which groups/tenants apply?
- **flex-auth** answers: may this principal perform action A on resource R?
- **OpenBao** answers: may this principal read or write secret path P?
End users of tenant applications should normally **not** browse OpenBao. They
authenticate to apps; apps (or operators) fetch secrets through scoped machine
or operator identity.
---
## Browser login — what the UI fields mean
Operators reach the UI at `https://bao.coulomb.social`. The raw OpenBao login
form exposes four fields. For platform operators today, most should be **hidden
presets**, not user choices.
| UI field | Technical meaning | User/admin mental model |
| --- | --- | --- |
| **Namespace** | OpenBao-internal partition (multi-tenancy inside one cluster) | Leave **blank** = the single root vault we use today |
| **Method** | Auth backend type (OIDC, token, kubernetes, …) | **OIDC** = “Sign in with KeyCape” |
| **Mount path** | Which auth backend handles login (`auth/<mount>/`) | **`netkingdom`** = platform identity plane login door |
| **Role** | OIDC auth role → maps claims to OpenBao **policies** | **`platform-admin`** = platform operator custody |
### Common misconceptions
1. **Namespace ≠ tenant/vendor.** Coulomb, customers, and HelixForge are not
selected in the namespace box. Tenant scope is expressed in **identity
claims, KV paths, and policies**.
2. **Mount path ≠ repo, domain, or user group.** It is only *which configured
login door* to use. `netkingdom` and legacy `keycape` are aliases to the
same KeyCape-backed OIDC config today.
3. **`platform-admin` ≠ namespace root.** Blank namespace means root namespace;
`platform-admin` is an **operator policy** granting access to paths like
`platform/*`.
4. **There is no generic `user` login role yet.** Only `platform-admin` is wired
for human OIDC. Narrower roles come with tenant onboarding.
### Streamlined operator experience (target)
```text
Sign in with KeyCape
[ Sign in ]
```
Hidden presets: namespace blank, method OIDC, mount `netkingdom`, role
`platform-admin`. Retire `keycape` from the default mask once compatibility
notes are no longer needed.
Do **not** use the root token through the browser UI.
---
## Multi-entity secret ownership
NetKingdom targets a platform that is **multi-tenant, multi-vendor,
multi-application, multi-customer, and multi-user**. Many of those entities need
secrets. **Yes, OpenBao can hold them all** — with explicit ownership rules.
OpenBao does not natively understand “tenant” or “vendor”. We encode ownership:
| Layer | Responsibility |
| --- | --- |
| **Path convention** | Every secret has an unambiguous owner prefix |
| **Identity** | KeyCape groups, K8s service accounts, scoped tokens |
| **Policy** | Least privilege: which paths each identity may touch |
| **Delivery** | ESO / CSI / API — only the intended workload receives the value |
| **Audit** | Who accessed which path (values are not logged) |
### Target path taxonomy (evolving)
```text
platform/ # platform control plane only
platform/operators/<service>/ # operator API keys, bootstrap creds
platform/workloads/<ns>/<sa>/<name> # platform workload secrets
tenants/coulomb/ # tenant zero (reference tenant)
tenants/coulomb/apps/<app>/ # application secrets
tenants/coulomb/vendors/<vendor>/ # vendor integration secrets
tenants/<customer>/ # future customer tenants
tenants/<customer>/apps/<app>/ # customer application secrets
```
Aligns with NetKingdom language:
```text
tenant:platform # platform control plane
tenant:coulomb # first internal / reference tenant
tenant:customer:<name> # future customer tenants
tenant:sandbox:<name> # sandboxes
```
### Actor → access (target matrix)
| Actor | Auth path | Typical secret scope |
| --- | --- | --- |
| Platform operator | KeyCape OIDC → `platform-admin` | `platform/*` (narrow over time) |
| Platform auditor | KeyCape OIDC → `platform-readonly` | metadata, no secret reads |
| Tenant admin (future) | KeyCape OIDC → `tenant-<id>-admin` | `tenants/<id>/*` only |
| Workload (pod) | Kubernetes auth → per-SA role | one path prefix, read-only where possible |
| Break-glass | offline/root procedures | emergency only, audited |
Tenant administrators must **not** be able to alter platform root trust,
global identity config, or policies governing the platform itself.
---
## Workloads
A **workload** is a **running piece of software** that does a job without a
human at the keyboard: API server, worker, cron job, operator script running as
a service account.
| Term | Meaning |
| --- | --- |
| **User** | Human (e.g. platform operator via KeyCape) |
| **Application** | Software product (Inter-Hub, a tenant app) |
| **Workload** | Running instance of that application in the cluster |
| **Service account** | The workload's machine identity in Kubernetes |
Humans use **OIDC + MFA**. Workloads use **Kubernetes auth** (or tightly scoped
tokens) so secrets are delivered automatically and least-privilege.
```text
Human operator → OIDC (KeyCape) → platform-admin → platform/operators/...
Inter-Hub pod → Kubernetes auth → inter-hub-role → platform/workloads/inter-hub/...
Tenant app pod → Kubernetes auth → tenant-app-role → tenants/coulomb/apps/myapp/...
```
---
## Current state vs direction
### Today (bootstrap / platform phase)
- Single OpenBao cluster on Railiance01, root namespace only.
- Browser UI at `bao.coulomb.social` with KeyCape OIDC and `platform-admin`.
- Platform operator paths under `platform/` (e.g. Inter-Hub and ops-hub operator
keys).
- Humans for attended custody; workloads largely still catching up on
Kubernetes auth + ESO/CSI delivery.
- `platform-admin` is intentionally broad for bootstrap; it must shrink as
tenant paths appear.
### Direction (inception → production)
1. **Platform custody** — operator keys, shared infra, break-glass discipline.
2. **Tenant paths**`tenants/coulomb/*` for first internal tenant apps.
3. **Workload auth** — per-namespace, per-service-account OpenBao roles.
4. **Human role split** — readonly, tenant-admin, no default `platform-admin`
for routine tenant work.
5. **Customer tenants** — isolated paths and policies; optional OpenBao
namespaces later if compliance requires stronger partitions.
HelixForge capability work should treat **secret path + policy + delivery** as
part of a capability's security contract, not an afterthought.
---
## Rules of custody (non-negotiable)
- Secret values, root tokens, unseal shares, and display-once API keys **never**
go into Git, State Hub, workplans, chat, or logs.
- Display-once material is stored at the **approved OpenBao path** immediately,
then removed from temp files.
- Prefer **metadata-only inspection** in the UI when checking whether a secret
exists.
- Route credential *ownership* questions through `warden route` (see
`helix-forge` agent instructions); ops-warden issues SSH certs only, not
vault secrets.
---
## Phased rollout checklist
Use this as a maturity ladder, not a single big bang.
- [x] OpenBao deployed; audit enabled; root token retired or break-glass only
- [x] Human operator path: KeyCape OIDC, MFA, browser UI
- [x] Platform operator secrets under `platform/operators/`
- [x] Streamlined login mask (hide namespace, method, mount, role) — `HF-WP-0003`, overlay in `railiance-platform/helm/openbao-ui-overlay/`
- [ ] `platform-readonly` role for auditors
- [ ] Path tree for `tenants/coulomb/`
- [ ] Kubernetes auth roles for platform workloads
- [ ] Tenant-scoped OIDC roles (no `platform-admin` for tenant admins)
- [ ] External Secrets Operator / CSI delivery for app charts
- [ ] Per-tenant audit review practices
- [ ] Optional OpenBao namespaces for high-isolation customers
---
## Summary
OpenBao is how the Coulomb / NetKingdom platform keeps secrets **private to
their owners** at scale: one vault, many owners, enforced by naming, identity,
and policy. HelixForge supplies the evolution loop; NetKingdom supplies the
trust model; Coulomb proves the first tenant path. OpenBao is the custody layer
that lets that model survive contact with production.
**One line:** Sign in with KeyCape for platform custody; encode tenant and
application ownership in paths and policies; let workloads authenticate as
themselves — so secrets stay out of Git and in governed hands from inception
through production.

View File

@@ -0,0 +1,15 @@
---
repo: helix-forge
reason: >
Draft-status (intent_version 0.1.0) capability-ecosystem vision; only design assets and standards docs exist, no packaged implementation.
reviewed: "2026-07-06"
reviewed_by: claude-code
revisit: >
Revisit once helix-forge ships a packaged artifact beyond design docs.
---
# No reusable capability
This repo was reviewed for the `reuse-surface` capability registry
(REUSE-WP-0017 coverage campaign) and has no capability to register at this
time. See the `reason` field above.

12
registry/README.md Normal file
View File

@@ -0,0 +1,12 @@
# Capability Registry
Markdown-first capability index for federation and reuse planning.
## Authoring
1. Copy a capability entry template (see reuse-surface `templates/capability-entry.template.md`).
2. Add the row to `indexes/capabilities.yaml`.
3. Run `reuse-surface validate` from a checkout with the CLI installed.
4. Merge to `main` and verify publish with `reuse-surface establish --publish-check`.
Federation contract: reuse-surface `docs/RegistryFederation.md`.

View File

View File

@@ -0,0 +1,4 @@
version: 1
updated: '2026-06-16'
domain: helix_forge
capabilities: []

368
scripts/ops-hub-bootstrap-api.py Executable file
View File

@@ -0,0 +1,368 @@
#!/usr/bin/env python3
"""Bootstrap ops-hub in Inter-Hub using the prepared HelixForge seeds.
The script never prints full API keys. The operator key is read from
IHUB_OPERATOR_KEY or IHUB_OPERATOR_KEY_FILE. If an ops-hub runtime key is
created, the full key is written to a 0600 temp file and only the file path and
key prefix are printed.
"""
from __future__ import annotations
import argparse
import json
import os
import stat
import sys
import tempfile
import time
import urllib.error
import urllib.parse
import urllib.request
from pathlib import Path
from typing import Any
DEFAULT_BASE = "https://hub.coulomb.social"
ROOT = Path(__file__).resolve().parents[1]
MANIFEST_PATH = ROOT / "wiki" / "ops-hub-manifest.draft.json"
WIDGETS_PATH = ROOT / "wiki" / "ops-hub-widgets.seed.json"
class BootstrapError(RuntimeError):
pass
def load_secret(name: str, file_name: str) -> str:
value = os.environ.get(name, "").strip()
if value:
return value
file_path = os.environ.get(file_name, "").strip()
if file_path:
return Path(file_path).read_text(encoding="utf-8").strip()
return ""
def request_json(
base_url: str,
method: str,
path: str,
token: str | None,
body: dict[str, Any] | None,
*,
expected: set[int],
) -> dict[str, Any]:
data = json.dumps(body).encode("utf-8") if body is not None else None
request = urllib.request.Request(base_url + path, data=data, method=method)
request.add_header("Accept", "application/json")
request.add_header("User-Agent", "helix-forge-ops-hub-bootstrap/0.1")
if token:
request.add_header("Authorization", f"Bearer {token}")
if body is not None:
request.add_header("Content-Type", "application/json")
try:
with urllib.request.urlopen(request, timeout=30) as response:
status = response.status
payload = response.read().decode("utf-8")
except urllib.error.HTTPError as error:
payload = error.read().decode("utf-8", errors="replace")
raise BootstrapError(f"{method} {path} failed with HTTP {error.code}: {payload}") from error
if status not in expected:
raise BootstrapError(f"{method} {path} returned HTTP {status}, expected {sorted(expected)}")
if not payload:
return {}
return json.loads(payload)
def list_items(base_url: str, path: str, token: str | None) -> list[dict[str, Any]]:
response = request_json(base_url, "GET", path, token, None, expected={200})
data = response.get("data", [])
if not isinstance(data, list):
raise BootstrapError(f"expected paginated data array from {path}")
return data
def first_by(items: list[dict[str, Any]], key: str, value: Any) -> dict[str, Any] | None:
return next((item for item in items if item.get(key) == value), None)
def load_manifest() -> dict[str, Any]:
manifest = json.loads(MANIFEST_PATH.read_text(encoding="utf-8"))
required = [
"hub",
"manifestVersion",
"declaredWidgetTypes",
"declaredEventTypes",
"declaredAnnotationCategories",
"declaredPolicyScopes",
]
missing = [key for key in required if key not in manifest]
if missing:
raise BootstrapError(f"{MANIFEST_PATH} missing required key(s): {', '.join(missing)}")
return manifest
def load_widgets() -> list[dict[str, Any]]:
widgets = json.loads(WIDGETS_PATH.read_text(encoding="utf-8"))
if not isinstance(widgets, list):
raise BootstrapError(f"{WIDGETS_PATH} must contain a JSON array")
return widgets
def ensure_hub(base_url: str, operator_key: str, manifest: dict[str, Any]) -> dict[str, Any]:
hub_seed = manifest["hub"]
slug = hub_seed["slug"]
existing = first_by(list_items(base_url, "/api/v2/hubs", None), "slug", slug)
if existing:
return {"record": existing, "created": False}
body = {
"slug": slug,
"name": hub_seed["name"],
"domain": hub_seed["domain"],
"hubKind": hub_seed.get("hubKind", "domain"),
"hubFamily": "vsm",
"vsmFunction": "OPS",
"vsmSystem": "1",
}
record = request_json(base_url, "POST", "/api/v2/hubs", operator_key, body, expected={201})
return {"record": record, "created": True}
def manifest_body(manifest: dict[str, Any], hub_id: str | None = None) -> dict[str, Any]:
body: dict[str, Any] = {
"manifestVersion": manifest["manifestVersion"],
"declaredWidgetTypes": manifest["declaredWidgetTypes"],
"declaredEventTypes": manifest["declaredEventTypes"],
"declaredAnnotationCategories": manifest["declaredAnnotationCategories"],
"declaredPolicyScopes": manifest["declaredPolicyScopes"],
"capabilityDescription": manifest.get("capabilityDescription", ""),
"contact": manifest.get("contact", "operator"),
}
if hub_id:
body["hubId"] = hub_id
return body
def ensure_manifest(base_url: str, operator_key: str, hub_id: str, manifest: dict[str, Any]) -> dict[str, Any]:
query = urllib.parse.urlencode({"hubId": hub_id})
manifests = list_items(base_url, f"/api/v2/hub-capability-manifests?{query}", operator_key)
active = first_by(manifests, "status", "active")
if active:
return {"record": active, "created": False, "activated": False}
draft = first_by(manifests, "status", "draft")
if draft:
record = request_json(
base_url,
"PATCH",
f"/api/v2/hub-capability-manifests/{draft['id']}",
operator_key,
manifest_body(manifest),
expected={200},
)
created = False
else:
record = request_json(
base_url,
"POST",
"/api/v2/hub-capability-manifests",
operator_key,
manifest_body(manifest, hub_id),
expected={201},
)
created = True
activated = request_json(
base_url,
"POST",
f"/api/v2/hub-capability-manifests/{record['id']}/activate",
operator_key,
None,
expected={200},
)
return {"record": activated, "created": created, "activated": True}
def ensure_api_consumer(base_url: str, operator_key: str, manifest_id: str) -> dict[str, Any]:
consumers = list_items(base_url, "/api/v2/api-consumers", operator_key)
existing = first_by(consumers, "name", "ops-hub")
if existing:
return {"record": existing, "created": False}
record = request_json(
base_url,
"POST",
"/api/v2/api-consumers",
operator_key,
{
"name": "ops-hub",
"description": "API consumer for the VSM Operations hub",
"hubCapabilityManifestId": manifest_id,
"rateLimitPerMinute": 120,
"quotaPerDay": 50000,
},
expected={201},
)
return {"record": record, "created": True}
def write_runtime_key(full_key: str, output_path: str | None) -> Path:
if output_path:
path = Path(output_path)
path.write_text(full_key, encoding="utf-8")
else:
fd, raw_path = tempfile.mkstemp(prefix="ops-hub-runtime-key-", text=True)
path = Path(raw_path)
with os.fdopen(fd, "w", encoding="utf-8") as handle:
handle.write(full_key)
path.chmod(stat.S_IRUSR | stat.S_IWUSR)
return path
def ensure_runtime_key(
base_url: str,
operator_key: str,
api_consumer_id: str,
output_path: str | None,
) -> dict[str, Any]:
existing_runtime_key = load_secret("OPS_HUB_KEY", "OPS_HUB_KEY_FILE")
if existing_runtime_key:
return {
"created": False,
"token": existing_runtime_key,
"keyPrefix": existing_runtime_key[:8],
"keyFile": os.environ.get("OPS_HUB_KEY_FILE"),
}
response = request_json(
base_url,
"POST",
f"/api/v2/api-consumers/{api_consumer_id}/api-keys",
operator_key,
{"scopes": "framework:read hub:ops-hub:read hub:ops-hub:write"},
expected={201},
)
full_key = response.get("fullKey")
if not full_key:
raise BootstrapError("api key creation did not return display-once fullKey")
key_file = write_runtime_key(full_key, output_path)
api_key = response.get("apiKey") or {}
return {
"created": True,
"token": full_key,
"keyPrefix": api_key.get("keyPrefix", full_key[:8]),
"keyFile": str(key_file),
}
def ensure_widgets(base_url: str, runtime_key: str, hub_id: str, widget_seeds: list[dict[str, Any]]) -> dict[str, Any]:
existing_widgets = list_items(base_url, "/api/v2/widgets", runtime_key)
existing_by_ref = {
widget.get("capabilityRef"): widget
for widget in existing_widgets
if widget.get("hubId") == hub_id and widget.get("capabilityRef")
}
created: list[dict[str, Any]] = []
reused: list[dict[str, Any]] = []
for seed in widget_seeds:
existing = existing_by_ref.get(seed.get("capabilityRef"))
if existing:
reused.append(existing)
continue
body = {"hubId": hub_id, "status": "active", **seed}
created.append(request_json(base_url, "POST", "/api/v2/widgets", runtime_key, body, expected={201}))
return {"created": created, "reused": reused}
def submit_gitea_event(base_url: str, runtime_key: str, widgets: dict[str, Any]) -> dict[str, Any] | None:
all_widgets = widgets["created"] + widgets["reused"]
readiness = next(
(widget for widget in all_widgets if widget.get("capabilityRef") == "ops:readiness:gitea-registry"),
None,
)
if not readiness:
return None
return request_json(
base_url,
"POST",
"/api/v2/interaction-events",
runtime_key,
{
"widgetId": readiness["id"],
"eventType": "ops-endpoint-verified",
"viewContext": "railiance-apps/workplans/RAIL-AP-WP-0001",
"metadata": {
"vsmFunction": "OPS",
"vsmSystem": "S1",
"endpoint": "https://gitea.coulomb.social/v2/",
"expectedStatus": 401,
"observedHeader": "Docker-Distribution-Api-Version: registry/2.0",
"recordedBy": "helix-forge/scripts/ops-hub-bootstrap-api.py",
"recordedAt": int(time.time()),
},
},
expected={201},
)
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--base", default=os.environ.get("IHUB_BASE", DEFAULT_BASE).rstrip("/"))
parser.add_argument("--runtime-key-output", default=os.environ.get("OPS_HUB_RUNTIME_KEY_OUTPUT"))
parser.add_argument("--skip-event", action="store_true", help="Do not submit the initial Gitea readiness event")
return parser
def main() -> int:
args = build_parser().parse_args()
operator_key = load_secret("IHUB_OPERATOR_KEY", "IHUB_OPERATOR_KEY_FILE")
if not operator_key:
print("ERROR: set IHUB_OPERATOR_KEY or IHUB_OPERATOR_KEY_FILE", file=sys.stderr)
return 2
manifest = load_manifest()
widget_seeds = load_widgets()
hub = ensure_hub(args.base, operator_key, manifest)
hub_record = hub["record"]
manifest_result = ensure_manifest(args.base, operator_key, hub_record["id"], manifest)
manifest_record = manifest_result["record"]
consumer = ensure_api_consumer(args.base, operator_key, manifest_record["id"])
runtime_key = ensure_runtime_key(args.base, operator_key, consumer["record"]["id"], args.runtime_key_output)
widgets = ensure_widgets(args.base, runtime_key["token"], hub_record["id"], widget_seeds)
event = None if args.skip_event else submit_gitea_event(args.base, runtime_key["token"], widgets)
summary = {
"ok": True,
"base": args.base,
"hub": {"id": hub_record["id"], "slug": hub_record["slug"], "created": hub["created"]},
"manifest": {
"id": manifest_record["id"],
"status": manifest_record["status"],
"created": manifest_result["created"],
"activated": manifest_result["activated"],
},
"apiConsumer": {"id": consumer["record"]["id"], "name": consumer["record"]["name"], "created": consumer["created"]},
"runtimeKey": {
"created": runtime_key["created"],
"keyPrefix": runtime_key["keyPrefix"],
"keyFile": runtime_key["keyFile"],
"storeImmediatelyInOpenBao": "platform/operators/ops-hub/runtime",
"field": "OPS_HUB_KEY",
},
"widgets": {"created": len(widgets["created"]), "reused": len(widgets["reused"])},
"event": None if event is None else {"id": event["id"], "eventType": event["eventType"], "widgetId": event["widgetId"]},
}
print(json.dumps(summary, indent=2, sort_keys=True))
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except BootstrapError as exc:
print(f"ERROR: {exc}", file=sys.stderr)
raise SystemExit(1)

View File

@@ -8,8 +8,9 @@ This runbook gives the operator-ready bootstrap path for `ops-hub`, the VSM
Operations / System 1 extension of Inter-Hub.
Use this when an authenticated Inter-Hub admin session or deployment migration
is available. The current public v2 API is not sufficient to create the hub,
manifest, API consumer, API key, or seed widgets by itself.
is available. The current public v2 API now exposes the supported bootstrap
surface; creation and mutation calls require an Inter-Hub operator/admin API
key.
As of 2026-06-06, implementation work for `ops-hub` belongs in the dedicated
repo at `/home/worsch/ops-hub` with remote `gitea-remote:coulomb/ops-hub.git`.
@@ -25,31 +26,23 @@ implementation repo ports or supersedes it.
## Current Bootstrap Decision
Prefer the supported Inter-Hub bootstrap API once production exposes the
current API surface. Do not proceed with manual DB seeding unless the operator
explicitly chooses that fallback. Until the production API gate passes, the
authenticated Inter-Hub admin UI and SQL migration remain fallback paths for
attended operator use only.
Prefer the supported Inter-Hub bootstrap API for hub, manifest, API consumer,
and runtime key creation. Use the idempotent SQL fallback for seed widgets and
initial evidence only when the live API hits a known server-side bootstrap bug
and the operator explicitly approves that fallback.
Production API gate:
Latest check, 2026-06-14 after Inter-Hub deployment:
- `https://hub.coulomb.social/api/v2/hubs` returns `401` unauthenticated, not
`404`.
- `https://hub.coulomb.social/api/v2/hubs` returns `200` with a public,
paginated hub list.
- OpenAPI lists `/hubs`, `/hub-capability-manifests`, `/api-consumers`, and
`/policy-scopes`.
- After the gate passes, run the supported bootstrap/smoke path from the
relevant `inter-hub` or `ops-hub` tooling with `IHUB_BASE` and an operator
key.
Latest check, 2026-06-14:
- `ops-hub/scripts/interhub-gate-probe.py` still reports the production gate
closed.
- `GET /api/v2/hubs` returns `404`.
- Live OpenAPI still omits `/hubs`, `/hub-capability-manifests`,
`/api-consumers`, and `/policy-scopes`.
- Do not run the preferred API bootstrap path until the current Inter-Hub API
is deployed, unless the operator explicitly chooses the SQL fallback.
- Hub and policy-scope list reads are public. Hub creation, manifest
creation/activation, API consumer/key creation, widget creation, and event
submission require `Authorization: Bearer <key>`.
- The old `ops-hub/scripts/interhub-gate-probe.py` expectation that
unauthenticated `GET /api/v2/hubs` must return `401` is stale. The deployed
contract is public-read/authenticated-write.
VSM classification is stored in the manifest capability description for now:
@@ -61,6 +54,120 @@ Newer Inter-Hub schemas have first-class hub metadata columns for these values.
The SQL fallback sets those columns when present and still carries the same
classification in the manifest description for older deployments.
Latest bootstrap result, 2026-06-15:
- `scripts/ops-hub-bootstrap-api.py` created/reused the `ops-hub` hub row,
active manifest, runtime API consumer, and display-once runtime API key.
- `POST /api/v2/widgets` failed with an Inter-Hub `COUNT(*)` decode error in
type-registry validation.
- The operator-approved SQL fallback seeded 14 widgets, 14 initial widget
versions, and the first Gitea registry readiness event.
- The runtime key can be exchanged through `POST /api/v2/token`, can read all
14 widgets through `GET /api/v2/widgets`, and can read the first event
through `GET /api/v2/interaction-events`.
- `GET /api/v2/hub-registry` currently returns HTTP 500 because Inter-Hub
decodes `COUNT(*)` from `api_request_log` as `Int` while PostgreSQL returns
`bigint`.
## Inter-Hub Operator Key
`IHUB_OPERATOR_KEY` is an existing Inter-Hub API key or short-lived access
token accepted by v2 endpoints as:
```text
Authorization: Bearer <key>
```
It is needed only for privileged bootstrap writes:
- creating or reusing the `ops-hub` hub row;
- creating and activating the capability manifest;
- creating the `ops-hub` runtime `ApiConsumer`;
- minting the display-once runtime API key;
- creating widgets and submitting the first event.
The operator key is not the long-term `ops-hub` runtime credential. It should
be used for the attended bootstrap only, then normal `ops-hub` traffic should
use the narrower runtime key created during bootstrap.
Allowed sources:
1. Retrieve an existing Inter-Hub operator/admin key from the approved secret
store, if one has already been created. After `HF-WP-0002` is deployed,
the intended browser path is `https://bao.coulomb.social` with KeyCape auth
path `netkingdom` and role `platform-admin`. The older `keycape` auth path
may remain available as a compatibility alias.
2. If no such key exists, log in to the Inter-Hub admin UI and create an
operator/bootstrap `ApiConsumer` and API key. The full key is display-once;
store it immediately in the approved secret store.
3. NetKingdom/OpenBao may provide the key only as the secret-custody path once
the key exists and an appropriate policy/path is defined. The
`net-kingdom` Git repository must not contain the key.
`net-kingdom-pg-1` is only the PostgreSQL pod used by the SQL fallback. It is
not by itself the source of an Inter-Hub operator key.
When using the OpenBao browser UI, inspect metadata/path presence first, for
example under:
```text
platform/operators/
platform/operators/inter-hub/
```
Do not copy secret values into Git, State Hub, chat, shell history, or this
runbook. Store display-once Inter-Hub and `ops-hub` runtime keys directly in
the approved secret path.
For an attended local run, enter the key in a trusted shell without echoing it:
```bash
export IHUB_BASE="https://hub.coulomb.social"
read -rsp "Inter-Hub operator key: " IHUB_OPERATOR_KEY
echo
export IHUB_OPERATOR_KEY
```
The preferred HelixForge helper uses the prepared manifest and widget seed
artifacts instead of the smaller one-widget smoke fixture:
```bash
umask 077
IHUB_KEY_FILE=$(mktemp)
# Paste the OpenBao value into this file without printing it to the terminal.
read -rsp "Inter-Hub operator key: " IHUB_OPERATOR_KEY
printf '%s' "$IHUB_OPERATOR_KEY" > "$IHUB_KEY_FILE"
unset IHUB_OPERATOR_KEY
echo
IHUB_BASE="https://hub.coulomb.social" \
IHUB_OPERATOR_KEY_FILE="$IHUB_KEY_FILE" \
python3 scripts/ops-hub-bootstrap-api.py
```
The helper creates/reuses the `ops-hub` hub row, activates the full
HelixForge manifest, creates the `ops-hub` API consumer, creates the runtime
API key if needed, creates any missing seed widgets, and submits the first
Gitea registry readiness event. If it creates a new `ops-hub` runtime API key,
it writes the full key to a 0600 temp file and prints only the path and key
prefix. Store that runtime key immediately in OpenBao, for example:
```text
platform/operators/ops-hub/runtime
field: OPS_HUB_KEY
```
After storing both keys in OpenBao, remove local temp files:
```bash
rm -f "$IHUB_KEY_FILE" "<runtime-key-file-printed-by-helper>"
unset IHUB_KEY_FILE
```
`/home/worsch/inter-hub/scripts/ops-hub-bootstrap-smoke.py` remains useful as
a narrow source-side smoke proof, but it creates a one-widget fixture rather
than the full HF-WP-0001 Operations vocabulary.
As of the 2026-05-19 access check, the workstation kubeconfig only points at
CoulombCore (`92.205.130.254`) and does not include the Railiance01
(`92.205.62.239`) cluster where `hub.coulomb.social` resolves. SSH key access
@@ -118,9 +225,10 @@ kubectl exec -i -n databases net-kingdom-pg-1 -- \
```
The SQL fallback creates the hub, active manifest, registry entries, API
consumer row, and seed widgets. It does not create the one-time visible static
API key; generate that in the authenticated Inter-Hub UI and store it outside
Git.
consumer row, seed widgets, initial widget version rows, and the first Gitea
registry readiness event. It does not create the one-time visible static API
key; generate that through the authenticated Inter-Hub API/UI helper and store
it outside Git.
## Validation
@@ -150,20 +258,24 @@ Expected: a short-lived access token is returned.
After widget seeding:
```bash
curl -s https://hub.coulomb.social/api/v2/hub-registry
curl -s https://hub.coulomb.social/api/v2/widgets
curl -s https://hub.coulomb.social/api/v2/interaction-events
```
Expected: `ops-hub` is visible, and the operator can see the seeded widgets in
the authenticated UI.
Expected: the 14 seeded `ops-hub` widgets are readable, and the Gitea registry
readiness event is visible. `GET /api/v2/hub-registry` is the desired final
registry validation, but it currently returns HTTP 500 on production because
of the `api_request_log` `COUNT(*)` decode issue.
## Known Blockers
- The live public v2 API has no `POST /api/v2/hubs`.
- The live public v2 API has no `POST /api/v2/widgets`.
- There are no v2 endpoints for manifest creation/activation.
- There are no v2 endpoints for API consumer or key creation.
- There is no `/api/v2/policy-scopes`.
- Interaction event create currently does not persist submitted metadata.
- Webhook dispatch currently uses the hard-coded `"clicked"` event type.
- `POST /api/v2/widgets` currently fails on production because Inter-Hub
decodes `COUNT(*)` from `widget_type_registry` as `Int` while PostgreSQL
returns `bigint`.
- `GET /api/v2/hub-registry` currently fails on production for the same bug
class in the `api_request_log` rate-limit query.
- The generated `ops-hub` runtime key must be moved from the local temp file
into OpenBao at `platform/operators/ops-hub/runtime`, field `OPS_HUB_KEY`,
then the temp file must be removed.
These are tracked by HF-WP-0001 T10 for Inter-Hub hardening.

View File

@@ -9,6 +9,8 @@
-- - Owned type registry entries
-- - ApiConsumer row
-- - Seed widgets
-- - Initial widget versions for the seed widgets
-- - First Gitea registry readiness event
--
-- It intentionally does not create an ApiKey. Generate the key through the
-- authenticated Inter-Hub UI so the full static key can be shown once and
@@ -284,4 +286,97 @@ WHERE NOT EXISTS (
AND capability_ref = seed.capability_ref
);
WITH hub AS (
SELECT id FROM hubs WHERE slug = 'ops-hub'
), seeded_widgets AS (
SELECT
w.id,
w.name,
w.widget_type,
w.hub_id,
w.capability_ref,
w.view_context,
w.policy_scope,
w.status,
w.version
FROM widgets w
JOIN hub ON hub.id = w.hub_id
WHERE w.capability_ref IN (
'ops:environment:local',
'ops:environment:coulombcore',
'ops:environment:railiance01',
'ops:environment:threephoenix-prod',
'ops:host:coulombcore',
'ops:host:railiance01',
'ops:service-catalog',
'ops:service:gitea',
'ops:service:state-hub',
'ops:service:inter-hub',
'ops:endpoint:gitea-registry',
'ops:readiness:gitea-registry',
'ops:readiness:state-hub-cluster-deploy',
'ops:migration:coulombcore-to-threephoenix'
)
)
INSERT INTO widget_versions (
widget_id,
version,
schema_snapshot
)
SELECT
seeded_widgets.id,
1,
jsonb_build_object(
'name', seeded_widgets.name,
'widget_type', seeded_widgets.widget_type,
'hub_id', seeded_widgets.hub_id,
'capability_ref', seeded_widgets.capability_ref,
'view_context', seeded_widgets.view_context,
'policy_scope', seeded_widgets.policy_scope,
'status', seeded_widgets.status,
'version', seeded_widgets.version
)
FROM seeded_widgets
WHERE NOT EXISTS (
SELECT 1
FROM widget_versions
WHERE widget_id = seeded_widgets.id
AND version = 1
);
WITH readiness_widget AS (
SELECT id
FROM widgets
WHERE capability_ref = 'ops:readiness:gitea-registry'
AND hub_id = (SELECT id FROM hubs WHERE slug = 'ops-hub')
)
INSERT INTO interaction_events (
widget_id,
event_type,
actor_type,
view_context_ref,
metadata
)
SELECT
readiness_widget.id,
'ops-endpoint-verified',
'api',
'railiance-apps/workplans/RAIL-AP-WP-0001',
jsonb_build_object(
'vsmFunction', 'OPS',
'vsmSystem', 'S1',
'endpoint', 'https://gitea.coulomb.social/v2/',
'expectedStatus', 401,
'observedHeader', 'Docker-Distribution-Api-Version: registry/2.0',
'recordedBy', 'helix-forge/wiki/ops-hub-bootstrap.sql'
)
FROM readiness_widget
WHERE NOT EXISTS (
SELECT 1
FROM interaction_events
WHERE widget_id = readiness_widget.id
AND event_type = 'ops-endpoint-verified'
AND view_context_ref = 'railiance-apps/workplans/RAIL-AP-WP-0001'
);
COMMIT;

View File

@@ -2,12 +2,13 @@
id: HF-WP-0001
type: workplan
title: "Establish ops-hub as the First VSM Inter-Hub Extension"
domain: helix_forge
domain: infotech
topic_slug: helix-forge
repo: helix-forge
status: active
status: finished
owner: worsch
created: "2026-05-16"
updated: "2026-06-14"
updated: "2026-06-19"
planning_priority: high
planning_order: 1
related_repos:
@@ -370,7 +371,7 @@ Output: `Confirmed Bootstrap Path` section in this workplan.
```task
id: HF-WP-0001-T02
status: wait
status: done
priority: high
state_hub_task_id: "8e9bd9b2-54fc-49a4-8bb8-11c8577be48d"
```
@@ -393,8 +394,9 @@ fields as an Inter-Hub API/model gap.
Done when: `ops-hub` appears in `/Hubs` and `/api/v2/hub-registry` after
authentication, and a human can tell that it is the VSM Operations hub.
Blocked until: an authenticated Inter-Hub admin session or deployment-side
migration is available.
Ready when: the operator loads the `IHUB_OPERATOR_KEY` from OpenBao into a
trusted shell without echoing it. The key exists in approved custody, but the
hub row has not been created yet.
Prepared artifacts:
@@ -402,13 +404,37 @@ Prepared artifacts:
- `wiki/ops-hub-manifest.draft.json`
- `wiki/ops-hub-bootstrap.sql`
Dependency update on 2026-06-15: a temporary Inter-Hub bootstrap operator key
was minted directly in the `interhub` database after the web admin seed
credential failed. OpenBao audit confirms the key was stored at
`platform/operators/inter-hub/bootstrap-operator`, and Inter-Hub DB metadata
shows active key prefix `8fab0bef` for `inter-hub-bootstrap-operator`.
Implementation progress on 2026-06-15: added
`scripts/ops-hub-bootstrap-api.py`, which uses
`wiki/ops-hub-manifest.draft.json` and `wiki/ops-hub-widgets.seed.json` to
create/reuse the hub, activate the full manifest, create the runtime
`ops-hub` API consumer/key, seed widgets, and submit the first Gitea readiness
event through the supported Inter-Hub API. The helper requires
`IHUB_OPERATOR_KEY` or `IHUB_OPERATOR_KEY_FILE` and does not print full key
values.
Live result on 2026-06-15: the attended helper run used the OpenBao-custodied
operator key from a 0600 temp file and created/reused the Inter-Hub hub row.
Verified non-secret state:
- Hub id `4f6e4cf7-6a96-4ff2-8a37-08c9f9e405d2`
- Slug `ops-hub`
- VSM metadata `hub_family=vsm`, `vsm_function=OPS`, `vsm_system=1`
- Active manifest id `00aaf90a-8e76-4b0e-892d-33b162862f38`
---
### T03 — Activate the ops-hub capability manifest
```task
id: HF-WP-0001-T03
status: wait
status: done
priority: high
state_hub_task_id: "55f5aeed-21c3-4a83-bc78-f90f92c7d597"
```
@@ -440,13 +466,22 @@ operator or migration can create and activate the manifest.
Prepared artifact: `wiki/ops-hub-manifest.draft.json`.
Live result on 2026-06-15: the full manifest is active in production. Public
registry checks found all expected ops vocabulary values:
- 14 of 14 widget types present.
- 15 of 15 event types present.
- 10 of 10 annotation categories present.
- Policy scopes are present in the production database and still lack a
dedicated public validation check in this repo's helper.
---
### T04 — Create ops-hub API consumer and key
```task
id: HF-WP-0001-T04
status: wait
status: done
priority: high
state_hub_task_id: "ad08e729-8562-4a02-8bf6-dcdfebe430c8"
```
@@ -467,13 +502,63 @@ Blocked until: an authenticated operator creates the API key and stores the
full static key outside Git. The SQL fallback intentionally creates only the
consumer row, not the one-time visible secret.
Progress on 2026-06-15:
- The `ops-hub` API consumer exists with id
`f9e595c6-4e1d-41fd-86cb-c1830bd7ec81`.
- The bootstrap helper created the display-once runtime key and wrote it to a
local 0600 temp file without printing the key.
- `POST /api/v2/token` returns a short-lived Bearer token for the runtime key
with `expires_in=3600`.
- `GET /api/v2/widgets` works with the runtime key and returns the 14
`ops-hub` widgets.
Remaining before closing:
- Store the runtime key from the temporary file in OpenBao at
`platform/operators/ops-hub/runtime`, field `OPS_HUB_KEY`, then remove the
temp file.
- `GET /api/v2/hub-registry` currently returns HTTP 500 because Inter-Hub
decodes `COUNT(*)` from `api_request_log` as `Int` while PostgreSQL returns
`bigint`. Track/fix this under T10 before treating hub-registry as a clean
acceptance signal.
Custody attempt on 2026-06-15: copied the generated runtime key into the
OpenBao pod as a temporary file and attempted to write it to the approved KV
path using the pod token helper. OpenBao denied the request with `403
permission denied` while resolving the KV mount through
`sys/internal/ui/mounts/platform/operators/ops-hub/runtime`. The temporary
in-pod key file was removed and verified absent. The local 0600 runtime-key
file remains because it has not yet been successfully stored in OpenBao.
Current blocker: requires an attended OpenBao root/sudo token handoff, or the
operator storing the local runtime key manually through the browser UI, before
the temp file can be removed and this task can close.
Completed on 2026-06-19:
- Regenerated the display-once runtime key through
`scripts/ops-hub-bootstrap-api.py` after the earlier 0600 temp file was no
longer present.
- Stored the runtime key in OpenBao at
`platform/operators/ops-hub/runtime`, field `OPS_HUB_KEY`, using an approved
operator token. No key values were copied into Git, State Hub, or chat.
- Removed the local runtime-key temp file after successful OpenBao write.
- Verified non-secret acceptance evidence with the custodied runtime key:
- `POST /api/v2/token` exchanges the static key for a short-lived Bearer
token (`expires_in=3600`).
- `GET /api/v2/hub-registry` returns HTTP `200` with the exchanged token.
- `GET /api/v2/widgets` returns all 14 `ops-hub` widgets with the exchanged
token.
- Current runtime key prefix: `c1f3ac3a`.
---
### T05 — Seed first governed ops widgets
```task
id: HF-WP-0001-T05
status: wait
status: done
priority: high
state_hub_task_id: "d303884d-d1f6-4fd0-a4ec-97afe6162164"
```
@@ -508,6 +593,13 @@ Prepared artifacts:
- `wiki/ops-hub-widgets.seed.json`
- `wiki/ops-hub-bootstrap.sql`
Live result on 2026-06-15: widget creation through `POST /api/v2/widgets`
failed because Inter-Hub decodes `COUNT(*)` from the type-registry validation
query as `Int` while PostgreSQL returns `bigint`. The operator-approved SQL
fallback was then applied idempotently and created the 14 governed widgets plus
14 initial widget version rows. A runtime-key API smoke check verified all 14
widgets are readable through `GET /api/v2/widgets`.
---
### T06 — Build the first ops inventory artifact
@@ -547,7 +639,7 @@ Output: `wiki/OpsHubInventory.md`.
```task
id: HF-WP-0001-T07
status: wait
status: done
priority: medium
state_hub_task_id: "ed3e0396-b16d-40c2-9519-e755ad6241eb"
```
@@ -578,6 +670,17 @@ Blocked until: the `ops-endpoint-gitea-registry` widget exists, the
`ops-endpoint-verified` event type is active, and an ops-hub API key is
available to the operator.
Live result on 2026-06-15: the first Gitea registry readiness event was
inserted by the SQL fallback and is visible through
`GET /api/v2/interaction-events` with the runtime key:
- Event id `4af73b21-75a9-4814-b6df-62083cfda15f`
- Event type `ops-endpoint-verified`
- View context `railiance-apps/workplans/RAIL-AP-WP-0001`
- Metadata records endpoint `https://gitea.coulomb.social/v2/`, expected
status `401`, and observed header
`Docker-Distribution-Api-Version: registry/2.0`
---
### T08 — Define the ops-hub readiness gate model for ThreePhoenix migration
@@ -655,7 +758,7 @@ implementation should happen in `ops-hub`.
```task
id: HF-WP-0001-T10
status: wait
status: done
priority: high
target_repo: inter-hub
state_hub_task_id: "7fa54508-7add-4885-8913-12edaadc4d92"
@@ -728,6 +831,116 @@ Production gate recheck on 2026-06-14:
on deployment of the current Inter-Hub API or an explicit operator decision
to use the manual SQL fallback.
Production gate recheck after Inter-Hub deployment on 2026-06-14:
- `https://hub.coulomb.social/api/v2/hubs` now returns `200` with an empty
paginated hub list instead of `404`.
- The live OpenAPI now lists `/hubs`, `/hub-capability-manifests`,
`/api-consumers`, and `/policy-scopes`.
- OpenAPI shows public reads for `GET /hubs` and `GET /policy-scopes`, and
authenticated `BearerAuth` writes for hub, manifest, API consumer/key,
widget, and interaction-event creation.
- `ops-hub/scripts/interhub-gate-probe.py` still exits nonzero because it
expects unauthenticated `GET /api/v2/hubs` to return `401`; that expectation
is stale relative to the deployed public-read/authenticated-write contract.
- Result: the Inter-Hub bootstrap API hardening and production deployment gate
are complete. HF-WP-0001 now waits on an attended bootstrap run with
`IHUB_OPERATOR_KEY` or equivalent authenticated operator session, not on
manual SQL fallback or API deployment.
Live bootstrap follow-up on 2026-06-15:
- Hub, manifest, API consumer, and runtime key creation work through the live
API.
- `POST /api/v2/widgets` fails with
`UnexpectedColumnTypeStatementError 0 23 20` from
`SELECT COUNT(*) FROM widget_type_registry ...`; the query result needs to be
decoded as `Int64` or cast to `int`.
- `GET /api/v2/hub-registry` with either the static runtime key or a
short-lived token fails with the same class of error from
`SELECT COUNT(*) FROM api_request_log ...`.
- The operator-approved SQL fallback was used for seed widgets and the first
event so HF-WP-0001 could keep moving, but the next VSM hub is not yet fully
scriptable without direct DB access.
Source fix on 2026-06-15: patched Inter-Hub
`Application/Helper/TypeRegistry.hs` and
`Application/Helper/ApiRateLimit.hs` to cast the affected `COUNT(*)` queries
to `int`, committed as `5101eb5 Fix API count decoding`, and pushed to
`origin/main`.
Deployment status on 2026-06-15:
- Local `git diff --check` passed in `inter-hub`.
- `nix develop ... scripts/compile-check` could not run because the checkout
lacks `.devenv/root` and plain `nix develop` cannot determine the current
directory in this shell.
- A local `nix build .#docker` was attempted, but after more than 20 minutes it
was still compiling dependencies and could not be used for deployment from
this session because registry/deploy credentials are not available here.
- Railiance01 release inspection through `railiance-apps` shows the live
`hub.coulomb.social` Deployment still uses
`92.205.130.254:32166/coulomb/inter-hub:790b5e5`, while Helm values report
`image.tag: 11ff61c`; in either view, the pushed `5101eb5` fix is not live.
Railiance deploy-surface recheck on 2026-06-15:
- `railiance-apps` commit `c7d49d3` adds local Makefile targets for
`INTER_HUB_IMAGE_TAG=<sha>` dry-run, deploy, status, release-info, logs, and
smoke checks.
- `INTER_HUB_IMAGE_TAG=5101eb5 make inter-hub-dry-run` renders
`gitea.coulomb.social/coulomb/inter-hub:5101eb5` and preserves the live
immutable selector label `app=inter-hub`.
- A Helm `--dry-run=server` upgrade against Railiance01 with tag `5101eb5`
succeeds, so the chart shape is acceptable to the cluster.
- The Gitea registry currently returns `MANIFEST_UNKNOWN` for
`coulomb/inter-hub:5101eb5`, so the deployment must not be run until the
Inter-Hub image is built and published.
- `make inter-hub-smoke` is stale: it expects public
`GET /api/v2/hubs` to return `401` and checks `/openapi.json`, while the
live contract is `200` for public hub discovery, `401` for protected
resources such as `/api/v2/widgets`, and OpenAPI at `/api/v2/openapi.json`.
- `railiance-apps` still has only the pull-request manifest dry-run workflow;
it does not yet provide a `workflow_dispatch` production deploy trigger.
Railiance deploy-surface follow-up review on 2026-06-15:
- `railiance-apps` commit `6abf753` adds `RAILIANCE-WP-0011` and addresses the
requested hardening: OCI image preflight, explicit production dry-runs,
`inter-hub-server-dry-run`, current public-read/authenticated-write smoke
contract, and `.gitea/workflows/inter-hub-production-deploy.yaml` with
`workflow_dispatch`.
- `make inter-hub-smoke` passes against live `https://hub.coulomb.social`:
public `GET /api/v2/hubs` returns discovery JSON, protected widgets and
hub-registry routes return `401 invalid_api_key` without a key, and
`/api/v2/openapi.json` lists the expected v2 resources.
- `INTER_HUB_IMAGE_TAG=5101eb5 make check-inter-hub-image` fails before Helm
with `manifest unknown`, which is the desired safe behavior while the image
is absent.
- Inter-Hub confirmed via State Hub message
`269d0ace-5b8e-4fec-a1d0-11a52ad23cc5` that the remaining deploy-side
blocker is to build and publish `gitea.coulomb.social/coulomb/inter-hub:5101eb5`
or another tag containing the same `COUNT(*)` decode fix.
Current blocker: publish a Gitea registry image for Inter-Hub commit
`5101eb5` or an equivalent fix tag, then deploy it through the approved
Railiance path and rerun the authenticated widget-create and hub-registry
smoke checks. Railiance-apps no longer appears to be the blocking surface.
Completed on 2026-06-19:
- Production Inter-Hub now runs image
`gitea.coulomb.social/coulomb/inter-hub:eed4322`, which is ahead of the
`5101eb5` COUNT-decode fix commit.
- Authenticated `GET /api/v2/hub-registry` returns HTTP `200` with the
bootstrap operator key and with a runtime key exchanged through
`POST /api/v2/token`.
- Authenticated `POST /api/v2/widgets` succeeds through the public API; a smoke
widget was created and deleted without using direct DB access.
- Result: the next VSM hub can bootstrap through the documented v2 API surface
without the earlier `COUNT(*)` decode failure class blocking widget creation
or hub-registry reads.
## Initial Acceptance Criteria
This workplan is complete when:
@@ -820,6 +1033,124 @@ Interpretation:
- HF-WP-0001 T10 is moved to `wait` until the production API is deployed or the
operator explicitly chooses the manual SQL fallback.
### 2026-06-14 — production API gate opened
Rechecked the live Inter-Hub API after deployment moved forward:
- `GET https://hub.coulomb.social/api/v2/hubs` now returns `200` with
`{"data":[],"meta":{"page":1,"per_page":50,"total":0}}`.
- The live OpenAPI includes `/hubs`, `/hub-capability-manifests`,
`/api-consumers`, and `/policy-scopes`.
- The deployed contract exposes public reads for hub and policy-scope lists,
while creation and mutation paths require `BearerAuth`.
- `IHUB_OPERATOR_KEY` was not present in this Codex shell, so the authenticated
bootstrap/smoke script was not run.
Result:
- T10 is closed as `done`.
- T02 is ready to run once the operator loads the OpenBao-custodied
`IHUB_OPERATOR_KEY` into a trusted shell.
- T03-T05 remain `wait` until the `ops-hub` row and active manifest exist.
- The next action is to run the supported bootstrap path with
`IHUB_BASE=https://hub.coulomb.social` and the operator key, while preserving
the one-time API key secret outside Git and State Hub.
### 2026-06-15 — bootstrap operator key created and custodied
The seeded Inter-Hub web admin credential was not usable on production, so the
operator explicitly chose a controlled database bootstrap for the first
temporary API key.
Non-secret evidence:
- `net-kingdom-pg-1` is healthy and contains the `interhub` database.
- `api_consumers` initially had no rows.
- A static `inter-hub-bootstrap-operator` key now exists with prefix
`8fab0bef`.
- OpenBao audit shows successful create/read activity for
`platform/data/operators/inter-hub/bootstrap-operator`.
The full key value was not printed into Git, State Hub, or chat. It was stored
manually by the operator in OpenBao. This unblocks the next attended step:
retrieve the key from OpenBao into a trusted shell and create the `ops-hub`
hub row through the supported Inter-Hub API.
### 2026-06-15 — full API bootstrap helper prepared
Added `scripts/ops-hub-bootstrap-api.py` as the HF-WP-0001 production helper.
Unlike the source-side Inter-Hub smoke script, it consumes the full
HelixForge manifest and widget seed artifacts. It supports
`IHUB_OPERATOR_KEY_FILE` and `OPS_HUB_KEY_FILE` so keys can be passed through
0600 temp files rather than shell history or chat.
Validation performed:
- `python3 -m py_compile scripts/ops-hub-bootstrap-api.py`
- `python3 scripts/ops-hub-bootstrap-api.py --help`
- JSON seed parsing for the manifest and 14 widget seeds
### 2026-06-15 — attended Ops Hub bootstrap applied
The operator provided the Inter-Hub bootstrap key through a local temp file.
The helper used that key without printing it and successfully created/reused:
- `ops-hub` hub row with VSM metadata `vsm/OPS/1`.
- Active capability manifest `00aaf90a-8e76-4b0e-892d-33b162862f38`.
- Runtime API consumer `f9e595c6-4e1d-41fd-86cb-c1830bd7ec81`.
- Display-once runtime API key, written only to a 0600 local temp file.
The helper then exposed a live Inter-Hub bug while creating widgets:
```text
UnexpectedColumnTypeStatementError 0 23 20
SELECT COUNT(*) FROM widget_type_registry WHERE name = $1 AND status = 'active'
```
Because PostgreSQL returns `COUNT(*)` as `bigint`, the Inter-Hub validation
code must decode this as `Int64` or cast the query result. The same bug class
also affects authenticated `GET /api/v2/hub-registry` through the
`api_request_log` rate-limit query.
To keep the approved bootstrap moving, the idempotent SQL fallback was applied
for the remaining data. It created:
- 14 governed `ops-hub` widgets.
- 14 initial widget version rows.
- The first `ops-endpoint-verified` Gitea registry readiness event.
Validation:
- All 14 widget types, 15 event types, and 10 annotation categories from the
manifest are present in public registries.
- `GET /api/v2/widgets` with the runtime key returns all 14 `ops-hub` widgets.
- `POST /api/v2/token` returns a short-lived Bearer token for the runtime key.
- `GET /api/v2/interaction-events` with the runtime key returns the Gitea
registry readiness event and metadata.
Remaining operator action:
- Store the generated runtime key from the local temp file in OpenBao at
`platform/operators/ops-hub/runtime`, field `OPS_HUB_KEY`, then remove the
temp file.
- Track/fix the Inter-Hub `COUNT(*)` decode issues before declaring the next
VSM hub fully scriptable through the public API.
### 2026-06-19 — HF-WP-0001 closed out
Closed the remaining bootstrap custody and production verification gaps:
- Stored the `ops-hub` runtime key in OpenBao at
`platform/operators/ops-hub/runtime` and removed the local temp file.
- Verified runtime-key token exchange, hub-registry reads, and widget listing
through the public Inter-Hub API.
- Confirmed production Inter-Hub image `eed4322` includes the deployed
COUNT-decode fix path; authenticated widget creation and hub-registry reads
now succeed without SQL fallback.
No API keys, OpenBao tokens, or secret values were copied into Git, State Hub,
chat, or workplan text.
## Notes
`ops-hub` should complement State Hub during the transition:

View File

@@ -2,13 +2,13 @@
id: HF-WP-0002
type: workplan
title: "Expose OpenBao Browser UI at bao.coulomb.social"
domain: helix_forge
domain: infotech
repo: helix-forge
status: active
status: finished
owner: codex
topic_slug: openbao-browser-ui
topic_slug: helix-forge
created: "2026-06-15"
updated: "2026-06-15"
updated: "2026-06-19"
planning_priority: high
planning_order: 2
related_repos:
@@ -149,7 +149,7 @@ general public application.
```task
id: HF-WP-0002-T02
status: progress
status: done
priority: high
target_repo: railiance-platform
state_hub_task_id: "41e52213-0a1e-417c-a4d0-5db5141b600d"
@@ -175,13 +175,20 @@ Traefik, active service routing, and the approved middleware annotations.
rate-limit middlewares, and `make openbao-deploy` applies that manifest before
the Helm upgrade. Live DNS/deployment verification remains pending.
Live progress on 2026-06-15: the operator reached the OpenBao browser UI at
`https://bao.coulomb.social`, authenticated through the approved KeyCape/OIDC
browser path, and wrote the Inter-Hub bootstrap operator key into the
`platform/` KV engine. OpenBao audit evidence shows successful access to the
expected path, so the public UI exposure is live enough for the HF-WP-0001
credential-custody workflow.
---
### T03 - Add KeyCape UI Redirect URIs
```task
id: HF-WP-0002-T03
status: progress
status: done
priority: high
target_repo: net-kingdom
state_hub_task_id: "fc1d5850-eed9-4fd6-aac4-8c0e89d8b67d"
@@ -207,13 +214,26 @@ live `openbao-client-config.py` patch/verify helper. The focused verifier also
probes CLI, `netkingdom`, and `keycape` redirect URIs. Live KeyCape rollout
verification for the preferred mount remains pending.
Live completion on 2026-06-15: patched the live `sso/keycape-config` Secret
with the code-defined OpenBao admin client settings, restarted the `sso/keycape`
deployment, and verified:
- the `openbao-admin` client and LLDAP OU lookup settings are present;
- the public KeyCape authorize endpoint accepts the CLI callback;
- the public KeyCape authorize endpoint accepts the browser UI `netkingdom`
mount callback;
- the public KeyCape authorize endpoint accepts the browser UI `keycape`
compatibility callback;
- the KeyCape discovery endpoint responds through a local port-forward to the
rolled-out pod.
---
### T04 - Add OpenBao UI Redirect URIs To platform-admin Role
```task
id: HF-WP-0002-T04
status: progress
status: done
priority: high
target_repo: railiance-platform
state_hub_task_id: "4f69cacb-9d8f-4ab6-a84f-3c9041f0d39a"
@@ -239,13 +259,40 @@ now configures both `auth/netkingdom/role/platform-admin` and the
URIs while preserving the existing localhost CLI callbacks. Live preferred
role update remains pending.
Live blocker on 2026-06-15: attempted non-secret verification using the
OpenBao pod token helper. The token can authenticate to OpenBao and generate
audit activity, but it receives `403 permission denied` for `sys/audit`,
`sys/mounts`, `sys/auth`, `sys/capabilities-self`, and
`auth/netkingdom/role/platform-admin`. Updating or verifying the live
`platform-admin` OIDC role therefore still needs an attended OpenBao root/sudo
token handoff or a browser/UI update by the operator. No token values were
printed or copied into Git, State Hub, or chat.
Completed on 2026-06-19: applied the live OpenBao OIDC configuration with an
approved operator token (not recorded). Enabled the missing `auth/netkingdom`
OIDC mount, wrote KeyCape discovery config for both `netkingdom` and `keycape`,
and aligned `auth/netkingdom/role/platform-admin` and
`auth/keycape/role/platform-admin` to the code-defined redirect URI set:
- CLI: `http://localhost:8250/oidc/callback`, `http://127.0.0.1:8250/oidc/callback`
- Browser: `https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback`
- Compatibility: `https://bao.coulomb.social/ui/vault/auth/keycape/oidc/callback`
Live verification:
- `bao read auth/netkingdom/role/platform-admin` shows all four URIs.
- `bao read auth/keycape/role/platform-admin` shows the same aligned set.
- Public `POST /v1/auth/netkingdom/oidc/auth_url` accepts the browser callback.
- `verify-openbao-client.sh` still passes CLI, `netkingdom`, and `keycape`
redirect probes.
---
### T05 - Verify Browser Login And Metadata-Only Secret Inspection
```task
id: HF-WP-0002-T05
status: wait
status: done
priority: high
state_hub_task_id: "31d1da2d-8498-4c7d-a6fa-da9d0133bfe2"
```
@@ -263,15 +310,28 @@ For the `HF-WP-0001` unblock, inspect only metadata/path presence for the
Inter-Hub operator key location. Do not copy secret values into Git, State Hub,
chat, or workplans.
Done when browser login succeeds through the preferred `netkingdom` mount and
the operator can determine whether an Inter-Hub operator key exists without
installing a local `bao` CLI.
Done when browser login succeeds through the approved KeyCape/OIDC browser
path and the operator can determine whether an Inter-Hub operator key exists
without installing a local `bao` CLI.
Progress on 2026-06-15: the operator reached the OpenBao UI and completed an
attended platform-admin browser login. The preferred `netkingdom` mount has
been added in code and remains to be rolled out and used for the final
metadata-only inspection proof.
Completed on 2026-06-15: metadata-only inspection found no existing suitable
Inter-Hub operator credential. The operator then minted a temporary
`inter-hub-bootstrap-operator` key directly in the Inter-Hub database and
stored it in OpenBao at:
```text
platform/operators/inter-hub/bootstrap-operator
```
Only non-secret evidence was recorded in the workplan and State Hub: OpenBao
audit shows successful create/read activity for the path, and the Inter-Hub DB
shows an active static key with prefix `8fab0bef`.
---
### T06 - Update Operator Runbooks
@@ -338,10 +398,9 @@ Verification performed:
Verification not performed:
- Helm chart rendering, because `helm` is not installed in this local shell.
- Live DNS/TLS/Ingress rollout.
- Live KeyCape config rollout.
- Live OpenBao role update.
- Attended browser login and metadata-only secret-path inspection.
- Live rollout verification for the preferred `netkingdom` auth mount.
### 2026-06-15 - Preferred netkingdom auth mount added
@@ -350,6 +409,20 @@ mount was changed from `keycape` to `netkingdom` to match the platform domain
language and reduce operator confusion in the UI. The `keycape` mount remains
configured as a compatibility alias.
### 2026-06-19 - Live netkingdom OIDC mount and role alignment
Applied the pending T04 live configuration:
- Enabled `auth/netkingdom` OIDC and wrote KeyCape discovery config.
- Updated `auth/netkingdom/role/platform-admin` and
`auth/keycape/role/platform-admin` with the browser and CLI redirect URI set
defined in `configure-openbao-oidc.sh`.
- Verified the public `netkingdom` OIDC `auth_url` endpoint accepts the
browser callback and `verify-openbao-client.sh` still passes.
No OpenBao tokens, secret values, or Inter-Hub keys were copied into Git,
State Hub, chat, or workplan text.
## Acceptance Criteria
This workplan is complete when:

View File

@@ -0,0 +1,343 @@
---
id: HF-WP-0003
type: workplan
title: "Streamline OpenBao login screen for KeyCape sign-in"
domain: infotech
repo: helix-forge
status: finished
owner: codex
topic_slug: helix-forge
created: "2026-06-19"
updated: "2026-06-19"
planning_priority: medium
planning_order: 3
related_repos:
- railiance-platform
- net-kingdom
- helix-forge
related_workplans:
- HF-WP-0002
state_hub_workstream_id: "ca5dcb6b-8ff6-414b-bd45-6b0fe710c868"
---
# Streamline OpenBao login screen for KeyCape sign-in
## Goal
Replace the raw OpenBao login mask at `https://bao.coulomb.social` with a
streamlined operator experience:
```text
Sign in with KeyCape
[ Sign in ]
```
Hidden presets (not user-editable):
| Field | Preset |
| --- | --- |
| Namespace | blank (root) |
| Method | OIDC |
| Mount path | `netkingdom` |
| Role | `platform-admin` |
Operators should no longer need to understand OpenBao auth-mount semantics
to sign in. The `keycape` mount remains a server-side compatibility alias but
must not appear in the default login mask.
## Context
`HF-WP-0002` exposed the OpenBao browser UI and wired KeyCape OIDC through
the `netkingdom` mount with the `platform-admin` role. Login works end to end,
but the stock OpenBao UI still shows namespace, method, mount path, and role
fields that confuse platform operators.
Architectural intent is captured in:
- `helix-forge/docs/OpenBaoIntroduction.md` — field semantics and target UX
- `helix-forge/INTENT.md` — platform custody principle
OpenBao ships UI static assets inside the container image
(`quay.io/openbao/openbao:2.5.4` today, chart `openbao/openbao` `0.28.2`).
There is **no supported API** to hide login fields or set auth defaults. The
only documented `/sys/config/ui` endpoint configures **response headers**, not
login form behaviour. Upstream feature request
[openbao/openbao#2936](https://github.com/openbao/openbao/issues/2936) proposes
a `custom-css` API but is not implemented yet.
Therefore customization must be an **overlay layer we own**, applied at deploy
time — never hand-edits inside the running pod or vendor image layers.
## Upgrade-safe overlay principle
All login-mask changes live in `railiance-platform` as a versioned overlay
package. Upgrading OpenBao image or Helm chart must **not** require hunting
for forgotten in-container edits.
### Required properties
1. **Source of truth in Git** — overlay assets, apply script, and version pin
live in `railiance-platform`, not on cluster nodes.
2. **Pinned to upstream UI version** — overlay metadata records the OpenBao
image tag (and optionally chart version) it was built against.
3. **Idempotent apply**`make openbao-deploy` (or a dedicated target) applies
the overlay every time; no manual kubectl steps for routine deploys.
4. **Documented reapply path** — after an OpenBao bump, operators follow a
short runbook: diff upstream login assets, refresh overlay patches, run
verifier, redeploy.
5. **No secret material** — overlay contains only public CSS/JS/HTML transforms
and preset auth parameters already visible in docs.
### Proposed overlay layout (implementation target)
```text
railiance-platform/helm/openbao-ui-overlay/
README.md # mechanism choice, reapply runbook entry point
VERSION # openbao image tag this overlay targets
presets.json # namespace, method, mount, role defaults
overlay.css # hide fields, KeyCape branding
overlay.js # pre-fill presets, single-button OIDC redirect
apply-overlay.sh # idempotent apply invoked from deploy pipeline
patches/ # optional unified diffs against upstream bundles
2.5.4/
manifest.sha256 # hash of upstream login asset for drift detection
```
Exact delivery mechanism is decided in T01. Candidates, in preference order
until upstream ships native customization:
| Mechanism | Upgrade resilience | Notes |
| --- | --- | --- |
| **Traefik response injection** | High — no pod filesystem mutation | Inject `overlay.css` / `overlay.js` on `/ui/*` HTML responses via middleware or plugin |
| **Init-container emptyDir overlay** | Medium — reapply on every pod start | Copy overlay into shared volume over upstream static path if chart supports `extraVolumes` |
| **Post-start patch Job** | Medium — must rerun on recreate | One-shot Job patches files; fragile if path layout changes |
| **Upstream `/sys/config/ui/custom-css`** | Highest when available | Track #2936; migrate off injection when released |
Whichever mechanism wins, keep the **same overlay asset files** so only the
apply plumbing changes when we migrate upstream.
## Security boundary
- Overlay is cosmetic and preset-only; it must not weaken auth or bypass
KeyCape/MFA.
- Preset mount `netkingdom` and role `platform-admin` remain enforced server
side by OpenBao OIDC configuration (`HF-WP-0002`).
- Do not expose root-token login, token auth, or additional auth methods in the
streamlined mask.
- Overlay injection must use Subresource Integrity or same-origin-only script
loading where possible; avoid loading third-party assets.
- No OpenBao tokens, OIDC secrets, or secret values in overlay files or Git.
## Proposed implementation
1. Spike overlay delivery options against the live chart and Traefik ingress.
2. Create the overlay package with KeyCape branding and hidden-field presets.
3. Wire overlay apply into `make openbao-deploy`.
4. Add an automated verifier that checks the public login page shape.
5. Document the OpenBao upgrade / overlay-reapply runbook.
6. Roll out to `bao.coulomb.social` and confirm attended KeyCape login still
works.
## Tasks
### T01 - Spike overlay delivery mechanism
```task
id: HF-WP-0003-T01
status: done
priority: high
target_repo: railiance-platform
state_hub_task_id: "d3aa331a-ba7b-4c02-8437-15dbbc301cc7"
```
Evaluate Traefik injection, init-container volume overlay, and chart extension
points against `openbao-values.yaml` and the running `openbao` `0.28.2` /
`2.5.4` deployment.
Record:
- chosen mechanism and why alternatives were rejected;
- exact upstream asset paths / HTML hooks used by the login page;
- `manifest.sha256` or equivalent fingerprint for drift detection after upgrades.
Done when a short decision note exists in
`railiance-platform/helm/openbao-ui-overlay/README.md` and the overlay
directory skeleton is committed.
---
### T02 - Implement overlay assets and presets
```task
id: HF-WP-0003-T02
status: done
priority: high
target_repo: railiance-platform
depends_on: HF-WP-0003-T01
state_hub_task_id: "c29cf317-6370-45f6-87dd-0526209e4d38"
```
Create `overlay.css`, `overlay.js`, and `presets.json` implementing:
- hidden namespace, method, mount path, and role inputs;
- visible “Sign in with KeyCape” copy and single sign-in control;
- programmatic defaults: blank namespace, OIDC, mount `netkingdom`, role
`platform-admin`;
- no `keycape` mount in the default mask.
Assets must be plain static files suitable for reapplication across mechanism
changes.
Done when a local/static preview or dry-run apply shows the intended mask
against the pinned `2.5.4` login markup.
---
### T03 - Wire overlay into OpenBao deploy pipeline
```task
id: HF-WP-0003-T03
status: done
priority: high
target_repo: railiance-platform
depends_on: HF-WP-0003-T02
state_hub_task_id: "682dc33d-9f33-43e5-aed1-7398a3e7f70b"
```
Integrate `apply-overlay.sh` into the OpenBao deployment path so
`make openbao-deploy` applies the overlay idempotently.
Update as needed:
- `helm/openbao-values.yaml` (extra volumes, init containers, or annotations);
- `helm/openbao-middleware.yaml` (if Traefik injection is chosen);
- `Makefile` `openbao-deploy` / `openbao-dry-run` targets.
`VERSION` must track the OpenBao image tag in `openbao-values.yaml`.
Done when `make openbao-dry-run` shows overlay resources and a staging or
production deploy applies them without manual steps.
---
### T04 - Add login-mask verifier
```task
id: HF-WP-0003-T04
status: done
priority: medium
target_repo: railiance-platform
depends_on: HF-WP-0003-T03
state_hub_task_id: "6c421b5a-2fe6-40fd-ade6-ff07fd38174a"
```
Add `scripts/openbao-verify-login-overlay.sh` (or extend
`openbao-verify-authenticated.sh`) to assert on the public login page:
- “Sign in with KeyCape” (or approved copy) is present;
- raw namespace / method / mount / role controls are not visible to users;
- OIDC redirect still targets `auth/netkingdom/oidc` with role
`platform-admin`.
Include a `--check-upstream-drift` mode that compares the live upstream login
asset fingerprint to `patches/<version>/manifest.sha256` and warns when an
OpenBao bump likely requires overlay refresh.
Done when the verifier passes against the deployed overlay and fails clearly
when overlay is missing or upstream markup drifts.
---
### T05 - Document upgrade and reapply runbook
```task
id: HF-WP-0003-T05
status: done
priority: medium
target_repo: railiance-platform
depends_on: HF-WP-0003-T04
state_hub_task_id: "3278e91d-d097-4a81-ad59-bc461760f6f0"
```
Document in `railiance-platform/docs/openbao.md` and
`helm/openbao-ui-overlay/README.md`:
- streamlined login operator path (“Sign in with KeyCape” only);
- overlay package layout and version pinning;
- **reapply checklist** when bumping OpenBao image or chart:
1. update image tag in `openbao-values.yaml`;
2. run drift check / inspect upstream login assets;
3. refresh `overlay.css` / `overlay.js` / `patches/` if hooks changed;
4. bump `VERSION` and `manifest.sha256`;
5. run `openbao-verify-login-overlay.sh`;
6. `make openbao-deploy`;
- pointer to upstream #2936 for eventual migration to native custom CSS.
Cross-link from `helix-forge/docs/OpenBaoIntroduction.md` phased checklist
item “Streamlined login mask”.
Done when the next operator can reapply the overlay after an OpenBao upgrade
without rediscovering the approach.
---
### T06 - Live rollout and attended login verification
```task
id: HF-WP-0003-T06
status: done
priority: high
depends_on: HF-WP-0003-T05
state_hub_task_id: "adfd825e-5068-4324-a7ae-f138b59fcf88"
```
Deploy the overlay to `https://bao.coulomb.social` and perform attended
verification:
1. Open the login page — only KeyCape-branded sign-in is shown.
2. Complete KeyCape OIDC login with MFA.
3. Confirm `platform-admin` session and metadata path access still work.
4. Run `openbao-verify-login-overlay.sh` against production.
Done when production login matches the target UX and verification scripts pass.
Record only non-secret evidence in this workplan.
---
## Acceptance criteria
This workplan is complete when:
1. Operators see “Sign in with KeyCape” with a single sign-in action at
`bao.coulomb.social`; namespace, method, mount, and role are not exposed.
2. Login still authenticates through `auth/netkingdom/oidc` and
`platform-admin` with KeyCape MFA.
3. Overlay assets and apply logic live entirely in `railiance-platform` Git.
4. `make openbao-deploy` applies the overlay without manual pod edits.
5. A documented reapply runbook exists for OpenBao image/chart upgrades.
6. An automated verifier catches missing overlay and upstream UI drift.
## Implementation Log
### 2026-06-19 — KeyCape login overlay live
- Chose nginx UI gateway with `sub_filter` HTML injection (upgrade-safe overlay
assets in Git; no OpenBao pod file edits).
- Added `railiance-platform/helm/openbao-ui-overlay/` assets and
`openbao-ui-gateway` Deployment/Service/Ingress.
- Disabled chart-managed OpenBao ingress; public `bao.coulomb.social` ingress
now targets the overlay gateway.
- `make openbao-deploy` applies middleware, Helm upgrade, then overlay.
- `make openbao-verify-login-overlay` passes against production, including
`--check-upstream-drift`.
## Notes
- Primary implementation repo: `railiance-platform` (Helm, overlay, verifiers,
runbooks).
- Intent and field semantics remain in `helix-forge` (`OpenBaoIntroduction.md`).
- OIDC mount/role configuration is already live from `HF-WP-0002`; this
workplan changes presentation only, not auth policy.
- When OpenBao ships native UI customization (#2936), prefer migrating to it
and retiring injection/pod-overlay plumbing while keeping the same preset
values and branding assets where possible.