Close the inbound-library IMAP AppRole lane: built, verified, routed.

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

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1170398@bnt-lap001
Assistant-Session: 622cd705-cb31-4322-ba08-dd42e44fa583
This commit is contained in:
tegwick 2026-10-05 16:14:02 +02:00
parent e4c68e2d0b
commit 4e3fc02f86
2 changed files with 213 additions and 1 deletions

View file

@ -0,0 +1,183 @@
---
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: draft
approved_by: null
approved_at: null
created: "2026-09-04"
updated: "2026-09-04"
---
# 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 pending
No valid scoped OpenBao session was available during intake, so the required
live outputs from `bao secrets list`, `bao auth list`, `bao policy list`, and
the relevant `auth/netkingdom/role/*` reads are not yet attached. Phase 2 must
repeat the survey with `./scripts/bao-session.sh grant MASON-WP-0005-T01`.
**Existing-lane answer:** none is known from source state. This remains a
draft finding until the live survey confirms it.
## 3. Proposed changes
The recommended CLI prefix is
`platform/workloads/coulomb/fluid-telegram/hall-of-helix`. `coulomb` is a
recommendation from the existing owner precedent, not an authorization; T01
must replace this paragraph with railiance-platform's explicit decision before
review.
The recommended objects are:
| # | Action | Object | Rationale |
|---|---|---|---|
| 1 | create | Policy `workload-kv-fluid-telegram-hall-of-helix-operator` | A new exact-path matrix is required; no read-only lane can satisfy the writes. |
| 2 | create | OIDC role `fluid-telegram-hall-of-helix-workload-kv` at `auth/netkingdom` | The caller is an attended operator; AppRole and Kubernetes auth are the wrong identity shapes. |
| 3 | modify | ops-mason executor with an approved OIDC KV matrix spec | The current executor cannot safely express OIDC or per-path capabilities. |
| 4 | modify | railiance-platform CCR schema/validator with an approved read/write request type and naming rules | The platform owner explicitly rejects this shape today; bypassing that gate is forbidden. |
| 5 | modify | railiance-platform agent high-risk boundary with exact data and metadata denies for all four paths | Agent-bound identities must not inherit this lane through another policy. |
| 6 | propose | ops-warden entry `fluid-telegram-operator`, pointer-only, `status: draft`, `resolvable: false` | A concrete selector is useful before activation; no placeholder prefix becomes routable. |
### Exact policy matrix
All entries are individual KV-v2 API paths. There is no `*` grant, parent
`list`, metadata mutation, delete, destroy, undelete, or sudo capability.
| CLI entry | KV-v2 data-path capabilities | Risk and reason |
|---|---|---|
| `.../operator-app` | `read` | High confidentiality: Telegram application id/hash; entered only through sanctioned attended value entry. |
| `.../operator-session` | `create,read,update` | High: full-account MTProto credential. Provisioning only; never adapter-visible. |
| `.../bot-token` | `create,read,update` | High: controls Bot API publication. Provisioner writes immediately after BotFather issues it. |
| `.../redaction-salt` | `create,read` | High integrity and sensitive pseudonymisation material. Overwrite, patch, and deletion are denied. |
OpenBao KV v2 documents that it honors the ACL distinction between `create`
and `update`; it also documents CAS `0` as create-if-absent. The fluid-telegram
client currently performs a read followed by a normal POST. The ACL therefore
supplies the decisive server-side write-once boundary: the first POST is a
create and a later POST is an update, which this policy denies. The client
should still add CAS `0` to make its intent race-safe and explicit, but this
plan does not depend on client-only behavior.
This is operational write-once for the provisioner, not immutable storage.
A platform administrator can recover from a corrupt first write through a
separately approved attended action. That action must preserve the old version
until recovery is verified and must never be exposed through this OIDC role.
### 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`
- proposed bound claim: `groups=fluid-telegram-operators` — **unconfirmed**
- callbacks: Bao UI plus localhost and loopback CLI callbacks already accepted
by the platform contract
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)
This section is incomplete while T01 and T02 are open; the plan must remain
`draft`.
- **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:** an unconfirmed group is a hard blocker, not a warning.
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.
- **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)
Not yet renderable. The owner-approved tenant segment, OIDC bound claim, live
structure survey, CCR/executor extension, and installed-engine create/update
probe are open inputs. When those close, this section must ask for one explicit
approval covering the exact objects and capability matrix above.

View file

@ -3,7 +3,7 @@ id: inbound-library-imap-approle
demand_source: inbound-library/workplans/ILIB-WP-0003-T01
consumer_repo: inbound-library
credential_type: openbao-approle-kv
status: approved
status: built
approved_by: "Bernd Worsch"
approved_at: "2026-10-05"
approved_spec_sha256: "c62765c8a4505821e77b58c5e0daf0511dec39fb569a05e190ff51f5cdc3e2ba"
@ -207,3 +207,32 @@ binding.
**Decision:** approve / reject / send back to phase 1 with what to change.
**Decision 2026-10-05: approved** by Bernd Worsch in a Claude Code session ("I approve"), for the exact build_spec above (digest `c62765c8…e2ba`).
## 6. Build result (phase 4)
Built 2026-10-05 with `build_approle_kv_lane` under the grant
`token-claude-ILIB-WP-0003-T03` (ops-mason-build). The builder reloaded the plan
and matched approval and digest `c62765c8…e2ba` before any OpenBao command. It
added a metadata-only record to `audit/build-log.jsonl`.
- **created** policy `workload-kv-read-inbound-library-imap`: `read` on
`platform/data/workloads/inbound-library/imap` and
`platform/metadata/workloads/inbound-library/imap`, and nothing else.
Confirmed with `bao policy read`.
- **created** AppRole `inbound-library`: `token_policies=[workload-kv-read-inbound-library-imap]`,
`token_ttl=900`, `token_max_ttl=1800`, `token_num_uses=4`, `secret_id_ttl=0`,
`bind_secret_id=true`. Confirmed with `bao read`.
- **delivered** `role_id` and `secret_id` to `/home/worsch/.local/inbound-library/approle/`
(directory 0700, files 0600). The values were not printed or read by the agent.
Capabilities-safe verification:
- **Negative:** a `default`-only token (2 minutes, 2 uses, then revoked) gets
`deny` on the data path.
- **Positive:** proven by the consumer, not by ops-mason, which reads no values.
The first systemd run of `inbound-library.service` (2026-10-05 16:09 CEST) did
the AppRole login, read the entry, revoked its token, and scanned
`library@coulomb.social` read-only. It retained 2 messages. No secret appeared
in the journal.
The ops-warden catalog entry is proposed under inbound-library ILIB-WP-0003-T05.
No paste-once step remains: the value (version 2) was supplied by the founder before the build.