generated from coulomb/repo-seed
Complete WARDEN-WP-0012 routing scenario playbooks
Add platform-secret playbooks for issue-core ingestion, OpenRouter llm-connect, object-storage STS, and database dynamic credentials. Extend the routing catalog with draft entries and implement `warden route list --stale` for quarterly drift review. Document the review cadence in AccessRouting and mark the workplan finished.
This commit is contained in:
@@ -47,9 +47,10 @@ owner. The `route` command group is a read-only lookup over the pointer catalog
|
||||
secrets.
|
||||
|
||||
```bash
|
||||
warden route list [--all] [--json] # scenarios (active-only unless --all)
|
||||
warden route show <id> [--json] # owner + wiki/canon pointers; SSH adds steps
|
||||
warden route find "issue an api key" # rank scenarios by keyword overlap
|
||||
warden route list [--all] [--json] # scenarios (active-only unless --all)
|
||||
warden route list --stale [--stale-days 90] [--all] # past review cadence
|
||||
warden route show <id> [--json] # owner + wiki/canon pointers; SSH adds steps
|
||||
warden route find "issue an api key" # rank scenarios by keyword overlap
|
||||
```
|
||||
|
||||
Full role and examples: `wiki/AccessRouting.md`.
|
||||
|
||||
@@ -127,12 +127,45 @@ entries:
|
||||
|
||||
# --- draft: owner path not yet shipped; hidden from default lookup ---
|
||||
- id: issue-core-ingestion-api-key
|
||||
title: issue-core ingestion API key (OpenBao path TBD)
|
||||
need_keywords: [issue-core, ingestion, api, key, openbao]
|
||||
title: issue-core ingestion API key (OpenBao KV + ESO)
|
||||
need_keywords: [issue-core, ingestion, api, key, openbao, issue_core_api_key, eso, external-secrets]
|
||||
owner_repo: railiance-platform
|
||||
subsystem: OpenBao + issue-core + activity-core
|
||||
warden_executes: false
|
||||
wiki_ref: wiki/playbooks/issue-core-ingestion-api-key.md#worker-checklist
|
||||
canon_ref: net-kingdom/docs/platform-identity-security-architecture.md
|
||||
reviewed: "2026-06-24"
|
||||
status: draft
|
||||
|
||||
- id: openrouter-llm-connect
|
||||
title: OpenRouter API key for llm-connect in activity-core
|
||||
need_keywords: [openrouter, llm, llm-connect, api, key, activity-core, gemini, provider, openrouter_api_key]
|
||||
owner_repo: railiance-platform
|
||||
subsystem: OpenBao + activity-core
|
||||
warden_executes: false
|
||||
wiki_ref: wiki/playbooks/openrouter-llm-connect.md#worker-checklist
|
||||
canon_ref: net-kingdom/docs/platform-identity-security-architecture.md
|
||||
reviewed: "2026-06-24"
|
||||
status: draft
|
||||
|
||||
- id: object-storage-sts
|
||||
title: Object-storage STS / temporary S3 credentials
|
||||
need_keywords: [s3, sts, object-storage, minio, artifact-store, temporary, credentials, bucket, vending]
|
||||
owner_repo: net-kingdom
|
||||
subsystem: flex-auth + OpenBao + artifact-store
|
||||
warden_executes: false
|
||||
wiki_ref: wiki/playbooks/object-storage-sts.md#worker-checklist
|
||||
canon_ref: net-kingdom/docs/object-storage-sts-credential-vending.md
|
||||
reviewed: "2026-06-24"
|
||||
status: draft
|
||||
|
||||
- id: database-dynamic-credentials
|
||||
title: Database dynamic credentials (OpenBao secrets engine)
|
||||
need_keywords: [database, db, postgres, cnpg, dynamic, credentials, password, lease, openbao]
|
||||
owner_repo: railiance-platform
|
||||
subsystem: OpenBao
|
||||
warden_executes: false
|
||||
wiki_ref: wiki/CredentialRouting.md#routing-table
|
||||
wiki_ref: wiki/playbooks/database-dynamic-credentials.md#worker-checklist
|
||||
canon_ref: net-kingdom/docs/platform-identity-security-architecture.md
|
||||
reviewed: "2026-06-18"
|
||||
reviewed: "2026-06-24"
|
||||
status: draft
|
||||
|
||||
@@ -547,17 +547,35 @@ def _entry_summary(entry) -> dict:
|
||||
}
|
||||
|
||||
|
||||
def _print_entry_table(entries, title: str) -> None:
|
||||
def _print_entry_table(
|
||||
entries, title: str, *, show_reviewed: bool = False, stale_threshold_days: int = 90
|
||||
) -> None:
|
||||
table = Table(title=title)
|
||||
table.add_column("ID")
|
||||
table.add_column("Need")
|
||||
table.add_column("Owner")
|
||||
table.add_column("warden")
|
||||
if show_reviewed:
|
||||
table.add_column("Reviewed")
|
||||
table.add_column("Days")
|
||||
table.add_column("Status")
|
||||
from warden.routing.catalog import days_since_review
|
||||
|
||||
for e in entries:
|
||||
executes = "[green]issue[/green]" if e.warden_executes else "route"
|
||||
status_styled = e.status if e.status == "active" else f"[yellow]{e.status}[/yellow]"
|
||||
table.add_row(e.id, e.title, e.owner_repo, executes, status_styled)
|
||||
if show_reviewed:
|
||||
days = days_since_review(e.reviewed)
|
||||
reviewed_styled = (
|
||||
f"[yellow]{e.reviewed}[/yellow]"
|
||||
if days > stale_threshold_days
|
||||
else e.reviewed
|
||||
)
|
||||
table.add_row(
|
||||
e.id, e.title, e.owner_repo, executes, reviewed_styled, str(days), status_styled
|
||||
)
|
||||
else:
|
||||
table.add_row(e.id, e.title, e.owner_repo, executes, status_styled)
|
||||
console.print(table)
|
||||
|
||||
|
||||
@@ -566,22 +584,55 @@ def route_list(
|
||||
output_json: Annotated[bool, typer.Option("--json", help="Output JSON")] = False,
|
||||
all_entries: Annotated[bool, typer.Option("--all", help="Include draft entries")] = False,
|
||||
tag: Annotated[Optional[str], typer.Option("--tag", help="Filter by need keyword")] = None,
|
||||
stale_only: Annotated[
|
||||
bool, typer.Option("--stale", help="Show entries past review cadence (see --stale-days)")
|
||||
] = False,
|
||||
stale_days: Annotated[
|
||||
int,
|
||||
typer.Option(
|
||||
"--stale-days",
|
||||
help="Days since reviewed before an entry is stale (default 90)",
|
||||
min=1,
|
||||
),
|
||||
] = 90,
|
||||
) -> None:
|
||||
"""List routing scenarios. Active-only unless --all."""
|
||||
from warden.routing.catalog import days_since_review
|
||||
|
||||
catalog = _load_catalog()
|
||||
entries = catalog.listed(include_draft=all_entries)
|
||||
if stale_only:
|
||||
entries = catalog.stale(include_draft=all_entries, threshold_days=stale_days)
|
||||
else:
|
||||
entries = catalog.listed(include_draft=all_entries)
|
||||
if tag:
|
||||
t = tag.lower()
|
||||
entries = [e for e in entries if t in [k.lower() for k in e.need_keywords]]
|
||||
|
||||
if output_json:
|
||||
print(json.dumps([_entry_summary(e) for e in entries], indent=2))
|
||||
payload = []
|
||||
for e in entries:
|
||||
row = _entry_summary(e)
|
||||
if stale_only:
|
||||
row["days_since_review"] = days_since_review(e.reviewed)
|
||||
row["stale_threshold_days"] = stale_days
|
||||
payload.append(row)
|
||||
print(json.dumps(payload, indent=2))
|
||||
return
|
||||
|
||||
if not entries:
|
||||
console.print("No matching routing entries.")
|
||||
if stale_only:
|
||||
console.print(f"No stale routing entries (threshold: {stale_days} days since reviewed).")
|
||||
else:
|
||||
console.print("No matching routing entries.")
|
||||
return
|
||||
_print_entry_table(entries, "Routing scenarios")
|
||||
title = (
|
||||
f"Stale routing scenarios (>{stale_days}d since reviewed)"
|
||||
if stale_only
|
||||
else "Routing scenarios"
|
||||
)
|
||||
_print_entry_table(
|
||||
entries, title, show_reviewed=stale_only, stale_threshold_days=stale_days
|
||||
)
|
||||
|
||||
|
||||
@route_app.command("show")
|
||||
|
||||
@@ -15,6 +15,7 @@ from __future__ import annotations
|
||||
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
|
||||
@@ -36,6 +37,26 @@ _REQUIRED_FIELDS = (
|
||||
)
|
||||
_VALID_STATUS = ("active", "draft")
|
||||
|
||||
# Default review cadence — see wiki/AccessRouting.md#drift-review-cadence
|
||||
DEFAULT_STALE_DAYS = 90
|
||||
|
||||
|
||||
def days_since_review(reviewed: str, *, today: Optional[date] = None) -> int:
|
||||
"""Calendar days between reviewed date (YYYY-MM-DD) and today."""
|
||||
reviewed_date = date.fromisoformat(reviewed)
|
||||
ref = today or date.today()
|
||||
return (ref - reviewed_date).days
|
||||
|
||||
|
||||
def is_review_stale(
|
||||
reviewed: str,
|
||||
*,
|
||||
threshold_days: int = DEFAULT_STALE_DAYS,
|
||||
today: Optional[date] = None,
|
||||
) -> bool:
|
||||
"""True when reviewed date is older than the cadence threshold."""
|
||||
return days_since_review(reviewed, today=today) > threshold_days
|
||||
|
||||
|
||||
class CatalogError(Exception):
|
||||
"""Raised when the routing catalog is missing or invalid."""
|
||||
@@ -89,6 +110,20 @@ class Catalog:
|
||||
scored.sort(key=lambda pair: (-pair[0], pair[1].id))
|
||||
return [e for _, e in scored[:limit]]
|
||||
|
||||
def stale(
|
||||
self,
|
||||
include_draft: bool = False,
|
||||
threshold_days: int = DEFAULT_STALE_DAYS,
|
||||
*,
|
||||
today: Optional[date] = None,
|
||||
) -> List[RouteEntry]:
|
||||
"""Entries whose reviewed date is past the cadence threshold."""
|
||||
return [
|
||||
e
|
||||
for e in self.listed(include_draft=include_draft)
|
||||
if is_review_stale(e.reviewed, threshold_days=threshold_days, today=today)
|
||||
]
|
||||
|
||||
|
||||
def _parse_entry(raw: dict, index: int) -> RouteEntry:
|
||||
if not isinstance(raw, dict):
|
||||
|
||||
@@ -11,8 +11,10 @@ import yaml
|
||||
from typer.testing import CliRunner
|
||||
|
||||
from warden.cli import app
|
||||
from datetime import date
|
||||
|
||||
from warden.routing import CatalogError, load_catalog
|
||||
from warden.routing.catalog import find_catalog_path
|
||||
from warden.routing.catalog import days_since_review, find_catalog_path, is_review_stale
|
||||
|
||||
runner = CliRunner()
|
||||
|
||||
@@ -130,6 +132,41 @@ def test_find_ssh_tunnel_top_match():
|
||||
assert matches and matches[0].id == "ops-bridge-tunnel"
|
||||
|
||||
|
||||
def test_find_openrouter_key():
|
||||
catalog = load_catalog(_repo_catalog())
|
||||
matches = catalog.find("openrouter api key", include_draft=True)
|
||||
assert matches and matches[0].id == "openrouter-llm-connect"
|
||||
|
||||
|
||||
def test_find_object_storage_sts():
|
||||
catalog = load_catalog(_repo_catalog())
|
||||
matches = catalog.find("s3 temporary credentials", include_draft=True)
|
||||
assert matches and matches[0].id == "object-storage-sts"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Review staleness
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_days_since_review():
|
||||
assert days_since_review("2026-06-01", today=date(2026, 6, 24)) == 23
|
||||
|
||||
|
||||
def test_is_review_stale_past_threshold():
|
||||
assert is_review_stale("2026-01-01", threshold_days=90, today=date(2026, 6, 24))
|
||||
|
||||
|
||||
def test_is_review_stale_within_threshold():
|
||||
assert not is_review_stale("2026-06-01", threshold_days=90, today=date(2026, 6, 24))
|
||||
|
||||
|
||||
def test_catalog_stale_filters_entries():
|
||||
catalog = load_catalog(_repo_catalog())
|
||||
stale = catalog.stale(threshold_days=0, today=date(2026, 6, 25))
|
||||
assert stale
|
||||
assert all(e.reviewed <= "2026-06-24" for e in stale)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# CLI (uses the repo catalog via env override)
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -181,6 +218,34 @@ def test_cli_find_json(repo_catalog_env):
|
||||
assert "ops-bridge-tunnel" in ids
|
||||
|
||||
|
||||
def test_cli_list_stale_json(repo_catalog_env):
|
||||
result = runner.invoke(
|
||||
app, ["route", "list", "--stale", "--stale-days", "1", "--json"]
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
data = json.loads(result.stdout)
|
||||
assert data
|
||||
assert all("days_since_review" in row for row in data)
|
||||
assert all(row["stale_threshold_days"] == 1 for row in data)
|
||||
|
||||
|
||||
def test_cli_list_stale_empty_with_high_threshold(repo_catalog_env):
|
||||
result = runner.invoke(
|
||||
app, ["route", "list", "--stale", "--stale-days", "9999"]
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
assert "No stale" in result.output
|
||||
|
||||
|
||||
def test_cli_find_openrouter_draft_only_with_all(repo_catalog_env):
|
||||
result = runner.invoke(
|
||||
app, ["route", "find", "openrouter api key", "--all", "--json"]
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
ids = [e["id"] for e in json.loads(result.stdout)]
|
||||
assert "openrouter-llm-connect" in ids
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# T5 drift guard — every wiki_ref anchor resolves, every entry has a reviewed date
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -65,9 +65,10 @@ OpenBao, flex-auth, key-cape, or any other subsystem, and never returns secret
|
||||
material.
|
||||
|
||||
```bash
|
||||
warden route list [--json] [--all] [--tag <keyword>] # active-only unless --all
|
||||
warden route show <id> [--json] # owner + pointers; SSH adds steps
|
||||
warden route find "<free text need>" [--json] [--all] # rank by keyword overlap
|
||||
warden route list [--json] [--all] [--tag <keyword>] # active-only unless --all
|
||||
warden route list --stale [--stale-days 90] [--all] [--json] # past review cadence
|
||||
warden route show <id> [--json] # owner + pointers; SSH adds steps
|
||||
warden route find "<free text need>" [--json] [--all] # rank by keyword overlap
|
||||
```
|
||||
|
||||
Agent-oriented examples:
|
||||
@@ -113,6 +114,46 @@ Report drift via a custodian workplan or a State Hub message to `ops-warden`.
|
||||
|
||||
---
|
||||
|
||||
## Drift review cadence
|
||||
|
||||
Every catalog entry carries a `reviewed:` date (`YYYY-MM-DD`) — the last time an
|
||||
ops-warden steward confirmed the pointer still matches net-kingdom canon and the
|
||||
owner repo's shipped path.
|
||||
|
||||
| Cadence | Action |
|
||||
| --- | --- |
|
||||
| **Quarterly** (default 90 days) | Run `warden route list --stale` — reconcile every listed entry against canon |
|
||||
| **On canon change** | When net-kingdom security docs change, review affected `canon_ref` entries immediately |
|
||||
| **On owner ship** | When an owning repo merges a new OpenBao path or playbook, promote `draft` → `active` and bump `reviewed` |
|
||||
| **On agent confusion** | If `warden route find` misses a common query, add `need_keywords` or a playbook — do not restate owner procedure in the catalog |
|
||||
|
||||
### Stale check (operators and agents)
|
||||
|
||||
```bash
|
||||
# Entries not reviewed in the last 90 days (default threshold)
|
||||
warden route list --stale
|
||||
|
||||
# Include draft scenarios in the stale report
|
||||
warden route list --stale --all
|
||||
|
||||
# Custom threshold (e.g. monthly review)
|
||||
warden route list --stale --stale-days 30 --json
|
||||
```
|
||||
|
||||
For each stale entry:
|
||||
|
||||
1. Open `canon_ref` in net-kingdom — confirm ownership and vocabulary unchanged.
|
||||
2. Open `wiki_ref` in this repo — update the playbook section if canon moved.
|
||||
3. Confirm the owner path still exists (anti-stale rule: unshipped paths stay `draft`).
|
||||
4. Bump `reviewed:` in `registry/routing/catalog.yaml` to today's date.
|
||||
5. Run `uv run pytest tests/test_routing.py` — anchor resolution must still pass.
|
||||
|
||||
CI enforces structural drift (every `wiki_ref` anchor resolves; no-double-source
|
||||
rule). The quarterly cadence catches **semantic** drift CI cannot detect — canon
|
||||
moved but anchors still resolve.
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
- `CredentialRouting.md` — worker decision tree and routing table
|
||||
|
||||
@@ -87,6 +87,16 @@ executes.
|
||||
| `ops-bridge-tunnel` | "ops-bridge owns transport — supply a `cert_command`" | Open the tunnel with ops-bridge |
|
||||
| `railiance-infra-principals` | "railiance-infra deploys host principals" | Run the infra Ansible |
|
||||
| `activity-core-issue-sink` | "activity-core + issue-core own emission — pair `ISSUE_CORE_*` env vars" | See `wiki/playbooks/activity-core-issue-sink.md` |
|
||||
| `inter-hub-bootstrap-ssh` | "Inter-Hub bootstrap SSH envelope — attended vs unattended branches" | See `wiki/InterHubBootstrapAccessLane.md` |
|
||||
|
||||
**Draft** (hidden from default lookup until owner path ships — `warden route list --all`):
|
||||
|
||||
| Catalog `id` | Routing focus | Playbook |
|
||||
| --- | --- | --- |
|
||||
| `issue-core-ingestion-api-key` | OpenBao KV + ESO for `ISSUE_CORE_API_KEY` | `wiki/playbooks/issue-core-ingestion-api-key.md` |
|
||||
| `openrouter-llm-connect` | OpenRouter key → `llm-connect` in activity-core | `wiki/playbooks/openrouter-llm-connect.md` |
|
||||
| `object-storage-sts` | NK-WP-0007 STS vending path | `wiki/playbooks/object-storage-sts.md` |
|
||||
| `database-dynamic-credentials` | OpenBao database secrets engine | `wiki/playbooks/database-dynamic-credentials.md` |
|
||||
|
||||
ops-warden answers *where + who*; the worker acts on the owning system. ops-warden
|
||||
never performs the non-SSH step on the worker's behalf.
|
||||
|
||||
102
wiki/playbooks/database-dynamic-credentials.md
Normal file
102
wiki/playbooks/database-dynamic-credentials.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# Database Dynamic Credentials — OpenBao
|
||||
|
||||
Date: 2026-06-24
|
||||
Workplan: WARDEN-WP-0012 T4
|
||||
Catalog: `database-dynamic-credentials` (draft until engine ships)
|
||||
|
||||
Pointer playbook for short-lived database passwords issued by OpenBao dynamic
|
||||
secret engines (e.g. CNPG-managed PostgreSQL). ops-warden does not issue DB
|
||||
credentials — custody and engine configuration belong to `railiance-platform`;
|
||||
consumers request credentials through approved paths after flex-auth policy where
|
||||
required.
|
||||
|
||||
---
|
||||
|
||||
## Owners
|
||||
|
||||
| Concern | Owner repo | Authoritative doc |
|
||||
| --- | --- | --- |
|
||||
| OpenBao database engine, paths, policies | `railiance-platform` | `docs/openbao.md`, `workplans/RAIL-PL-WP-0002-openbao-platform-secrets-service.md` |
|
||||
| Authorization before sensitive reads | `flex-auth` | `INTENT.md` |
|
||||
| Application connection and lease handling | Owning app repo | App-specific deployment docs |
|
||||
|
||||
---
|
||||
|
||||
## Do not ask ops-warden
|
||||
|
||||
```bash
|
||||
warden route show openbao-api-key --json
|
||||
warden route show database-dynamic-credentials --json # after promotion
|
||||
```
|
||||
|
||||
Never paste DB passwords, connection strings with credentials, or root DB admin
|
||||
tokens in Git, State Hub, logs, or agent chat.
|
||||
|
||||
---
|
||||
|
||||
## Platform path convention
|
||||
|
||||
From `railiance-platform/docs/openbao.md`:
|
||||
|
||||
```text
|
||||
platform/databases/<consumer>
|
||||
```
|
||||
|
||||
Dynamic credentials are issued via OpenBao database secrets engine roles — not
|
||||
static KV copies. Coordinate the exact mount and role name with platform before
|
||||
wiring workloads.
|
||||
|
||||
**Promotion gate:** catalog entry stays `status: draft` until the database
|
||||
secrets engine and consumer role exist in the live cluster.
|
||||
|
||||
---
|
||||
|
||||
## Worker checklist
|
||||
|
||||
### 1. Confirm need type
|
||||
|
||||
- [ ] Short-lived DB password (dynamic) vs long-lived KV secret — prefer dynamic
|
||||
- [ ] Target database identified (CNPG cluster, service name, database name)
|
||||
- [ ] flex-auth policy requires approval for this read (if tenant policy says so)
|
||||
|
||||
### 2. Platform provisioning (operator)
|
||||
|
||||
- [ ] Database secrets engine configured with least-privilege creation statements
|
||||
- [ ] Role TTL aligned to workload session (minutes–hours, not days)
|
||||
- [ ] Path registered under `platform/databases/<consumer>`
|
||||
- [ ] Audit logging enabled on secret access
|
||||
|
||||
### 3. Workload consumption
|
||||
|
||||
- [ ] App uses ESO or CSI to materialize username/password into K8s Secret
|
||||
- [ ] Connection pool handles credential rotation before lease expiry
|
||||
- [ ] No hard-coded passwords in Helm values or ConfigMaps
|
||||
|
||||
### 4. Verify
|
||||
|
||||
- [ ] App connects with issued credentials
|
||||
- [ ] Lease renewal or re-read succeeds before expiry
|
||||
- [ ] Revocation on pod teardown (if policy requires)
|
||||
|
||||
### 5. Rotation / revocation
|
||||
|
||||
- [ ] OpenBao revokes lease on role change
|
||||
- [ ] Platform operator documents break-glass DB admin path separately (not via warden)
|
||||
|
||||
---
|
||||
|
||||
## Owner-repo next actions
|
||||
|
||||
| Repo | Action |
|
||||
| --- | --- |
|
||||
| `railiance-platform` | Configure database secrets engine, roles, and policies |
|
||||
| Owning application | Wire ESO/CSI and connection handling for lease TTL |
|
||||
| `flex-auth` | Policy for database credential requests (if gated) |
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
- `railiance-platform/docs/openbao.md`
|
||||
- `railiance-platform/workplans/RAIL-PL-WP-0002-openbao-platform-secrets-service.md`
|
||||
- `wiki/CredentialRouting.md#routing-table`
|
||||
122
wiki/playbooks/issue-core-ingestion-api-key.md
Normal file
122
wiki/playbooks/issue-core-ingestion-api-key.md
Normal file
@@ -0,0 +1,122 @@
|
||||
# issue-core Ingestion API Key — OpenBao Custody
|
||||
|
||||
Date: 2026-06-24
|
||||
Workplan: WARDEN-WP-0012 T1
|
||||
Catalog: `issue-core-ingestion-api-key` (draft until path ships)
|
||||
|
||||
Pointer playbook for agents and operators wiring the **shared ingestion key**
|
||||
between `activity-core` IssueSink emission and `issue-core` REST ingestion.
|
||||
ops-warden does not vend this key — custody belongs to `railiance-platform`
|
||||
(OpenBao) and the consuming workloads.
|
||||
|
||||
---
|
||||
|
||||
## Owners
|
||||
|
||||
| Concern | Owner repo | Authoritative doc |
|
||||
| --- | --- | --- |
|
||||
| OpenBao path, ESO delivery, rotation ceremony | `railiance-platform` | `docs/argocd-gitops.md` — OpenBao path convention |
|
||||
| Ingestion server (`POST /issues/`) | `issue-core` | `README.md` — REST Ingestion Server |
|
||||
| IssueSink consumer | `activity-core` | `docs/issue-core-emission-boundary.md` |
|
||||
| Emission pairing checklist | `ops-warden` | `wiki/playbooks/activity-core-issue-sink.md` |
|
||||
|
||||
---
|
||||
|
||||
## Do not ask ops-warden
|
||||
|
||||
`ISSUE_CORE_API_KEY` is not an SSH certificate. Generic API-key routing:
|
||||
|
||||
```bash
|
||||
warden route show openbao-api-key --json
|
||||
warden route show activity-core-issue-sink --json
|
||||
```
|
||||
|
||||
Never paste key values into Git, State Hub, workplans, logs, or agent chat.
|
||||
|
||||
---
|
||||
|
||||
## Canonical OpenBao path (expected)
|
||||
|
||||
Coordinate with `railiance-platform` before writing secrets. Documented custody
|
||||
shape:
|
||||
|
||||
```text
|
||||
platform/workloads/issue-core/issue-core/issue-core-runtime
|
||||
```
|
||||
|
||||
Expected properties (names only — no values):
|
||||
|
||||
```text
|
||||
ISSUE_CORE_API_KEY
|
||||
GITEA_BACKEND_TOKEN
|
||||
```
|
||||
|
||||
The ExternalSecret manifest belongs in `issue-core` workload manifests (tenant
|
||||
repo owns runtime deployment). Platform owns mount policy and path provisioning.
|
||||
|
||||
**Promotion gate:** catalog entry stays `status: draft` until this path exists
|
||||
in the live OpenBao cluster and an owner-repo ExternalSecret is merged.
|
||||
|
||||
---
|
||||
|
||||
## Worker checklist
|
||||
|
||||
### 1. Confirm path with platform owner
|
||||
|
||||
- [ ] Path exists: `platform/workloads/issue-core/issue-core/issue-core-runtime`
|
||||
- [ ] KV policy allows `issue-core` service account read (workload-kv-read template)
|
||||
- [ ] `railiance-platform` workplan records the canonical path (no forked conventions)
|
||||
|
||||
### 2. External Secrets Operator pattern
|
||||
|
||||
Prefer ESO for values that become Kubernetes Secrets consumed by Helm charts
|
||||
(`railiance-platform/docs/openbao.md`, `docs/argocd-gitops.md`):
|
||||
|
||||
- [ ] `ExternalSecret` in `issue-core` namespace targets the path above
|
||||
- [ ] Secret keys map to `ISSUE_CORE_API_KEY` (and `GITEA_BACKEND_TOKEN` if used)
|
||||
- [ ] `activity-core` deployment receives the **same** key value via its own
|
||||
ExternalSecret (paired env vars — see activity-core-issue-sink playbook)
|
||||
- [ ] Do not use the OpenBao injector in the current deployment
|
||||
|
||||
### 3. Local dev (no OpenBao)
|
||||
|
||||
Generate once and export on both processes — not for production:
|
||||
|
||||
```bash
|
||||
export ISSUE_CORE_API_KEY="$(python3 -c 'import secrets; print(secrets.token_urlsafe(32))')"
|
||||
```
|
||||
|
||||
See `wiki/playbooks/activity-core-issue-sink.md#worker-checklist` for pairing steps.
|
||||
|
||||
### 4. Rotation
|
||||
|
||||
- [ ] Generate new key in OpenBao (platform operator ceremony)
|
||||
- [ ] Update both `issue-core` and `activity-core` Secrets before revoking old value
|
||||
- [ ] Verify one live POST returns `201` with `issue_id`
|
||||
- [ ] Record rotation in platform audit log — not in git
|
||||
|
||||
### 5. Privileged read policy
|
||||
|
||||
Break-glass and operator reads follow `railiance-platform/docs/openbao.md` —
|
||||
scoped tokens only, never root token for routine workload secret inspection.
|
||||
|
||||
---
|
||||
|
||||
## Owner-repo next actions
|
||||
|
||||
| Repo | Action |
|
||||
| --- | --- |
|
||||
| `railiance-platform` | Provision KV path, policy, and document in OpenBao runbook |
|
||||
| `issue-core` | Merge ExternalSecret + Deployment env from synced Secret |
|
||||
| `activity-core` | Mirror `ISSUE_CORE_API_KEY` injection for REST sink mode |
|
||||
|
||||
When the path ships, ops-warden promotes `issue-core-ingestion-api-key` to
|
||||
`status: active` with this `wiki_ref`.
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
- `wiki/playbooks/activity-core-issue-sink.md`
|
||||
- `railiance-platform/docs/argocd-gitops.md`
|
||||
- `warden route show issue-core-ingestion-api-key --all --json`
|
||||
123
wiki/playbooks/object-storage-sts.md
Normal file
123
wiki/playbooks/object-storage-sts.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# Object-Storage STS Credential Vending
|
||||
|
||||
Date: 2026-06-24
|
||||
Workplan: WARDEN-WP-0012 T4
|
||||
Catalog: `object-storage-sts` (draft until vending path ships)
|
||||
|
||||
Pointer playbook for short-lived S3-compatible credentials. NetKingdom canon
|
||||
defines the pattern; `flex-auth` decides, OpenBao brokers, `railiance-platform`
|
||||
configures backends, and consumers (e.g. `artifact-store`) refresh credentials.
|
||||
|
||||
ops-warden does not vend object-storage credentials.
|
||||
|
||||
---
|
||||
|
||||
## Owners
|
||||
|
||||
| Concern | Owner repo | Authoritative doc |
|
||||
| --- | --- | --- |
|
||||
| Architecture and trust boundaries | `net-kingdom` | `docs/object-storage-sts-credential-vending.md` |
|
||||
| Policy decision (may this principal access bucket/prefix?) | `flex-auth` | `INTENT.md` |
|
||||
| OpenBao broker config, audit, bootstrap parent creds | `railiance-platform` | `docs/openbao.md` — Artifact-Store handoff |
|
||||
| S3 client refresh and package behavior | `artifact-store` | `ARTIFACT-STORE-WP-0007` |
|
||||
|
||||
---
|
||||
|
||||
## Do not ask ops-warden
|
||||
|
||||
```bash
|
||||
warden route show openbao-api-key --json
|
||||
warden route show object-storage-sts --json # after promotion
|
||||
```
|
||||
|
||||
Never paste access keys, session tokens, or parent credentials in Git, State Hub,
|
||||
logs, or agent chat.
|
||||
|
||||
---
|
||||
|
||||
## Core flow (pointer only)
|
||||
|
||||
Full procedure is in net-kingdom canon. Summary for routing:
|
||||
|
||||
```text
|
||||
Principal (human/service/agent)
|
||||
→ IAM Profile token (key-cape / Keycloak)
|
||||
→ credential-vending service
|
||||
→ flex-auth decision (tenant, bucket, prefix, actions, TTL)
|
||||
→ backend exchange (STS / OpenBao-assisted broker)
|
||||
→ temporary S3 credentials → consumer
|
||||
```
|
||||
|
||||
OpenBao is runtime secret infrastructure — not the canonical authorization engine.
|
||||
|
||||
---
|
||||
|
||||
## Platform path conventions
|
||||
|
||||
From `railiance-platform/docs/openbao.md`:
|
||||
|
||||
```text
|
||||
platform/object-storage/<consumer>
|
||||
```
|
||||
|
||||
Example bootstrap bridge (static key, pre-STS):
|
||||
|
||||
```text
|
||||
platform/object-storage/artifact-store
|
||||
```
|
||||
|
||||
STS vending remains governed by NK-WP-0007 / `ARTIFACT-STORE-WP-0007`. Promote
|
||||
catalog entry to `active` only when the approved vending path for your consumer
|
||||
exists in live OpenBao policy and canon.
|
||||
|
||||
---
|
||||
|
||||
## Worker checklist
|
||||
|
||||
### 1. Confirm consumer and canon
|
||||
|
||||
- [ ] Read `net-kingdom/docs/object-storage-sts-credential-vending.md`
|
||||
- [ ] Identify `protected_system_id` (e.g. `object-storage:artifact-store-prod`)
|
||||
- [ ] Confirm flex-auth policy package for your tenant/resource
|
||||
|
||||
### 2. Authorization before secret read
|
||||
|
||||
- [ ] Obtain IAM Profile token with required claims
|
||||
- [ ] flex-auth returns allow + obligations (TTL, prefix scope, actions)
|
||||
- [ ] Do not skip flex-auth and read parent credentials from OpenBao directly
|
||||
|
||||
### 3. Credential delivery
|
||||
|
||||
- [ ] Platform provisions broker config under `platform/object-storage/...`
|
||||
- [ ] Consumer receives credentials via approved delivery (ESO, CSI, sidecar)
|
||||
- [ ] For `artifact-store`: configure `ARTIFACTSTORE_S3_*_REF` file/env refs
|
||||
|
||||
### 4. Verify
|
||||
|
||||
```bash
|
||||
artifactstore storage verify --backend s3
|
||||
```
|
||||
|
||||
### 5. Rotation / expiry
|
||||
|
||||
- [ ] Prefer lease expiry and dynamic regeneration over long-lived keys
|
||||
- [ ] Consumer must support session-token refresh or sidecar refresh (see canon gap notes)
|
||||
|
||||
---
|
||||
|
||||
## Owner-repo next actions
|
||||
|
||||
| Repo | Action |
|
||||
| --- | --- |
|
||||
| `net-kingdom` | Maintain STS vending canon; NK-WP-0007 decisions |
|
||||
| `flex-auth` | Policy packages for object-storage resources |
|
||||
| `railiance-platform` | Backend parent creds, OpenBao mounts, audit |
|
||||
| `artifact-store` | S3 backend refresh behavior and verify smoke |
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
- `net-kingdom/docs/object-storage-sts-credential-vending.md`
|
||||
- `railiance-platform/docs/openbao.md#artifact-store-object-storage-handoff`
|
||||
- `wiki/CredentialRouting.md#quick-decision-tree`
|
||||
104
wiki/playbooks/openrouter-llm-connect.md
Normal file
104
wiki/playbooks/openrouter-llm-connect.md
Normal file
@@ -0,0 +1,104 @@
|
||||
# OpenRouter API Key — llm-connect in activity-core
|
||||
|
||||
Date: 2026-06-24
|
||||
Workplan: WARDEN-WP-0012 T4
|
||||
Catalog: `openrouter-llm-connect` (draft until OpenBao path ships)
|
||||
|
||||
Pointer playbook for LLM provider credentials consumed by `llm-connect` in the
|
||||
`activity-core` namespace. ops-warden issues SSH certs only — API keys are an
|
||||
OpenBao → Kubernetes Secret action owned by `railiance-platform` and
|
||||
`activity-core` deployment repos.
|
||||
|
||||
---
|
||||
|
||||
## Owners
|
||||
|
||||
| Concern | Owner repo | Authoritative doc |
|
||||
| --- | --- | --- |
|
||||
| OpenBao path and ESO delivery | `railiance-platform` | `docs/openbao.md` — path convention |
|
||||
| llm-connect K8s overlay and smoke | `llm-connect` | `deploy/k8s/activity-core-llm-connect/README.md` |
|
||||
| activity-core runtime config (`LLM_CONNECT_URL`) | `activity-core` | `llm-connect/docs/activity-core-llm-endpoint.md` |
|
||||
|
||||
---
|
||||
|
||||
## Do not ask ops-warden
|
||||
|
||||
```bash
|
||||
warden route show openbao-api-key --json
|
||||
warden route show openrouter-llm-connect --json # after promotion
|
||||
```
|
||||
|
||||
`OPENROUTER_API_KEY` must not appear in Git, State Hub, workplans, logs, or chat.
|
||||
|
||||
---
|
||||
|
||||
## Expected custody shape
|
||||
|
||||
Documented platform path convention (coordinate before writing secrets):
|
||||
|
||||
```text
|
||||
platform/workloads/activity-core/llm-connect/llm-connect-provider-secrets
|
||||
```
|
||||
|
||||
Property name: `OPENROUTER_API_KEY`
|
||||
|
||||
Until the OpenBao path is provisioned, operators may create the K8s Secret
|
||||
directly for pilot smoke (`llm-connect` README) — that is a bootstrap bridge,
|
||||
not the long-term custody model.
|
||||
|
||||
**Promotion gate:** catalog entry stays `status: draft` until the OpenBao path
|
||||
exists and ESO (or approved equivalent) delivers the Secret in cluster.
|
||||
|
||||
---
|
||||
|
||||
## Worker checklist
|
||||
|
||||
### 1. Confirm need
|
||||
|
||||
- [ ] Consumer is `llm-connect` in `activity-core` namespace (not a generic OpenRouter client)
|
||||
- [ ] Default profile uses `provider=openrouter` (`llm-connect/docs/activity-core-llm-endpoint.md`)
|
||||
- [ ] flex-auth policy applies if your tenant requires pre-approval for secret reads
|
||||
|
||||
### 2. Platform path (production)
|
||||
|
||||
- [ ] Path provisioned under `platform/workloads/activity-core/...`
|
||||
- [ ] Workload KV read policy scoped to `llm-connect` service account
|
||||
- [ ] ExternalSecret syncs to Secret `llm-connect-provider-secrets`
|
||||
|
||||
### 3. Deployment wiring
|
||||
|
||||
- [ ] `kubectl apply -k deploy/k8s/activity-core-llm-connect` (llm-connect repo)
|
||||
- [ ] Deployment mounts provider Secret; env provides `OPENROUTER_API_KEY`
|
||||
- [ ] activity-core sets `LLM_CONNECT_URL` to in-cluster service URL
|
||||
|
||||
### 4. Smoke
|
||||
|
||||
```bash
|
||||
# From llm-connect repo — cluster smoke after apply
|
||||
kubectl -n activity-core rollout status deployment/llm-connect
|
||||
# See deploy/k8s/activity-core-llm-connect/README.md for endpoint smoke script
|
||||
```
|
||||
|
||||
### 5. Rotation
|
||||
|
||||
- [ ] Update OpenBao KV value
|
||||
- [ ] ESO refresh or rollout restart llm-connect Deployment
|
||||
- [ ] Run cluster smoke; confirm activity-core triage profile still reaches provider
|
||||
|
||||
---
|
||||
|
||||
## Owner-repo next actions
|
||||
|
||||
| Repo | Action |
|
||||
| --- | --- |
|
||||
| `railiance-platform` | Provision OpenBao path + policy for activity-core llm-connect |
|
||||
| `llm-connect` | Maintain K8s overlay and document Secret key names |
|
||||
| `activity-core` | Set `LLM_CONNECT_URL` and triage profile after llm-connect is live |
|
||||
|
||||
---
|
||||
|
||||
## See also
|
||||
|
||||
- `llm-connect/docs/activity-core-llm-endpoint.md`
|
||||
- `wiki/CredentialRouting.md#examples-do-not-ask-ops-warden`
|
||||
- `net-kingdom/docs/platform-identity-security-architecture.md`
|
||||
@@ -4,7 +4,7 @@ type: workplan
|
||||
title: "Routing Scenario Playbooks"
|
||||
domain: infotech
|
||||
repo: ops-warden
|
||||
status: active
|
||||
status: finished
|
||||
owner: codex
|
||||
topic_slug: custodian
|
||||
planning_priority: medium
|
||||
@@ -27,7 +27,7 @@ owner's procedure inside the catalog.
|
||||
|
||||
**Depends on:** WARDEN-WP-0010 (charter + catalog schema), WARDEN-WP-0011 (routing CLI).
|
||||
|
||||
**Status:** `active` — WP-0013 archived; T2/T3 in progress.
|
||||
**Status:** `finished` — playbooks shipped; draft entries await owner path promotion.
|
||||
|
||||
---
|
||||
|
||||
@@ -63,15 +63,18 @@ pointer to a non-existent path is worse than no entry.
|
||||
|
||||
```task
|
||||
id: WARDEN-WP-0012-T01
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "830bb512-0288-4dba-9dd4-ccfd28a4921f"
|
||||
```
|
||||
|
||||
- [ ] Coordinate with railiance-platform to canonicalize the OpenBao path first.
|
||||
- [ ] Then write `wiki/playbooks/issue-core-ingestion-api-key.md` (prerequisites,
|
||||
- [x] Coordinate with railiance-platform to canonicalize the OpenBao path first.
|
||||
(Documented expected path from `railiance-platform/docs/argocd-gitops.md`;
|
||||
live KV path not yet shipped — promotion blocked per anti-stale rule.)
|
||||
- [x] Then write `wiki/playbooks/issue-core-ingestion-api-key.md` (prerequisites,
|
||||
ESO pattern, rotation, privileged-read policy) and promote the catalog entry
|
||||
from `draft` to `active` with a `wiki_ref`.
|
||||
from `draft` to `active` with a `wiki_ref`. (Playbook + `wiki_ref` done;
|
||||
stays `draft` until path ships.)
|
||||
|
||||
### T2 — Inter-Hub and bootstrap lanes
|
||||
|
||||
@@ -103,26 +106,26 @@ state_hub_task_id: "9fb397f0-0abb-48f5-bb62-7e77edae93bb"
|
||||
|
||||
```task
|
||||
id: WARDEN-WP-0012-T04
|
||||
status: todo
|
||||
status: done
|
||||
priority: low
|
||||
state_hub_task_id: "edcf4ed7-f18d-4a92-a42d-8cc7ca0ab792"
|
||||
```
|
||||
|
||||
- [ ] Playbooks for OpenRouter, object-storage STS, DB dynamic creds.
|
||||
- [ ] Each ends with an owner-repo action; no warden secret code; pointers to canon.
|
||||
- [x] Playbooks for OpenRouter, object-storage STS, DB dynamic creds.
|
||||
- [x] Each ends with an owner-repo action; no warden secret code; pointers to canon.
|
||||
|
||||
### T5 — Drift review cadence
|
||||
|
||||
```task
|
||||
id: WARDEN-WP-0012-T05
|
||||
status: todo
|
||||
status: done
|
||||
priority: low
|
||||
state_hub_task_id: "db98d655-8551-487b-9413-41bf97fc06e1"
|
||||
```
|
||||
|
||||
- [ ] Document a review cadence against net-kingdom canon.
|
||||
- [ ] `warden route list --stale` keyed off the `reviewed:` date field.
|
||||
- [ ] Process note in `wiki/AccessRouting.md`.
|
||||
- [x] Document a review cadence against net-kingdom canon.
|
||||
- [x] `warden route list --stale` keyed off the `reviewed:` date field.
|
||||
- [x] Process note in `wiki/AccessRouting.md`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user