ops-mason/plans/fluid-telegram-operator-credential-lane.md
tegwick 21bce610b5 MASON-WP-0005-T03 done: founder approved the fluid-telegram operator lane
Plan status approved (Bernd Worsch, 2026-10-09) with build_spec and
approved_spec_sha256 41a0ca27; the executor's approval gate accepts it.
T04 now waits on the founder-run applier (RPF-WP-0035-T03, decision 62aa95e4).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 327203@bnt-lap001
Assistant-Session: 55028ae3-4914-4bd1-8f5c-815f3527aef0
2026-10-09 00:45:03 +02:00

292 lines
14 KiB
Markdown

---
id: fluid-telegram-operator-credential-lane
demand_source: fluid-telegram/workplans/FT-WP-0002 and State Hub message 287cf897-7f47-4bcc-87d6-085f43d5f03e
consumer_repo: fluid-telegram
credential_type: openbao-oidc-kv-read-write
status: approved
approved_by: "Bernd Worsch"
approved_at: "2026-10-09"
approved_spec_sha256: "41a0ca2746aa93b6ee9d1ca631517d680ade95c56e7a38b75a55209ef2316b73"
created: "2026-09-04"
updated: "2026-10-09"
build_spec:
engine: openbao-oidc-kv-read-write
policy_name: workload-kv-fluid-telegram-hall-of-helix-operator
role_name: fluid-telegram-hall-of-helix-workload-kv
campaign_path: platform/workloads/coulomb/fluid-telegram/hall-of-helix
bound_group: fluid-telegram-operators
allowed_redirect_uris:
- http://localhost:8250/oidc/callback
- http://127.0.0.1:8250/oidc/callback
- http://127.0.0.1:18200/ui/vault/auth/netkingdom/oidc/callback
identity_confirmation_ref: key-cape message b2ed890f (key-cape e0b5fae); LLDAP group
fluid-telegram-operators exists, member tegwick (founder 2026-10-09)
platform_matrix_review_ref: railiance-platform docs/credential-lane-designs/fluid-telegram-operator-kv.md
(matrix identical, checked 2026-10-09)
auth_mount: netkingdom
token_ttl: 15m
token_max_ttl: 30m
bao_bin: bao
audit_log_path: /home/worsch/ops-mason/audit/build-log.jsonl
---
# Construction plan: fluid-telegram operator credential lane
## 1. Demand
`fluid-telegram` has an attended provisioner that creates a Telegram bot and
channels from a declared campaign specification. The provisioner needs an
operator OIDC token that reads four campaign-specific KV entries, writes the
MTProto session and bot token, and creates the redaction salt once. The
MTProto session is a full-account credential, so this is a high-risk operator
lane, never an autonomous agent or adapter lane. ops-mason constructs only the
OpenBao structure and never handles Telegram values.
The first consumer is campaign `hall-of-helix`. A future adapter has a
different, narrower need and is intentionally excluded from this plan.
## 2. Existing-structure survey
### Disk survey completed 2026-09-04
- `railiance-platform/docs/workload-kv-access-lanes.md` documents attended
OIDC through `auth/netkingdom`, exact group-bound roles, three callback URIs,
and 15-minute tokens.
- The nearest concrete tenant-shaped path is
`platform/workloads/coulomb/whynot-design/npm-publish`. Its CCR history says
the owner corrected the first segment to the organisation/tenant, not the
State Hub domain.
- Existing policy files contain no `fluid-telegram` lane.
- `ops-warden/registry/routing/catalog.yaml` contains only the generic
`openbao-api-key` template for this need, and its login string omits
`-path=netkingdom`. No concrete Telegram lane exists.
- `ops-mason/src/ops_mason/executor.py` supports AppRole and Kubernetes KV
lanes. It has no OIDC role writer and its generic policy helper emits the
same capabilities for one data and metadata path, which cannot express this
four-path matrix.
- `railiance-platform/schemas/credential-change-request.schema.yaml` permits
only `request_type: workload-kv-read`; its validator also requires the
`workload-kv-read-` policy prefix and `-workload-kv-read` OIDC role suffix.
### Live survey (2026-10-08)
Generated by `scripts/plan-survey.py` at 2026-10-08T21:53:55Z against `BAO_ADDR=http://127.0.0.1:18200`, token display_name `token-claude-MASON-WP-0005-T01` (policies: default, ops-mason-survey). Metadata only: no `kv get` and no `*/data/*` read was issued; policy bodies were read with `bao policy read` for name-matched candidates only.
Demand: KV path `platform/workloads/coulomb/fluid-telegram/hall-of-helix`, proposed policy `workload-kv-fluid-telegram-hall-of-helix-operator`, AppRole `-`, Kubernetes role `-`, consumer `fluid-telegram`. Match terms: `fluid-telegram`, `hall-of-helix`, `telegram`, `coulomb`.
#### `bao secrets list` (10 mounts, 1 relevant)
```text
platform/ kv version=2
```
#### `bao auth list` (5 auth mounts, 2 relevant)
```text
approle/ approle
kubernetes/ kubernetes
```
#### `bao policy list` (81 policies, 0 relevant)
```text
(no relevant entries)
```
#### `bao list auth/approle/role` (approle) (14 roles, 0 relevant)
```text
(no relevant entries)
```
#### `bao list auth/kubernetes/role` (kubernetes) (27 roles, 0 relevant)
```text
(no relevant entries)
```
#### `bao policy read` of name-matched candidates (target `platform/data/workloads/coulomb/fluid-telegram/hall-of-helix`)
```text
(no relevant entries)
```
#### `bao kv metadata get` / `bao kv list` (metadata only)
```text
platform/workloads/coulomb/fluid-telegram/hall-of-helix: no metadata (path unused)
platform/workloads/coulomb/fluid-telegram/: request failed (output suppressed)
```
#### ops-warden routing catalog `/home/worsch/ops-warden/registry/routing/catalog.yaml` (42 entries, 4 relevant)
```text
whynot-design-npm-publish owner=railiance-platform status=active matches: coulomb
path_template: platform/workloads/coulomb/whynot-design/npm-publish
policy_ref: flex-auth check secret.read:whynot-design
auth_method: bao login -method=oidc -path=netkingdom role=whynot-design-workload-kv-read
reuse-surface-hub-write-token owner=railiance-platform status=active matches: coulomb
path_template: platform/workloads/reuse/reuse-surface/runtime-secrets
policy_ref: flex-auth check secret.read:reuse
auth_method: caller's own OpenBao token (operator OIDC via key-cape, or a token carrying workload-kv-read-reuse-surface-runtime)
inbound-library-imap-approle owner=ops-mason status=active matches: coulomb
path_template: platform/workloads/inbound-library/imap
auth_method: AppRole login role=inbound-library (role_id+secret_id in ~/.local/inbound-library/approle, config/library.json credential.approle_dir)
coulomb-social-runtime-env owner=railiance-platform status=active matches: coulomb
path_template: k8s:coulomb-social/coulomb-social-env
auth_method: kubectl as platform operator (or bao OIDC when OpenBao lane is provisioned)
```
#### Does an existing lane already satisfy this demand?
**Draft answer: no.** Review before committing; this is generated, not a decision.
- no surveyed policy grants `platform/data/workloads/coulomb/fluid-telegram/hall-of-helix`, the proposed names are free, the KV path is unused and no catalog entry routes it
- policy grants were checked only for name-matched candidates; a policy with an unrelated name that covers `platform/data/workloads/coulomb/fluid-telegram/hall-of-helix` is not detected
#### Additions by hand (same grant)
- `bao list auth/netkingdom/role` (12 roles): no `fluid-telegram*` role; the
proposed `fluid-telegram-hall-of-helix-workload-kv` is free. The generator
did not flag the `netkingdom` OIDC mount as relevant; it is the one this lane
reuses.
- All 81 policy bodies scanned (closing the generator's name-match gap): only
`platform-admin` (`platform/*`) covers the target. No workload policy does.
- Live reference role `keycape-secrets-engine-openbao-operator-workload-kv-read`
has exactly the binding this plan renders: `bound_claims_type=string`,
`bound_claims={groups:[net-kingdom-admins], /assurance/level: aal2}`,
`groups_claim=groups`, `token_no_default_policy=true`. The executor's role
document matches that shape.
- Naming: live OIDC roles end `-workload-kv-read` and their policies start
`workload-kv-read-`. This lane writes, so it drops `-read`
(`workload-kv-fluid-telegram-hall-of-helix-operator`); that is why the CCR
schema extension (T02) is needed.
**Existing-lane answer: none.** Confirmed live: path unused, names free, no
covering workload policy, no catalog route.
### OIDC role shape
- auth mount: `netkingdom`
- role: `fluid-telegram-hall-of-helix-workload-kv`
- token policies: exactly
`workload-kv-fluid-telegram-hall-of-helix-operator`; set
`token_no_default_policy=true`
- role type: `oidc`
- token TTL: `15m`, maximum TTL `30m`
- user claim: `sub`; groups claim: `groups`
- scopes: `openid,profile,email,groups`
- bound claims (`bound_claims_type=string`): `groups=[fluid-telegram-operators]`
and `/assurance/level=aal2` — confirmed by key-cape 2026-10-08 (message
`b2ed890f-a05a-4b96-95eb-2cd9d8b32caa`, key-cape `e0b5fae`). Human ID tokens
always carry `groups` (LLDAP group names); `aal2` is set only after a
verified privacyIDEA factor, the same pin as CCR-2026-0034.
- precondition met: LLDAP group `fluid-telegram-operators` exists with the
single member `tegwick` (founder report 2026-10-09, created in the LLDAP admin
UI; names only).
- callbacks (subset of the `openbao-admin` client registration):
`http://localhost:8250/oidc/callback`, `http://127.0.0.1:8250/oidc/callback`,
`http://127.0.0.1:18200/ui/vault/auth/netkingdom/oidc/callback`. Never the
retired `https://bao.coulomb.social/...` (2026-09-15).
The role is campaign-specific because its policy is campaign-specific. A
second campaign receives a second policy and role after its own review; it is
not added below this prefix.
### Value flow
- An attended operator enters `operator-app` with the existing sanctioned
paste-once/value-entry mechanism.
- The provisioner creates or updates `operator-session` and `bot-token`.
- The provisioner creates `redaction-salt` once.
- ops-mason records paths, policy/auth metadata, capabilities, versions,
timestamps, and audit references only. It never requests or reads a value.
## 4. Review notes (phase 2)
Complete as of 2026-10-09 (T01 and T02 done); plan moved to `reviewed`.
- **Reuse:** reuse the existing `netkingdom` auth mount, callback set, group
claim pattern, 15-minute TTL, and KV-v2 mount. Create only the policy and
role that do not exist.
- **Least privilege:** exact entry paths are safer than one campaign-prefix
wildcard and are necessary for the salt's create-only rule. The role has no
access to KV metadata because the provisioner uses data GETs to detect
absence; metadata evidence comes from the separately scoped builder/operator
verification path.
- **Identity binding:** group and assurance claims confirmed by key-cape; the
group exists with one member. Membership is the whole access decision, so
adding a member later is a change to this lane's reach.
The role must not be readable by every OIDC user.
- **Write-once semantics:** KV-v2 create/update distinction is documented, but
activation also requires an owner-run non-secret probe proving the installed
engine behaves as expected. The resulting evidence contains no probe value.
- **Agent boundary:** explicit denies must cover all four data paths and their
metadata paths. The operator-session path is the decisive negative check.
- **Adapter separation:** `MASON-IN-0003` tracks a later runtime lane that
reads only `bot-token` and `redaction-salt`; no role or policy in this plan is
reused by the adapter.
- **Reversal:** disable/delete the OIDC role first, then retire the policy. KV
entries remain in operator custody for an explicit retention/recovery
decision; teardown does not silently delete values.
- **Platform matrix:** railiance-platform's
`docs/credential-lane-designs/fluid-telegram-operator-kv.md` carries the same
four paths and capabilities as `_OPERATOR_MATRIX` in
`src/ops_mason/executor.py` (checked 2026-10-09).
- **Implementation gates:** both ops-mason and railiance-platform must extend
their current read-lane tooling before construction. Direct `bao policy
write`/`bao write auth/netkingdom/role/...` commands outside those approved
surfaces are not an alternative.
### Verification required before routing activation
1. Positive capabilities on each exact path match the matrix above.
2. Parent, sibling workload, and sibling campaign paths report `deny`.
3. A non-member OIDC identity cannot obtain the role's policy.
4. Agent-bound identities report `deny` on all four data and metadata paths.
5. An owner-run disposable KV-v2 probe proves create succeeds and a second
write with the same policy is denied; only timestamps, versions, path, and
pass/fail are returned.
6. The value-entry/provisioner flow confirms all required entries exist using
metadata/version evidence only.
7. ops-warden keeps `fluid-telegram-operator` non-resolvable until all checks
pass and the exact built pointers are returned.
## 5. Executive summary (phase 3)
**What this creates.** One OpenBao policy and one sign-in role, nothing else:
- policy `workload-kv-fluid-telegram-hall-of-helix-operator`: on
`platform/data/workloads/coulomb/fluid-telegram/hall-of-helix/`
`operator-app` read; `operator-session` and `bot-token` create, read, update;
`redaction-salt` create, read (write-once); plus revoke-self. No metadata,
list, parent or sibling access.
- OIDC role `fluid-telegram-hall-of-helix-workload-kv` on the existing
`auth/netkingdom` mount: only members of `fluid-telegram-operators`
(today: `tegwick`) with a verified second factor (`aal2`); 15-minute token,
30 minutes at most; no default policy; the two loopback CLI callbacks and the
operator-tunnel UI callback.
**Why it is safe.** The MTProto session is a full-account Telegram credential,
so the lane is attended only: a person with MFA, never an agent or the future
adapter. The path is unused and no workload policy reaches it today (live
survey 2026-10-08). ops-mason writes only the policy and role; no value passes
through it, and the salt cannot be overwritten.
**What approval also accepts.** The tenant segment `coulomb` (replacing the
consumer's `infotech` assumption), and the exact matrix above, which
railiance-platform's lane design already matches.
**What happens after approval.** The structure is applied through the approved
surfaces only (railiance-platform's per-path applier, RPF-WP-0035-T03, under the
attended provisioning decision `62aa95e4`; ops-mason's
`build_oidc_kv_lane`), never by direct `bao` writes. ops-warden's
`fluid-telegram-operator` entry stays non-resolvable until the seven checks
above pass, including the owner-run write-once probe.
**Undo.** Delete the role, then the policy. Stored values stay in operator
custody until someone decides their retention explicitly.
To approve, the founder says "APPROVE fluid-telegram-operator-credential-lane";
ops-mason then sets `status: approved`, `approved_by` and `approved_at`.