diff --git a/workplans/MASON-WP-0003-state-hub-forge-read-lane.md b/workplans/MASON-WP-0003-state-hub-forge-read-lane.md new file mode 100644 index 0000000..a38e755 --- /dev/null +++ b/workplans/MASON-WP-0003-state-hub-forge-read-lane.md @@ -0,0 +1,187 @@ +--- +id: MASON-WP-0003 +type: workplan +title: "Forge read lane so central can derive private repositories" +domain: infotech +repo: ops-mason +status: proposed +owner: codex +topic_slug: custodian +created: "2026-08-26" +updated: "2026-08-26" +related: + - CUST-ADR-012 + - STATE-WP-0083 +--- + +# Forge read lane so central can derive private repositories + +## Goal + +Give the State Hub read-only access to Forgejo so it can derive projections for +private repositories, and register the lane so `warden route find` answers this +question the next time someone asks it. + +## Why this exists + +`ADR-012` makes the forge the projection source: central clones a repository's +default branch and derives its work records from what Forgejo holds. That works +today because the pod clones anonymously. + +It does not work for private repositories. The first fleet-wide reset +(`STATE-WP-0083-T04`, 2026-08-26) covered 121 repositories and nine failed +identically: + +```text +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 the hub's derivation entirely. + +This is a limit on the ADR's own premise. *The forge is the projection source* +holds only for repositories central can read, and nothing in the current design +says so. Worse, the failure is indistinguishable from a broken repository: the +pass records an error, not a policy, so "cannot read" and "does not exist" look +the same from the outside. + +## Why ops-mason owns it + +`warden route find` returns no lane for this need. The nearest entry is +`forgejo-admin-api-token` — an operator PAT owned by `railiance-platform` — which +is far more authority than derivation requires and the wrong thing to reuse. + +This repository builds "OpenBao AppRoles, policies, and KV secret paths so +ops-warden always has something real to route to", which is exactly what is +missing. ops-warden issues SSH certificates only and routes everything else; a +Forgejo token is not its to vend. + +## Decide the token's scope and breadth + +```task +id: MASON-WP-0003-T01 +status: todo +priority: high +``` + +Two choices to settle before anything is created, because they determine what +the credential can do if it leaks. + +**Permission.** Derivation performs `git clone --depth 1` and nothing else, so +the token needs repository *read* and no other scope. It must not be able to +push, administer, or read other Forgejo resources. Reusing the existing admin PAT +is explicitly rejected: a projection that only reads should not hold an +identity that can write. + +**Breadth.** Organisation-wide read means new private repositories derive without +further work and no one has to remember this lane exists. A per-repository grant +is tighter but needs maintenance every time a repository is added, and a +forgotten grant reappears as the same silent "cannot read" this workplan exists +to remove. Recommend organisation-wide read, on the grounds that the failure mode +of the narrow option is invisible. + +Acceptance: scope and breadth recorded as a decision with the reasoning, not +just a value. + +## Create the AppRole, policy, and KV path + +```task +id: MASON-WP-0003-T02 +status: todo +priority: high +``` + +Create the read-only Forgejo token, an OpenBao policy granting read on exactly +one KV path, and an AppRole for the State Hub workload to authenticate with. + +The token value never appears in Git, State Hub, a workplan, a log, or a chat +message. This task records *where* the credential lives and *who* may read it — +never what it is. + +Operator-executed. This workplan describes the lane; it does not perform the +creation. + +Acceptance: the KV path exists and holds a working read-only token; the policy +grants read on that path alone; the AppRole authenticates and cannot read +anything else. + +## Register the lane in the routing catalog + +```task +id: MASON-WP-0003-T03 +status: todo +priority: high +``` + +Add an entry so `warden route find "state hub read private repository"` resolves. +Its absence is why this had to be reasoned out from first principles rather than +looked up. + +The entry should name the owner, the subsystem, the KV path, the rotation +method, and state plainly that ops-warden routes but does not execute it. + +Acceptance: `warden route find` returns the lane; `warden route show` gives an +operator enough to act without reading this workplan. + +## Deliver the credential to the pod + +```task +id: MASON-WP-0003-T04 +status: todo +priority: medium +``` + +Make the token available to the `state-hub` deployment as a Kubernetes Secret, +mounted or injected, and referenced by the chart the same way `state-hub-env` +already is. + +It must not be baked into the image, and it must not be committed to the chart. +Rotation must not require a chart change. + +Acceptance: the pod can read the credential; nothing in the repository contains +it; rotating the token does not require a redeploy. + +## Teach the derivation to use it + +```task +id: MASON-WP-0003-T05 +status: todo +priority: medium +``` + +`derive_from_forge()` in `state-hub` clones anonymously. It should use the +credential when present and continue working without it — a hub with no token +must still derive public repositories rather than failing wholesale. + +Credentials must never reach the clone URL in a way that lands in logs or process +listings; use a credential helper or header, not an embedded userinfo URL. + +**Distinguish "cannot read" from "does not exist".** A repository the hub is not +permitted to read must be reported as unreadable, not as a repository whose +records no longer derive. Today the reset treats a failed clone as an error, +which is correct only by accident: if a clone ever returned empty instead of +failing, every record in that repository would be proposed for retirement. That +is the failure that nearly retired `vergabe-teilnahme`, and it must be impossible +here by construction rather than by luck. + +Acceptance: private repositories derive; a hub without the credential still +derives public ones; an unreadable repository is reported as unreadable and never +as retirement work. + +## Confirm the nine + +```task +id: MASON-WP-0003-T06 +status: wait +priority: medium +``` + +Re-run the fleet reset and confirm the nine repositories derive rather than +error. Expect them to arrive with a real diff — quite possibly a large one, since +none has ever been reconciled against its files. + +Treat the first diff for each as review material, not as work to apply. Every +repository examined this way for the first time has produced findings. + +Acceptance: fleet errors fall from nine to zero; each newly readable repository +has its first diff reviewed before anything is applied.