diff --git a/docs/credential-lane-designs/activity-core-repo-manager-forgejo-read.md b/docs/credential-lane-designs/activity-core-repo-manager-forgejo-read.md new file mode 100644 index 0000000..7d99db7 --- /dev/null +++ b/docs/credential-lane-designs/activity-core-repo-manager-forgejo-read.md @@ -0,0 +1,114 @@ +# Activity Core repo-manager Forgejo read lane (CCR-2026-0034, number to confirm) + +Status: **proposed**, drafted 2026-10-04 for STATE-WP-0094 memo 3 +(`freshness-runner-deploy-trial-e77f8b4`, accepted by the founder 2026-10-04, +approval `4ae04c8e`) and RMGR-WP-0020-T04. Nothing here is approved, applied or +provisioned. No Forgejo, OpenBao or cluster object was created. Confirm the CCR +number with the CCR tooling before registering; 0033 was the highest seen. + +## Why + +The bounded freshness runner (`rmgr freshness run`, repo-manager `e77f8b4`) +runs inside the Activity Core worker (`activity-core/actcore-worker`). It reads +each repository's default-branch HEAD through the Forgejo API at +`forgejo.coulomb.social/coulomb/`. Anonymous reads fail on private +repositories, which the runner records as a refusal and which stops the memo 3 +trials. `warden route find` (2026-10-04) has no route for this workload: + +- `state-hub-forge-derivation-read` is bound to SA `state-hub/state-hub` only. +- `core-hub-forgejo-source-read` (CCR-2026-0031) and + `policy-nexus-forgejo-source-read` (CCR-2026-0014) are bound to other namespaces. + +Reusing any of them would share one identity across workloads, which the +CCR-2026-0014 and CCR-2026-0031 precedent avoids. This lane gives the runner its +own read-only identity. + +## Shape + +| Item | Value | +| --- | --- | +| Forgejo user | `activity-core-freshness` (restricted, non-admin, private; random unrecorded password) | +| Forgejo team | `coulomb/activity-core-freshness-readers`: permission read, all repositories, `repo.code` read, every other unit none, cannot create repos | +| PAT | `activity-core-freshness-read-`, scope exactly `read:repository` | +| OpenBao | `platform/workloads/activity-core/repo-manager-forgejo-read` fields `FORGEJO_READ_TOKEN`, `API_USER`, `API_BASE_URL`, `TOKEN_SCOPES`, `GENERATED_AT` | +| Policy | `workload-kv-read-activity-core-repo-manager-forgejo` (read on data + metadata of that one path) | +| Auth | Kubernetes role `external-secrets-activity-core-repo-manager-forgejo`, bound to SA `external-secrets/external-secrets`, TTL 15m | +| Delivery | ExternalSecret `activity-core/actcore-repo-manager-forgejo-read` → separate Secret, key `FORGEJO_READ_TOKEN`. Never merged into `actcore-runtime-secret`, so the token is not in the worker env | +| Consumer | worker mounts the Secret read-only at `/var/run/secrets/repo-manager` (`defaultMode: 0440`) and sets `REPO_MANAGER_FORGEJO_TOKEN_FILE=/var/run/secrets/repo-manager/FORGEJO_READ_TOKEN` (activity-core `docs/rollout-owner-return-and-freshness-worker.md` §3b, lines marked "only once 3c is resolved") | +| Rotation | every 180 days or owner change; Forgejo PATs have no server-side expiry | +| Front door | ops-warden `activity-core-repo-manager-forgejo-read`, follow-up after approval | + +Open choice for the reviewer: either a dedicated ClusterSecretStore +`openbao-activity-core-repo-manager-forgejo` (CCR-2026-0031 pattern, below) or +granting the existing `openbao-activity-core` store role read on the new path. +The dedicated store keeps this high-risk org-wide read separate from the +worker's other secrets and is the default here. + +## Draft manifests (land only after metadata apply) + +```yaml +# CCR-2026-0034 (to confirm). Do not apply until approval and confirm-binding. +apiVersion: external-secrets.io/v1 +kind: ClusterSecretStore +metadata: + name: openbao-activity-core-repo-manager-forgejo + labels: + app.kubernetes.io/part-of: railiance-gitops + railiance-platform/component: external-secrets + app.kubernetes.io/name: activity-core +spec: + provider: + vault: + server: http://openbao.openbao.svc:8200 + path: platform + version: v2 + auth: + kubernetes: + mountPath: kubernetes + role: external-secrets-activity-core-repo-manager-forgejo + serviceAccountRef: + name: external-secrets + namespace: external-secrets + conditions: + - namespaces: + - activity-core +--- +apiVersion: external-secrets.io/v1 +kind: ExternalSecret +metadata: + name: actcore-repo-manager-forgejo-read + namespace: activity-core +spec: + refreshInterval: 5m + secretStoreRef: + kind: ClusterSecretStore + name: openbao-activity-core-repo-manager-forgejo + target: + template: + metadata: {} + name: actcore-repo-manager-forgejo-read + creationPolicy: Owner + deletionPolicy: Retain + data: + - secretKey: FORGEJO_READ_TOKEN + remoteRef: + key: workloads/activity-core/repo-manager-forgejo-read + property: FORGEJO_READ_TOKEN +``` + +## Attended sequence (after approval) + +1. Reviewers approve and confirm the binding with the CCR tooling + (`credential-change.py approve …`, `confirm-binding …`). +2. Founder, in their own terminal: delegated metadata apply (policy + auth + role), then mint the PAT through `warden access forgejo-admin-api-token + --exec`, verify scope, private-repository read and denials, write the value + to OpenBao from stdin and read it back for equality. A bootstrap script + modelled on `scripts/openbao-bootstrap-core-hub-source.sh` and + `scripts/provision-core-hub-forgejo-source.py` is still to be written; it is + not part of this draft. +3. Commit the two manifests and let Argo sync; check presence only. +4. activity-core adds the token env and mount lines from its rollout package + §3b, syncs, and runs the memo 3 apply trials. +5. Add the ops-warden catalog entry and move the CCR to verified/active with + evidence.