secrets-engine/docs/ops-warden-routing-contract.md
tegwick f87f4e5e4d refactor(catalog): explicit org/repo terminology; npm targets coulomb Gitea registry
Gitea's "project/package/release" terms are overloaded, so the catalog now uses
the most explicit words:
- org  = coulomb (the Gitea organisation)
- repo = whynot-design (the Gitea repository/product) — not an org, not a scope
- npm scope @whynot and package @whynot/design are distinct from both

Changes:
- catalog schema: replace conflated `owner` with required `org` + `repo`; `owner`
  is now a derived `org/repo` slug property
- npm-config delivery is data-driven: registry + scope live in
  delivery_config.npm and are validated; engine no longer hardcodes a registry
- exec delivery writes `<scope>:registry=<url>` + scoped `:_authToken` for the
  configured Gitea registry (token still env-expanded, never written to disk)
- pilot lane points at https://gitea.coulomb.social/api/packages/coulomb/npm/,
  scope @whynot, KV path coulomb/whynot-design/npm/publish
- npm-publish-demo uses @whynot scope so dry-run resolves the Gitea registry
- docs: terminology table; routing owner shown as coulomb/whynot-design
- tests: org/repo required, npm-config validation, registry authkey mapping

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 12:44:55 +02:00

62 lines
2.4 KiB
Markdown

# ops-warden → secrets-engine Routing Contract
ops-warden issues **SSH certificates only**. Every other credential need (API
keys, provider tokens, DB passwords, npm publish tokens) routes to
**secrets-engine**, which is OpenBao-backed. ops-warden must never request, hold,
cache, or vend a raw secret value. A route result is a **pointer**, not a key.
## What ops-warden calls
```bash
secrets-engine route <catalog-id> --json
```
## What it returns (the contract)
```json
{
"catalog_id": "whynot-design-npm-publish",
"owner": "coulomb/whynot-design",
"stage": "prod",
"decision_status": "resolved",
"decision_ref": "whynot-design-npm-publish",
"review_url": "http://127.0.0.1:8000/decisions/<id>",
"metadata_applied": true,
"value_present": true,
"ready": true,
"next_command": "secrets-engine exec --catalog whynot-design-npm-publish -- <command...>",
"missing": ""
}
```
| Field | Meaning |
| --- | --- |
| `decision_status` | `resolved`/`approved` => approved; `missing`/`pending` => not yet |
| `metadata_applied` | OpenBao ACL policy + approle exist for the lane |
| `value_present` | the secret value has been provisioned (boolean only — value never read) |
| `ready` | approved **and** applied **and** provisioned |
| `next_command` | the single safe command the caller should run next |
| `missing` | the one human/provisioning step still outstanding |
## Guarantees
- **No value crosses this boundary.** `route` reports a boolean `value_present`,
derived from a metadata/presence check — it never reads the secret.
- **Actionable when not ready.** If a lane is unapproved, unapplied, or
unprovisioned, `next_command` + `missing` tell the caller exactly what to do.
- **Idempotent / read-only.** `route` performs no mutation.
## whynot-design retry flow
1. whynot-design CI needs a publish token → asks ops-warden.
2. ops-warden runs `secrets-engine route whynot-design-npm-publish --json`.
3. If `ready=false`, it surfaces `missing` + `next_command` to the human (e.g.
"needs approved decision" or "needs provisioning").
4. Once `ready=true`, the workload runs
`secrets-engine exec --catalog whynot-design-npm-publish -- npm publish`.
## Anti-patterns (forbidden)
- ops-warden `POST /messages/` asking for `NPM_TOKEN` / `OPENROUTER_API_KEY`.
- Caching `value_present` as if it were the value.
- Inventing `warden secret` / `warden bao` — they do not exist.