generated from coulomb/repo-seed
Compare commits
22 Commits
386036afb1
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 98ed061649 | |||
| 27970faa0a | |||
| 071f861a65 | |||
| a1e3963002 | |||
| 94793f6544 | |||
| 0b1ea48352 | |||
| 0d6d76f230 | |||
| 02f11f8a9b | |||
| 32c552809b | |||
| 9579848c45 | |||
| 632afffc91 | |||
| 023eb20a14 | |||
| 215e62a221 | |||
| 3638ee14ad | |||
| 7becd45eea | |||
| 468b1386fa | |||
| 081ef10bc5 | |||
| e8492f153a | |||
| 0057176b31 | |||
| d7821c8c62 | |||
| 5eda99f120 | |||
| 426a943bb9 |
50
.claude/rules/credential-routing.md
Normal file
50
.claude/rules/credential-routing.md
Normal 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`
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"*
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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-15 00:03 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:**
|
||||
- ► 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`
|
||||
- ► T05 - Verify Browser Login And Metadata-Only Secret Inspection `31d1da2d`
|
||||
*(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
27
.repo-classification.yaml
Normal 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.
|
||||
63
AGENTS.md
63
AGENTS.md
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
39
INTENT.md
39
INTENT.md
@@ -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.
|
||||
|
||||
|
||||
3
SCOPE.md
3
SCOPE.md
@@ -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
275
docs/OpenBaoIntroduction.md
Normal 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.
|
||||
15
registry/NO_CAPABILITIES.md
Normal file
15
registry/NO_CAPABILITIES.md
Normal 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
12
registry/README.md
Normal 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`.
|
||||
0
registry/capabilities/.gitkeep
Normal file
0
registry/capabilities/.gitkeep
Normal file
4
registry/indexes/capabilities.yaml
Normal file
4
registry/indexes/capabilities.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
version: 1
|
||||
updated: '2026-06-16'
|
||||
domain: helix_forge
|
||||
capabilities: []
|
||||
368
scripts/ops-hub-bootstrap-api.py
Executable file
368
scripts/ops-hub-bootstrap-api.py
Executable 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)
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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: progress
|
||||
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:
|
||||
|
||||
343
workplans/HF-WP-0003-openbao-keycape-login-overlay.md
Normal file
343
workplans/HF-WP-0003-openbao-keycape-login-overlay.md
Normal 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.
|
||||
Reference in New Issue
Block a user