diff --git a/registry/routing/catalog.yaml b/registry/routing/catalog.yaml index 6adaa55..8bf07cf 100644 --- a/registry/routing/catalog.yaml +++ b/registry/routing/catalog.yaml @@ -390,6 +390,37 @@ entries: - "Confirm the new PAT works (fetch --field API_TOKEN into env, call /api/v1/user — never print it), then delete the old token in Forgejo." - "Verify capabilities-safe on the data path (`bao token capabilities`); notify consumers (package-prune, operator-bootstrap, npm-smoke, reuse-webhook) to re-fetch." + # --- tenant commercial secrets (mount tenants/; WARDEN-WP-0028) --- + + - id: binky-company-email-imap + title: Binky company email IMAP credentials + need_keywords: [binky, company, email, imap, mailbox, binky-hedgehog, mail, company-email] + owner_repo: railiance-platform + subsystem: OpenBao + email-connect + warden_executes: false + wiki_ref: wiki/playbooks/binky-company-email-imap.md#worker-checklist + canon_ref: railiance-platform/docs/workload-kv-access-lanes.md + reviewed: "2026-07-17" + status: draft + risk: high + # CCR-2026-0007: tenants/ mount + policy + OIDC role applied 2026-07-17. + # Values: founder Red provision pending. Promote to active after provision + + # capabilities-safe verify. Sibling non-secret host/port in email-connect config. + auth_method: "caller's own OpenBao token (OIDC netkingdom role binky-company-email-imap-workload-kv-read)" + path_template: "tenants/binky/company-email/imap" + fetch_command: "bao kv get -field=IMAP_PASSWORD tenants/binky/company-email/imap" + exec_capable: true + lane: secret + rotation: + method: rotate + owner: binky-control + automatable: false + steps: + - "At the mail provider, revoke the old app password / mailbox password and mint a new one (do not paste it into chat or Git)." + - "`bao kv put tenants/binky/company-email/imap IMAP_PASSWORD=@file` (and IMAP_USERNAME=@file if the login changed); shred the mode-0600 input file(s)." + - "Re-run email-connect read-only scan with warden access --exec / env inject; confirm metadata-only evidence under binky-control/mailmeta/." + - "Verify capabilities-safe on tenants/data/binky/company-email/imap; if EXPOSED taint was set, clear custom_metadata after rotation." + # --- draft: owner path not yet shipped; hidden from default lookup --- - id: object-storage-sts diff --git a/wiki/CredentialRouting.md b/wiki/CredentialRouting.md index 772e69d..47a86e6 100644 --- a/wiki/CredentialRouting.md +++ b/wiki/CredentialRouting.md @@ -102,12 +102,16 @@ run the owner's tool as the caller and preserve owner custody. Promotion criteria: `wiki/playbooks/catalog-lane-promotion.md`. High-risk / agent boundary: `wiki/playbooks/agent-read-boundary.md`. -EXPOSED taint: `wiki/playbooks/exposed-taint.md` · `warden taint `. +EXPOSED taint: `wiki/playbooks/exposed-taint.md` · `warden taint `. +**Tenant secrets:** mount `tenants/` — path `tenants///` +(see `wiki/playbooks/tenant-secret-onboarding.md`, WARDEN-WP-0028). Not under +`platform/workloads/`. **Draft** (hidden from default lookup until owner path ships — `warden route list --all`): | Catalog `id` | Routing focus | Playbook | | --- | --- | --- | +| `binky-company-email-imap` | Tenant IMAP creds on `tenants/binky/…` (provision pending) | `wiki/playbooks/binky-company-email-imap.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` | diff --git a/wiki/playbooks/agent-read-boundary.md b/wiki/playbooks/agent-read-boundary.md index 5795c53..9dc5660 100644 --- a/wiki/playbooks/agent-read-boundary.md +++ b/wiki/playbooks/agent-read-boundary.md @@ -25,6 +25,7 @@ operator identity. | `railiance-backup-offsite-lane` | Nextcloud upload + `AGE_PRIVATE_KEY` recovery escrow | | `forgejo-admin-api-token` | Forgejo site-admin PAT | | `openrouter-llm-connect` | Provider key (spend + prompt-adjacent) | +| `binky-company-email-imap` | Tenant mailbox IMAP password (`tenants/binky/…`) | Keep this table in sync with `risk: high` rows in `registry/routing/catalog.yaml` and path denials in `agent-high-risk-boundary.hcl`. diff --git a/wiki/playbooks/binky-company-email-imap.md b/wiki/playbooks/binky-company-email-imap.md new file mode 100644 index 0000000..ec83666 --- /dev/null +++ b/wiki/playbooks/binky-company-email-imap.md @@ -0,0 +1,103 @@ +# Binky company email IMAP + +Date: 2026-07-17 +Catalog: `binky-company-email-imap` (status `draft` until founder provisions) +Owner: `railiance-platform` (CCR-2026-0007) · consumer need: `binky-control` +Workplan: WARDEN-WP-0028 + +IMAP credentials for the company mailbox (founder address) so email-connect can +run **read-only** scans for control-plane event intake. + +--- + +## OpenBao pointers + +| Field | Value | +| --- | --- | +| Mount | `tenants` | +| Path | `tenants/binky/company-email/imap` | +| Fields | `IMAP_USERNAME`, `IMAP_PASSWORD` | +| Policy | `workload-kv-read-binky-company-email-imap` | +| OIDC role | `binky-company-email-imap-workload-kv-read` (`groups=net-kingdom-admins`) | +| Risk | `high` | + +Non-secret connection facts (IMAP host/port) live in email-connect / binky-control +config as host + env **names**, not passwords. + +--- + +## Worker checklist + +1. Login as caller: + + ```bash + bao login -method=oidc -path=netkingdom role=binky-company-email-imap-workload-kv-read + ``` + +2. Fetch via sanctioned transport (never paste into chat): + + ```bash + warden access binky-company-email-imap --all --no-policy --out /tmp/imap.user + # primary field is IMAP_USERNAME; for password use --field after template support + # or: + warden access binky-company-email-imap --all --no-policy --exec -- \ + env IMAP_USERNAME=… # prefer secrets-engine / dual-field exec when wired + ``` + + Until catalog is `active` and resolvable, use bao as caller with files: + + ```bash + bao kv get -field=IMAP_USERNAME tenants/binky/company-email/imap > /tmp/u + bao kv get -field=IMAP_PASSWORD tenants/binky/company-email/imap > /tmp/p + chmod 600 /tmp/u /tmp/p + ``` + +3. Run email-connect read-only scan (config uses env names only). + +4. Store **metadata-only** evidence under `binky-control/mailmeta/`. + +Agents (`WARDEN_AGENT_ID` set): raw value stream refused (exit 7). Use `--out` / +`--exec` / `--wrap` / `--fingerprint`. + +--- + +## Verify (capabilities-safe) + +```bash +LANE=$(bao token create -policy=workload-kv-read-binky-company-email-imap -ttl=2m -field=token) +bao token capabilities "$LANE" tenants/data/binky/company-email/imap # read +bao token revoke "$LANE" + +DEFAULT=$(bao token create -policy=default -ttl=2m -field=token) # deny of create is also pass +bao token capabilities "$DEFAULT" tenants/data/binky/company-email/imap # deny +bao token revoke "$DEFAULT" +``` + +Never use `bao kv get` for deny tests. + +--- + +## Founder provision (Red lane) + +```bash +# values only in mode-0600 files, never argv/chat +bao kv put tenants/binky/company-email/imap \ + IMAP_USERNAME=@/path/to/user.file \ + IMAP_PASSWORD=@/path/to/pass.file +shred -u /path/to/user.file /path/to/pass.file +``` + +Then re-verify capabilities, promote catalog to `active`, set CCR +`access_frontdoor.resolvable: true` / `readiness: ready`. + +## Rotation + +```bash +warden rotate-guide binky-company-email-imap +``` + +## See also + +- `binky-control/integrations/company-email-openbao.md` +- `wiki/playbooks/tenant-secret-onboarding.md` +- CCR-2026-0007 in railiance-platform diff --git a/wiki/playbooks/catalog-lane-promotion.md b/wiki/playbooks/catalog-lane-promotion.md index e1352fd..5bdc929 100644 --- a/wiki/playbooks/catalog-lane-promotion.md +++ b/wiki/playbooks/catalog-lane-promotion.md @@ -87,10 +87,11 @@ out on the OpenBao-delivered value, positive + negative verification audit-logge --- -## Draft lanes (2026-07-16) +## Draft lanes (2026-07-17) | Catalog `id` | Blocker | | --- | --- | +| `binky-company-email-imap` | Founder Red provision of IMAP values (CCR-2026-0007 applied; mount/policy/role live) | | `object-storage-sts` | NK-WP-0007 vending path not production-exercised | | `database-dynamic-credentials` | OpenBao database engine role paths TBD per workload | @@ -98,6 +99,9 @@ out on the OpenBao-delivered value, positive + negative verification audit-logge capabilities-safe re-verify (WP-0026 T07); primary field `NC_WEBDAV_TOKEN`; `risk: high` + EXPOSED taint on version 2 (operator may rotate optionally). +**Tenant path (WP-0028):** new client secrets use mount `tenants/`, not +`platform/workloads/`. See `wiki/playbooks/tenant-secret-onboarding.md`. + Re-run promotion when the owning repo closes the blocker; do not promote on playbook prose alone. diff --git a/wiki/playbooks/tenant-secret-onboarding.md b/wiki/playbooks/tenant-secret-onboarding.md new file mode 100644 index 0000000..cf23d41 --- /dev/null +++ b/wiki/playbooks/tenant-secret-onboarding.md @@ -0,0 +1,81 @@ +# Tenant secret onboarding + +Date: 2026-07-17 +Workplan: WARDEN-WP-0028 + +How to add a **client/tenant** commercial secret to NetKingdom OpenBao so +ops-warden can route it and consumers can use it without pasting values into +Git, State Hub, or chat. + +--- + +## Path convention + +```text +mount: tenants # dedicated KV v2 mount (not platform/) +path: tenants/// +``` + +| Segment | Meaning | Example | +| --- | --- | --- | +| `tenant` | Stable client slug | `binky` | +| `workload` | Capability / system | `company-email` | +| `bundle` | One purpose / one CCR | `imap` | + +**Do not** put new client secrets under `platform/workloads/…` (fleet/platform +services) or invent `secret/prod/…` as the production home. + +First worked lane: `tenants/binky/company-email/imap` (CCR-2026-0007). + +--- + +## Ownership + +| Step | Owner | +| --- | --- | +| Business need, non-secret host facts, consumer config | Tenant control repo (e.g. `binky-control`) | +| CCR, policy HCL, OIDC role, mount hygiene | `railiance-platform` | +| Catalog front door + playbook + rotation guide | `ops-warden` | +| Value provision (Red) | Human founder/operator | +| Optional exec wrapper | `secrets-engine` (same path only) | + +--- + +## Checklist + +1. **Slug + fields** — pick `tenant`/`workload`/`bundle` and field names (no values). +2. **CCR** — `railiance-platform/credential-change-requests/CCR-YYYY-NNNN-….yaml` + - `openbao.mount: tenants` + - `openbao.kv_path: tenants///` + - `policy_name` starts with `workload-kv-read-` + - OIDC role ends with `-workload-kv-read` + - `risk` high for mailbox/admin/recovery-class secrets +3. **Policy file** — exact `tenants/data/…` + `tenants/metadata/…` read only. +4. **Applier dry-run** — `scripts/credential-change.py applier-dry-run ` +5. **Approve + apply metadata** — policy + OIDC role; **no** secret write in apply. +6. **ops-warden catalog** — draft entry with `risk: high`, rotation block, concrete + `fetch_command` for primary field; playbook under `wiki/playbooks/`. +7. **Founder provision** — `bao kv put tenants/… FIELD=@file` (mode 0600 file) or + secrets-engine provision; never chat/Git. +8. **Verify capabilities-safe** — `bao token capabilities` allow/deny; never + `kv get` for deny tests (WP-0026). +9. **Promote** catalog `draft` → `active` when resolvable; update CCR readiness. +10. **Agent boundary** — add exact data `deny` + metadata `read` to + `agent-high-risk-boundary` for high-risk lanes. + +--- + +## Worker fetch (after active) + +```bash +bao login -method=oidc -path=netkingdom role=-workload-kv-read +warden access --no-policy --out /tmp/secret.file # mode 0600 +# agents: never raw --fetch stream; WARDEN_AGENT_ID + risk=high → exit 7 +``` + +## See also + +- `wiki/playbooks/binky-company-email-imap.md` +- `wiki/playbooks/catalog-lane-promotion.md` +- `wiki/playbooks/agent-read-boundary.md` +- `railiance-platform/docs/credential-change-approval.md` diff --git a/workplans/WARDEN-WP-0028-tenant-secret-custody.md b/workplans/WARDEN-WP-0028-tenant-secret-custody.md new file mode 100644 index 0000000..2085150 --- /dev/null +++ b/workplans/WARDEN-WP-0028-tenant-secret-custody.md @@ -0,0 +1,408 @@ +--- +id: WARDEN-WP-0028 +type: workplan +title: "Tenant secret custody — NetKingdom pattern for client/tenant secrets" +domain: infotech +repo: ops-warden +status: active +owner: grok +topic_slug: custodian +planning_priority: high +planning_order: 28 +created: "2026-07-16" +updated: "2026-07-17" +--- + +# WARDEN-WP-0028 — Tenant secret custody (NetKingdom pattern) + +## Origin + +Founder establishing **binky-control** as the control-plane repo for a Coulomb +client/tenant (Binky-Hedgehog GmbH). Immediate need: store **tenant-specific** +secrets in OpenBao (e.g. company mailbox usernames/passwords for IMAP scan) and +consume them safely via email-connect / agents **without** putting values in +git, State Hub, chat, or workplans. + +BINKY-WP-0003-T01 prepared a draft plan +(`binky-control/integrations/company-email-openbao.md`) that targeted +`secret/prod/binky/...` via secrets-engine. That draft is directionally right on +*workflow*, but it does not match a production-ready custody layout on +`bao.coulomb.social`. This workplan resolves the NetKingdom layout and makes +**tenant** secrets first-class — **separate from** platform workload lanes +(`platform/workloads/...`). + +## Goal + +Define and land a **repeatable, multi-tenant-safe** pattern for: + +1. Where tenant secrets live in OpenBao (path + mount convention) +2. Who owns policy, CCR, catalog, and front door +3. How agents and workloads obtain values (sanctioned transports only) +4. First concrete lane: **binky company email IMAP** (unblocks real-world event + intake for binky-control) + +## Architectural facts (as of 2026-07-16) + +### Planes (ADR-0006) + +| Plane | Role | Example | +| --- | --- | --- | +| Bootstrap | First trust / recovery | unseal, platform-root | +| Platform control | Shared identity, secrets, authz | OpenBao, key-cape, flex-auth, railiance-platform | +| Tenant | Scoped client/workload authority | coulomb products, **binky**, future clients | + +**Binky is a tenant**, not platform-root. Tenant secrets must not require +platform-admin for day-to-day read, and tenant operators must not gain +platform-root grants. + +### OpenBao mounts (live + planned) + +| Mount | Type | Role | +| --- | --- | --- | +| **`platform/`** | KV v2 | **Platform control-plane** secrets: `platform/workloads/...` (fleet CCRs, ESO) and `platform/operators/...` — *not* client/tenant commercial secrets | +| **`secret/`** | KV v2 | secrets-engine stage / pilot paths (`secret/coulomb/...`) — not the tenant home | +| **`tenants/`** | KV v2 (**new**) | **Client/tenant commercial secrets** — explicit multi-tenant namespace | + +Live under `platform/workloads/` today: `activity-core/`, `coulomb/`, `forgejo/`, +`issue-core/`, `railiance/`, `reuse/`. Those stay where they are; we do **not** +migrate them into `tenants/` in this workplan. + +### Dual front-door systems (both real; don't invent a third) + +| Layer | Owns | Entry | +| --- | --- | --- | +| **OpenBao** | Custody, ACL, audit | `bao` CLI / API | +| **railiance-platform CCR** | Approved metadata apply (policy + OIDC role + evidence) | `credential-change-requests/CCR-*.yaml` | +| **ops-warden catalog** | Routing + `warden access` proxy (no custody) | `registry/routing/catalog.yaml` | +| **secrets-engine catalog** | Workflow: decision → plan → provision → exec delivery | `secrets-engine/catalog/*.yaml` | + +**Binding rule (ops-warden):** never vend secret values; only route/proxy as the +caller. **Binding rule (WP-0026):** capabilities-safe verify; agents on +`risk: high` use `--out` / `--exec` / `--wrap` only. + +### Why not bury tenants under `platform/workloads/` + +Earlier draft of this workplan used `platform/workloads//...`. Founder +prefer a **shallower, explicit** namespace. That is sound: + +- **No conflict with Vault/OpenBao best practice.** Separate mounts (or + top-level prefixes) for different security *domains* are common and good. + `platform/workloads` is an *internal fleet convention* for platform services, + not an industry mandate for every secret. +- **Clearer mental model:** platform plane vs tenant plane (ADR-0006) maps to + mount/prefix names operators can see at a glance. +- **Isolation:** ACLs, list boundaries, and future audit queries stay simpler + when tenant material is not mixed under the same path tree as forgejo/ESO + lanes. + +### Decision (2026-07-17) — use dedicated mount `tenants/` + +```text +mount: tenants # KV v2, enable once (platform-admin) +logical path: // +CLI: tenants/// +API data path: tenants/data/// +``` + +**Rejected alternatives (kept for archaeology only):** + +| Option | Why not | +| --- | --- | +| `secret/prod/binky/...` | `secret/prod/` does not exist; stage model not ready as sole production path | +| `platform/workloads/binky/...` | Too deep; conflates tenant clients with platform workloads | +| `platform/tenants/binky/...` | Acceptable fallback if enabling a new mount is blocked; prefer full `tenants/` mount | + +### Tooling debt to clear (not a reason to keep the deep path) + +`railiance-platform/scripts/credential-change.py` currently hard-fails unless: + +- `openbao.mount == "platform"` +- `openbao.kv_path` starts with `platform/workloads/` + +That guard must be **extended** to allow `mount: tenants` and +`kv_path` under `tenants/` (T03). It is an implementation constraint, not a +security reason to force the deep path. + +## Recommended path convention (tenant secrets) + +```text +tenants/// +``` + +| Segment | Rules | Binky email example | +| --- | --- | --- | +| `tenant_slug` | Stable kebab slug; company/client id | `binky` | +| `workload` | Capability or system using the secret | `company-email` | +| `bundle` | Single purpose unit (one CCR / one policy) | `imap` | + +Full CLI path: + +```text +tenants/binky/company-email/imap +``` + +Suggested fields (names only — never values in git): + +| Field | Purpose | +| --- | --- | +| `IMAP_USERNAME` | mailbox login (often the address) | +| `IMAP_PASSWORD` | app password / mailbox password | +| `IMAP_HOST` | optional if not fixed in consumer config | +| `IMAP_PORT` | optional (default 993 in config) | + +Non-secret connection facts (provider host if stable) may live in +`binky-control` config / email-connect yaml as **env names + host**, not +passwords. + +### Future multi-mailbox / multi-tenant + +```text +tenants//mailbox/ +# e.g. tenants/binky/mailbox/founder-primary +# tenants/acme/mailbox/billing +``` + +One CCR + one least-privilege policy **per bundle** (or per mailbox). Do not +grant `tenants/binky/*` list/read to agents — exact-path policies only. + +### Risk class + +Mailbox credentials and similar client secrets are **`risk: high`** (WP-0026 +T04): agent identities get metadata/capabilities only; raw stream refused when +`WARDEN_AGENT_ID` is set. + +## Ownership split + +| Concern | Owner repo | Artifact | +| --- | --- | --- | +| Tenant business need, IMAP host facts, scan config, queues | **binky-control** | `integrations/*`, mailmeta (metadata only), activity defs | +| OpenBao path, policy HCL, OIDC role, CCR lifecycle | **railiance-platform** | `CCR-YYYY-NNNN-…yaml`, `openbao/policies/…` | +| Routing front door + rotation guidance | **ops-warden** | catalog entry, playbook, `warden access` / `rotate-guide` | +| Exec-time delivery workflow (optional same path) | **secrets-engine** | catalog entry pointing at `tenants/...` (same path; no second copy) | +| Identity groups for tenant operators | **net-kingdom / key-cape** | group e.g. `tenant-binky-operators` (near-term may use `net-kingdom-admins` for founder-only) | +| Mailbox scan consumer | **email-connect** | config with `username_env` / `password_env` only | + +## Auth model (near-term vs target) + +**Near-term (founder dogfood):** OIDC `netkingdom` role bound to +`groups=net-kingdom-admins` (same pattern as backup/forgejo lanes). Acceptable +while binky is founder-operated only. + +**Target (real multi-tenant):** + +- IAM group `tenant-binky-operators` (and later per-tenant groups) +- OpenBao OIDC role `binky-company-email-imap-workload-kv-read` bound only to + that group +- flex-auth check `secret.read:binky-company-email` if pre-approval required +- Agents **never** hold the workload-kv-read policy; use operator wrap / exec + injection / AppRole for email-connect scanner only + +## First worked lane (deliverable sketch) + +| Item | Value | +| --- | --- | +| Catalog id | `binky-company-email-imap` | +| CCR | `CCR-2026-0007` (next free) — title: Binky company email IMAP | +| Mount | `tenants` | +| KV path | `tenants/binky/company-email/imap` | +| Policy | `workload-kv-read-binky-company-email-imap` | +| OIDC role | `binky-company-email-imap-workload-kv-read` | +| Consumer | email-connect `scan-mailbox` via env inject | +| Front door | `warden access binky-company-email-imap --out FILE` or `--exec -- …` | +| Provision | **Red lane, founder once** — value via `bao kv put … @file` or secrets-engine provision; never chat | + +## Out of scope + +- Mass rotation / lockdown machinery (WARDEN-WP-0027 Strand B) +- Sending mail / SMTP from company address (separate lane if needed) +- Migrating all `secret/` mount lanes onto `platform/` (separate secrets-engine + / railiance work) +- Full key-cape tenant group productization (may be a net-kingdom follow-on) + +## Tasks + +### T01 — Canon note: tenant secret path + ownership + +```task +id: WARDEN-WP-0028-T01 +status: done +priority: high +``` + +Done 2026-07-17: `wiki/playbooks/tenant-secret-onboarding.md` + CredentialRouting +tenant paragraph; catalog-lane-promotion draft table updated. + +Land a short ops-warden wiki page (and one paragraph in +`wiki/CredentialRouting.md`) that freezes: + +- mount **`tenants`** + path `tenants///` +- ownership table (binky-control / railiance-platform / ops-warden / secrets-engine) +- link to WP-0026 hygiene (capabilities verify, high-risk, taint) +- **do not** put new client/tenant commercial secrets under + `platform/workloads/` or invent `secret/prod/...` as the production home + +**Done when:** wiki + CredentialRouting pointer exist; binky integration doc +updated to match (or superseded with link). + +### T02 — Align binky-control integration plan to production path + +```task +id: WARDEN-WP-0028-T02 +status: done +priority: high +``` + +Done 2026-07-17: `binky-control/integrations/company-email-openbao.md` rewritten +to `tenants/binky/company-email/imap` + checklist (host confirm + Red provision +still open). + +**Cross-repo: binky-control.** Rewrite +`integrations/company-email-openbao.md` to use +`tenants/binky/company-email/imap`, CCR + ops-warden front door as primary, +secrets-engine as optional exec wrapper on the same path. Confirm non-secret +IMAP host/port with founder (Blue). Keep Red-lane value provision as the last +step. + +**Done when:** binky doc matches this workplan; checklist items re-opened for +implementation (T01 in BINKY was "prepared only"). + +### T03 — Enable `tenants` mount + extend CCR tooling + policy/role + +```task +id: WARDEN-WP-0028-T03 +status: done +priority: high +``` + +Done 2026-07-17: `tenants/` KV v2 mount live; CCR applier allowlist extended; +CCR-2026-0007 + policy + OIDC role applied; lane-policy `read` / default +`deny` / agent boundary on tenants path; `docs/workload-kv-access-lanes.md` +tenant section. + +**Cross-repo: railiance-platform (+ OpenBao admin once).** + +1. **Enable KV v2 mount** `tenants/` on `bao.coulomb.social` (platform-admin / + attended; record non-secret evidence). Versioning + max versions policy + aligned with `platform` if practical. +2. **Extend** `scripts/credential-change.py` allowlist: `mount: tenants` and + `kv_path` under `tenants/` (keep existing `platform/workloads/` rules + intact for fleet lanes). Update dry-run docs and tests. +3. Author `CCR-2026-0007` (or next id) for the binky IMAP path; policy HCL on + exact data/metadata paths under `tenants/…`; OIDC role (founder group + near-term). Dry-run; founder approves; apply metadata only. +4. Document fields without values; add a **tenant lanes** section to + `docs/workload-kv-access-lanes.md` (or sibling doc). + +**Done when:** mount exists; CCR tool accepts tenant paths; CCR approved + +policy/role applied; capabilities-safe positive/negative evidence (no value +reads for verify). + +### T04 — ops-warden catalog + playbook + rotation + +```task +id: WARDEN-WP-0028-T04 +status: done +priority: high +``` + +Done 2026-07-17: catalog draft `binky-company-email-imap` (`risk: high`, +rotation, path `tenants/binky/...`); playbooks +`binky-company-email-imap.md` + onboarding. Promote to active after T05. + +Add `binky-company-email-imap` to `registry/routing/catalog.yaml`: +`status: draft` until verify, then promote; `risk: high`; `exec_capable: true`; +concrete `fetch_command` for primary field; `rotation:` block (re-establish or +rotate mailbox app-password). Playbook under `wiki/playbooks/`. Scorecard +rotation coverage satisfied on promote. + +**Done when:** `warden route show binky-company-email-imap --json` shows +expected pointers; after T03+T05, `resolvable: true`. + +### T05 — Founder provision (Red) + first scan evidence + +```task +id: WARDEN-WP-0028-T05 +status: wait +priority: high +``` + +**Human-only value path.** Founder provisions IMAP username/password into +OpenBao via approved tool (`bao kv put … @file` or secrets-engine provision). +Agents never see the value. Run email-connect read-only scan with +`warden access … --exec` or secrets-engine exec; store **metadata-only** +evidence under `binky-control/mailmeta/`. + +**Done when:** one successful read-only scan evidence note exists; lane +promoted active/resolvable; CCR verification evidence complete. + +### T06 — Generalize "tenant secret onboarding" playbook + +```task +id: WARDEN-WP-0028-T06 +status: done +priority: medium +``` + +Done 2026-07-17 with T01: `wiki/playbooks/tenant-secret-onboarding.md` + +CredentialRouting link. + +From the binky lane, write a reusable playbook: +`wiki/playbooks/tenant-secret-onboarding.md` — steps for any new tenant: +slug, CCR template fields, policy naming, catalog entry, agent boundary, +rotation block, promotion checklist. Intended for the next Coulomb client +without redesign. + +**Done when:** playbook exists and is linked from CredentialRouting + first +session notes for tenant repos. + +### T07 — secrets-engine alignment decision (record only) + +```task +id: WARDEN-WP-0028-T07 +status: done +priority: low +``` + +Done 2026-07-17: **Decision** — tenant production lanes use `mount: tenants` +and the CCR path only; secrets-engine may wrap exec delivery against that same +path later but must not store a second copy under `secret/`. Until stage roles +gain an approved `tenants/` grant, production path is CCR + ops-warden + +caller `bao`. + +Record a short decision: secrets-engine catalog entries for **tenant** +production lanes **must** use `mount: tenants` and the same path as the CCR +(no second copy under `secret/`). Stage roles may need a later extension to +touch `tenants/` under an approved plan — until then, ops-warden + CCR + +`bao` as caller are the production path. + +**Done when:** decision note in this workplan or `POST /decisions/` + wiki +pointer; no dual-value storage for the IMAP password. + +## Suggested implementation order + +1. T01 canon (unblocks everyone) +2. T02 binky doc alignment +3. T03 mount + CCR tooling + CCR apply (metadata) +4. T04 catalog draft +5. T05 founder provision + verify + promote +6. T06 generalize playbook +7. T07 secrets-engine alignment (can parallel after T01) + +## Acceptance + +- [ ] Documented path convention `tenants//…` used for ≥1 live lane (`binky`) +- [ ] CCR applier accepts `tenants/` without weakening `platform/workloads/` rules +- [ ] No secret values in any git/State Hub artifact +- [ ] Lane is capabilities-verified and agent high-risk safe +- [ ] email-connect can scan with env injection without printing password +- [ ] Next tenant can copy the onboarding playbook without redesign + +## References + +- ADR-0006 recursive multi-tenant identity (`net-kingdom/docs/adr/…`) +- `net-kingdom/docs/secrets-engine-security-infrastructure-boundary.md` +- `railiance-platform/docs/workload-kv-access-lanes.md` +- `railiance-platform/docs/credential-change-approval.md` +- `binky-control/integrations/company-email-openbao.md` (draft to align) +- WARDEN-WP-0026 disclosure hygiene (capabilities, high-risk, taint, rotation) +- WARDEN-WP-0027 Strand B (out of scope here)