ops-mason/plans/rein-openweights-openrouter-approle.md
tegwick 0d62ac501d Review/optimize checklist, executive-summary format, build executor (T02-T04)
docs/review-optimize-checklist.md: six checks (naming, TTL/scoping,
redundancy, compaction, ease of use, posture), applied for real to the
rein-openweights plan's section 4 -- including a genuinely useful
finding (credentials.py already expects this exact path/delivery shape,
zero code changes needed to consume it).

docs/executive-summary-format.md: six fixed fields, no bao syntax, no
restating earlier sections, explicit approve/reject/revise decision.
Rendered for real into the plan's section 5 -- ready for an actual
decision.

src/ops_mason/{plan,executor,audit}.py: the phase-4 build executor for
credential_type openbao-approle-kv. Refuses to run against anything but
an approved plan -- verified the refusal never even calls subprocess.run.
role_id/secret_id (the AppRole's own access credential, not the
downstream secret) land as 0600 files, never logged; the HCL policy
goes over stdin, never argv; the audit trail is metadata-only. 12 tests,
all mocked at the bao boundary (no live OpenBao access from this
session).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-27 00:56:34 +02:00

8.5 KiB

id demand_source consumer_repo credential_type status approved_by approved_at created updated
rein-openweights-openrouter-approle glas-harness/workplans/GLAS-WP-0002-T02 rein-openweights openbao-approle-kv reviewed null null 2026-07-27 2026-07-27

Construction plan: rein-openweights OpenBao AppRole

1. Demand

rein-openweights (the OpenRouter-driven rein in the glas-harness family) needs to authenticate to OpenBao non-interactively — no operator OIDC session, no human in the loop at run time — to fetch its OpenRouter API key when it runs unattended (e.g. via a future scheduled trigger, or a glas-harness gateway invocation with no operator present). This was scoped as glas-harness/GLAS-WP-0002-T02 ("Option B" — see that workplan's discussion) and is currently blocked: the only credential path exercised so far is the OPENROUTER_API_KEY env-var short-circuit in rein_openweights/credentials.py; the AppRole/vault branch (_acquire_token, bao kv get) is real code, live-verifiable, but has never actually talked to OpenBao.

2. Existing-structure survey

Checked: ops-warden/registry/routing/catalog.yaml (readable directly — no OpenBao session needed for this). Not checked: live bao policy list / bao auth list / bao secrets list — this session has no valid OpenBao token (bao token lookup returns 403). Whoever executes phase 4 must re-verify against live state before applying anything below; this survey is current as of what's on disk, not as of live OpenBao ACL state.

Relevant existing catalog entry: openrouter-llm-connect (ops-warden/registry/routing/catalog.yaml:283) — an OpenRouter key already exists at platform/workloads/activity-core/llm-connect/llm-connect-provider-secrets (field OPENROUTER_API_KEY), gated by policy workload-kv-read-llm-connect-provider-secrets, auth_method: caller's own OpenBao token (operator OIDC via key-cape, or a token carrying [that policy]), marked risk: high ("provider API key with spend impact"). rein-aharness's own mail-triage/brief-daily already reuse this exact lane (see binky-control/integrations/executor-worker-secrets.md, Lane 1 — "REUSE (verified)").

Does this existing lane satisfy the demand? Partially, by design, not fully — reuse considered and declined:

  • The existing lane's auth_method is interactive-caller-shaped (OIDC / "a token carrying the policy") — it was never scoped for a bare AppRole login the way agent-harness-binky-mail's Lane 3 was. Binding a new AppRole to the existing policy is mechanically possible (Option A from the original discussion) but would give rein-openweights standing, non-interactive access to a policy explicitly flagged risk: high for spend impact, shared with activity-core's broader usage — a wider blast radius than a single rein needs.
  • Recommendation: do not reuse. Build a dedicated, narrowly-scoped AppRole + KV path for rein-openweights specifically (matches credentials.py's existing default reins/rein-openweights/openrouter path already in the code) — same reasoning agent-harness-binky-mail used for its own dedicated lane rather than widening an existing one. This keeps a leak scoped to exactly one consumer, one credential.

3. Proposed changes

# Action Object Reuse-vs-new rationale
1 create KV v2 secret path reins/rein-openweights/openrouter (field api_key) New, narrowly scoped — matches credentials.py's existing default path; declined reuse of the shared activity-core path (see §2)
2 create Policy workload-kv-read-rein-openweights-openrouter — read-only, scoped to exactly path #1 New — mirrors the shape of workload-kv-read-llm-connect-provider-secrets but scoped to one path, not shared
3 create AppRole rein-openweights, bound to policy #2 New — mirrors agent-harness-binky-mail's AppRole shape: token_ttl=15m, token_max_ttl=30m, bounded token_num_uses, secret_id_ttl per current posture (build-phase; see ops-warden/wiki/WorkloadSecurityPosture.md)
4 deliver role_id/secret_id files, 0600, to wherever the live verification runs (workstation or a future Railiance deployment) New — matches agent-harness-binky-mail's delivery pattern (~/.local/agent-harness/approle-binky-mail), analogous path for this consumer
5 propose ops-warden catalog entry rein-openweights-openrouter-approle — pointer-only, warden_executes: false, status: draft New — no existing entry covers this AppRole; openrouter-llm-connect entry stays as-is, untouched

No tear-down in this plan — no existing lane is being retired or compacted; the existing openrouter-llm-connect lane is explicitly kept as-is for its current interactive-caller consumers.

4. Review notes (phase 2)

  • Naming convention check: follows <consumer>-<credential-purpose> shape used by agent-harness-binky-mail — proposed rein-openweights-openrouter-approle for the AppRole/catalog id, workload-kv-read-rein-openweights-openrouter for the policy (matches the workload-kv-read-<path-slug> convention already visible in the existing catalog).
  • TTL/scoping check: token_ttl=15m/token_max_ttl=30m/bounded token_num_uses matches agent-harness-binky-mail exactly — no reason to diverge, this consumer's access pattern (one-shot credential fetch per run) is identical in shape.
  • Redundancy check: confirmed no second existing AppRole or policy already scoped to reins/rein-openweights/* — this is genuinely new structure, not a duplicate.
  • Compaction opportunity: none identified — nothing existing becomes redundant once this lands; the shared activity-core lane keeps its own consumers.
  • Ease of use for the consumer: matches directly — credentials.py already reads REIN_OPENWEIGHTS_APPROLE_DIR (defaults to a role_id/secret_id file pair) and a KV path override via REIN_OPENWEIGHTS_OPENROUTER_KV_PATH (defaulting to exactly reins/rein-openweights/openrouter, field api_key) — this plan's path #1 and delivery #4 need zero code changes in rein-openweights to consume; the code was already written expecting this exact shape.
  • Posture check: build phase (one founder-operator, pre-revenue) per ops-warden/wiki/WorkloadSecurityPosture.mdagent-harness-binky-mail used secret_id_ttl=0 (no expiry) at this same posture; carrying that forward here rather than adding a rotation schedule neither agent-harness-binky-mail nor current posture requires. Explicit assumption, not a silent default — flagged again in the executive summary below for the approval decision to confirm or override.

5. Executive summary (phase 3)

One-line ask: create a narrowly-scoped, non-interactive credential lane so rein-openweights can fetch its own OpenRouter API key at run time without an operator present.

Who/what gets access: a new OpenBao AppRole named rein-openweights. Nothing else — no existing role, human, or service gains anything new.

To what: read-only access to exactly one KV path, reins/rein-openweights/openrouter (field api_key) — a path that does not exist today and holds nothing else. The AppRole cannot read the existing shared activity-core/llm-connect secret or any other path.

For how long: each login is a 15-minute token, renewable up to a 30-minute cap, bounded number of uses per token — matches the existing agent-harness-binky-mail lane exactly. The AppRole credential itself (role_id/secret_id) does not expire on a schedule at current (build-phase) posture, same choice already made for agent-harness-binky-mail.

Blast radius if the credential leaks: whoever holds a valid role_id/secret_id pair can mint a short-lived token that reads one OpenRouter API key — nothing else in OpenBao. Actual damage from the OpenRouter key itself is real but bounded (API spend on that key, no data-store or infra access) and independent of this plan (same exposure openrouter-llm-connect's existing consumers already carry).

Cost to reverse: delete one AppRole, one policy, one KV path — three bao commands, no other lane or consumer affected. Fully isolated blast/reversal radius by construction (that was the point of declining reuse in §2).

Decision needed: approve as proposed, reject, or send back to phase 1 with feedback (e.g. different TTL, different path name, or "actually reuse the shared lane instead").

6. Build result (phase 4)