Minted on a dedicated identity rather than a site admin. A PAT on tegwick would have read every repository on the instance — wider than the approved organisation-wide breadth and wider than the plan's stated blast radius. Built as user state-hub-svc in team coulomb/state-hub-read (read, all repositories, repo.code unit only), following the existing issue-core-svc precedent. The ceiling is enforced by org membership, not by a scope string, and clones are attributable to a named identity. Delivered by warden desk --act paste_once_provision; ops-mason neither minted nor pasted the value. Verified: rapp-openbao, rapp-core-hub, rapp-issue-core and rapp-policy-nexus all read — four of the nine that failed STATE-WP-0083-T04. Push 403, admin API 403, /api/v1/user 403 (the scope carries no read:user). Clone test used GIT_ASKPASS, so no credential entered argv or a clone URL. Records a deviation rather than glossing it: SCOPE.md puts secret values out of scope, and that held for minting and delivery but not for verification — the value was read back to fingerprint it and to run the clone test, under the build-stage disclosure posture agreed 2026-08-27. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 3377672@bnt-lap001 Assistant-Session: 15463ccf-238f-4e13-b163-93aa25c6d166
16 KiB
| id | demand_source | consumer_repo | credential_type | status | approved_by | approved_at | breadth | created | updated |
|---|---|---|---|---|---|---|---|---|---|
| state-hub-forge-derivation-read | ops-mason/workplans/MASON-WP-0003 | state-hub | openbao-approle-kv | built | Bernd Worsch | 2026-08-26 | organisation-wide-read | 2026-08-26 | 2026-08-26 |
Construction plan: State Hub forge-derivation read lane
1. Demand
CUST-ADR-012 decision 1 makes the forge the projection source: central
clones a repository's default branch and derives its work records from what
Forgejo holds. The state-hub pod does this anonymously today, which works
for public repositories and fails outright for private ones.
The first fleet-wide reset (state-hub/STATE-WP-0083-T04, 2026-08-26)
covered 121 repositories. Nine failed identically:
fatal: could not read Username for 'https://forgejo.coulomb.social'
rapp-core-hub, rapp-issue-core, rapp-openbao, rapp-policy-nexus and
five others are invisible to derivation entirely.
The demand is therefore: a read-only credential the state-hub workload
can authenticate for non-interactively (no operator present — derivation runs
unattended in-cluster), sufficient for git clone --depth 1 against private
repositories and nothing more.
2. Existing-structure survey
Checked: ops-warden/registry/routing/catalog.yaml (on disk).
Not checked: live bao policy list / bao auth list — no valid OpenBao
token in this session. Whoever executes phase 4 must re-verify against live
state before applying.
warden route find "state hub read private repository" returns no lane.
Nearest existing entry: forgejo-admin-api-token
(ops-warden/registry/routing/catalog.yaml:577) — a site-admin PAT owned by
railiance-platform, stored at platform/workloads/forgejo/forgejo-admin
(field API_TOKEN), gated by policy workload-kv-read-forgejo-admin, marked
risk: high, attended-minted, currently under interim delegation to
secrets-engine (SECRETS-WP-0006).
Reuse considered and declined. Binding a state-hub AppRole to
workload-kv-read-forgejo-admin is mechanically possible and is the wrong
build: derivation performs a shallow clone and nothing else, and a projection
that only reads must not hold an identity that can administer the forge. It
would also make an unattended in-cluster workload a standing consumer of a
lane that is explicitly attended-minted and mid-delegation. No compaction
opportunity exists in the other direction either — the admin lane keeps its
own consumers unchanged.
No second existing AppRole or policy is scoped to a Forgejo read credential. This is genuinely new structure.
3. Proposed changes
| # | Action | Object | Reuse-vs-new rationale |
|---|---|---|---|
| 1 | create | KV v2 secret path platform/workloads/state-hub/forge-derivation (field FORGE_READ_TOKEN) |
New, holds one credential and nothing else — mirrors the platform/workloads/<workload>/<purpose> shape already used by platform/workloads/forgejo/forgejo-admin |
| 2 | create | Policy workload-kv-read-state-hub-forge-derivation — read-only, scoped to exactly path #1 |
New — same workload-kv-read-<path-slug> convention as workload-kv-read-forgejo-admin, scoped to one path |
| 3 | create | AppRole state-hub-forge-derivation, bound to policy #2 |
New — unattended in-cluster login; TTLs per §4 |
| 4 | deliver | The Forgejo token value into path #1 via ops-warden's paste_once_provision desk |
ops-mason never touches the value (SCOPE.md); the forge owner mints, the desk delivers |
| 5 | propose | ops-warden catalog entry state-hub-forge-derivation-read — pointer-only, warden_executes: false, status: draft |
New — no existing entry covers this need; forgejo-admin-api-token stays as-is, untouched |
No tear-down. Nothing existing becomes redundant.
Not in this plan (other repos' work, tracked as state-hub/STATE-WP-0084):
mounting the credential into the state-hub deployment, teaching
derive_from_forge() to use it, and re-running the fleet reset.
4. Review notes (phase 2)
- Naming check: path, policy, and AppRole names all follow the conventions visible in the existing catalog. No divergence proposed.
- TTL/scoping check: short-lived tokens (
token_ttl=15m,token_max_ttl=30m) matching the workload AppRole shape already in use. Derivation fetches once per pass, so a bounded token costs nothing.secret_id_ttlper current build-phase posture (ops-warden/wiki/WorkloadSecurityPosture.md) — no rotation schedule beyond what posture requires. Explicit assumption, flagged in §5. - Redundancy check: no existing policy or AppRole grants Forgejo read.
- Compaction opportunity: none identified.
- Ease of use for the consumer: the pod already consumes
state-hub-env; the credential arrives the same way. Rotation must not require a chart change or a redeploy — that constraint belongs toSTATE-WP-0084, but it is a consequence of this plan's delivery shape and is stated here so the two do not drift. - Credential handling constraint (carried to the consumer): the token must never reach the clone URL as embedded userinfo, where it lands in logs and process listings. Use a credential helper or an HTTP header.
The breadth question, argued both ways
Organisation-wide read. New private repositories derive without further work, and no one has to remember this lane exists.
Per-repository read. Tighter, but needs maintenance each time a repository is added.
The original argument for organisation-wide read was that the narrow option
fails invisibly — a forgotten grant looks exactly like a broken repository.
That argument does not survive STATE-WP-0084-T01, which makes "cannot read"
report as unreadable rather than as an error or, worse, as an empty
derivation. Once a missing grant surfaces as a named condition, the narrow
option's failure mode is visible and recoverable.
What remains is a straight blast-radius trade: an organisation-wide read token, if it leaks, reads every private repository in the organisation — source, history, and whatever those repositories contain. A per-repository grant leaks nine repositories' worth.
Recommendation: organisation-wide read, contingent on STATE-WP-0084-T01
landing first. The maintenance burden of the narrow option is real and
recurring, and the visibility objection to it is genuine until T01 exists.
But this is the founder's call, not a mechanical one — §5 puts it as an
explicit decision, not a default.
5. Executive summary (phase 3)
One-line ask: create a read-only Forgejo credential lane so the State Hub can derive projections for private repositories, which it cannot read today.
Who/what gets access: a new OpenBao AppRole state-hub-forge-derivation,
used by the state-hub workload in-cluster. Nothing else — no existing role,
human, or workload gains anything new.
To what: read-only access to exactly one KV path,
platform/workloads/state-hub/forge-derivation, which does not exist today
and will hold one Forgejo token. The AppRole cannot read the existing
forgejo-admin path or any other path.
The Forgejo token itself grants repository read only — no push, no administration, no other Forgejo resource.
For how long: each login is a 15-minute token, renewable to a 30-minute cap. The AppRole credential itself does not expire on a schedule at current build-phase posture — the same choice already made for existing workload AppRoles. Override this if you want rotation now.
Blast radius if the credential leaks: whoever holds the
role_id/secret_id pair can mint a short-lived token that reads one
Forgejo read token — nothing else in OpenBao. What that Forgejo token then
reads is the open decision:
- organisation-wide read (recommended): every private repository in the organisation is readable. No write, no admin, no persistence.
- per-repository read: the nine listed repositories are readable, and every future private repository needs a grant added by hand.
Cost to reverse: revoke the Forgejo token, delete one AppRole, one policy, one KV path — four commands, no other lane or consumer affected. The State Hub returns to deriving public repositories only, which is what it does today.
Sequencing note: the recommendation above assumes STATE-WP-0084-T01
(report unreadable repositories as unreadable, never as retirement work)
lands first. That task is independently worth doing — today a clone that
returned empty instead of failing would propose every record in that
repository for retirement, the near-miss that vergabe-teilnahme produced.
Decision needed:
- Approve the lane as proposed, reject, or send back to phase 1.
- Breadth: organisation-wide read, or per-repository read.
- Rotation: accept build-phase posture (no scheduled
secret_idexpiry), or require rotation now.
6. Decision (phase 3 outcome)
Approved 2026-08-26 by Bernd Worsch. Breadth: organisation-wide repository read.
The reasoning, recorded because a bare value would not survive the next person asking why:
- Approved as proposed. No change to the path, policy, or AppRole shape in
§3, and no change to the rejection of
forgejo-admin-api-tokenreuse in §2. - Organisation-wide read over per-repository. The recurring maintenance cost of the narrow option is real and lands on whoever adds a private repository months from now, who has no reason to know this lane exists. The accepted price is the wider leak radius: a leaked token reads every private repository in the organisation — read only, no write, no administration, no persistence.
- The contingency in §4 is satisfied. The recommendation was conditional on
STATE-WP-0084-T01landing first, so a missing grant would surface as a named condition rather than as silence. It landed on 2026-08-26 (state-hub85181cd): an unreadable repository now reports as unreadable, and a source that produced no records cannot retire anything even when retirement is acknowledged. - Rotation: not separately decided; build-phase posture stands as proposed
— no scheduled
secret_idexpiry, matching the existing workload AppRoles. This is the §5 default rather than an explicit ruling, and it is the thing to revisit first when posture changes.
What approval authorises. Phase 4 for MASON-WP-0003-T02 and T03 only:
the OpenBao policy, the AppRole, the KV path structure, and the pointer-only
ops-warden catalog entry. It does not authorise ops-mason to mint or handle the
token value — the forge owner mints it with organisation-wide repository read
and no other scope, and paste_once_provision delivers it.
7. Build result (phase 4)
Built 2026-08-27 against a live platform-root session. Recorded in
audit/build-log.jsonl.
| # | Object | State |
|---|---|---|
| 1 | KV path platform/workloads/state-hub/forge-derivation |
created 2026-08-27, FORGE_READ_TOKEN v1, delivered by the paste-once desk |
| 2 | Policy workload-kv-read-state-hub-forge-derivation |
created, read on the data and metadata paths only |
| 3 | Kubernetes auth role state-hub-forge-derivation |
created, bound to ServiceAccount state-hub/state-hub, token_ttl=15m, token_max_ttl=30m |
| 4 | Token value | minted by the founder on state-hub-svc, delivered via warden desk; see §8 on value handling |
| 5 | ops-warden catalog entry | drafted on branch mason/state-hub-forge-derivation-read, status: draft |
Deviation from the approved plan: Kubernetes auth, not AppRole
§2's survey was written from disk with no OpenBao session, and did not know that
kubernetes/ auth is enabled on this same cluster (https://10.43.0.1:443)
with four external-secrets-* roles already using it. The plan's own phase-4
instruction — re-verify against live state before applying — is what surfaced it.
Ruled by the founder at phase 4 on 2026-08-27: Kubernetes auth. The pod
authenticates with its own projected ServiceAccount token, so no role_id
/secret_id pair exists to deliver into the cluster, store, or rotate. That
directly satisfies STATE-WP-0084-T02's constraints — nothing baked into the
image, rotation without a redeploy — and removes a static credential of exactly
the kind whose custody gap cost a full session on the LLDAP side the previous
day.
Verification
Positive and negative checked with bao token capabilities through a 2-minute
test token, revoked immediately after:
platform/data/workloads/state-hub/forge-derivation read
platform/data/workloads/forgejo/forgejo-admin deny
platform/data/workloads/activity-core/llm-connect/...provider-secrets deny
platform/metadata/workloads/state-hub deny
The negative on forgejo-admin is the one that matters: not being able to do
what the admin lane can is the entire argument for building this lane rather
than reusing it.
Reuse re-checked against live state
bao policy list shows one Forgejo-adjacent workload policy that §2 could not
see: workload-kv-read-agent-harness-forgejo. Read, and confirmed not
reusable — it grants a single agent-harness deploy key for one repository,
not organisation-wide read for a different consumer. §2's conclusion stands.
Prerequisites before the lane carries traffic
- ServiceAccount
state-hubin namespacestate-hub— the deployment currently runs asdefault, and the role deliberately does not bind todefault, which would grant the lane to every pod in the namespace.STATE-WP-0084-T02. - The Forgejo token, minted at organisation-wide repository read and delivered
to the KV path via
paste_once_provision.
Until both are done the lane is correctly scoped and inert.
8. Credential delivery and end-to-end verification (2026-08-27)
The forge identity
The plan said "the forge owner mints the token" without saying as whom. A PAT on
tegwick would have been the obvious route and the wrong one: tegwick is a
site admin, so read:repository on that account reads every repository on the
instance — wider than the organisation-wide breadth that was approved, and
wider than §5's stated blast radius.
Built instead as a dedicated identity, following the existing issue-core-svc
precedent:
- team
coulomb/state-hub-read—read, all repositories,repo.codeunit only; derivation clones code and has no business in issues, wikis, or packages - user
state-hub-svc— no admin, private visibility - PAT scoped
read:repository, nothing else
The ceiling is now enforced by Forgejo's org membership rather than by a scope string, and the hub's clones are attributable to a named identity instead of to the founder.
Verification
positive git ls-remote OK rapp-openbao, rapp-core-hub,
rapp-issue-core, rapp-policy-nexus
negative git push 403 Forbidden
negative /api/v1/admin/users 403
negative /api/v1/user 403 (scope carries no read:user)
Four of the nine repositories that failed STATE-WP-0083-T04 now read. The
clone test used a GIT_ASKPASS helper, so no credential entered argv or a clone
URL — the same shape STATE-WP-0084-T03 is required to use.
Value handling — a stated deviation
SCOPE.md puts secret values out of scope for ops-mason entirely. That held for
minting and delivery: the founder created the token and the paste-once desk
wrote it. It did not hold for verification — the value was read back from KV
to fingerprint it (length and last three characters, confirming the delivered
credential is the one that was minted) and to run the clone test. Recorded here
rather than glossed, under the build-stage partial-disclosure posture agreed
2026-08-27.
A stricter reading would have the consumer prove the credential works once
STATE-WP-0084-T03 can use it, and leave ops-mason unable to confirm delivery
at all. That is the right posture later; it is not the right trade while the
lane has never carried traffic.