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

2.4 KiB

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

secrets-engine route <catalog-id> --json

What it returns (the contract)

{
  "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.