Batch: the Forgejo operations that need an attended admin token without being unsafe, and which of them a guardfile change can actually fix #1068

Open
opened 2026-08-15 20:40:35 +00:00 by coilyco-ops · 0 comments
Member

Filed by Darren (director seat) at Kai's instruction, 2026-08-15. She asked for the full set of things that are tedious because they demand the admin key while not being unsafe, so the aosguard update can land as one batch.

Filed here because .specgen/guardfiles/aosguard/forgejo.kdl and forgejo-admin.kdl are authored here. The consumers are in coilyco-flight-deck/infrastructure.

The distinction that decides the fix

Three of these are not guardfile problems, and a guardfile change alone will not move them. Sorting on that first, because the batch will otherwise half-work.

  • Surface gap - the operation is safe, coilyco-ops is permitted to do it, and aosguard simply does not expose a verb. A guardfile edit fixes it outright.
  • Permission gap - Forgejo itself refuses the ops token, so exposing a verb produces a well-formed 403. Needs a grant change first, then the verb.
  • Coupling - a read-only path is gated only because the script resolves an admin token before it branches on dry-run. Fixed in the script, not in aosguard at all.

Coupling: read-only audits that demand an attended TTY

The strongest case, and the cheapest. Both converge scripts call resolve_token() before the dry-run branch, and scripts/forgejo-admin-token.py hard-refuses without a TTY:

if not sys.stdin.isatty() or not sys.stderr.isatty():
    raise SystemExit("forgejo-admin-token: attended operator shell required")
  • scripts/forgejo-branch-protection.py - execute = not args.dry_run at line 274, Forgejo(resolve_token()) at line 277.
  • scripts/forgejo-repo-settings.py - same shape, lines 180 and 183.

So dry_run=1 writes nothing and still needs a human at a terminal. Nobody can audit protection or merge-policy drift between attended sessions, which is the specific reason drift goes unnoticed rather than a hypothetical.

Fix: resolve the ordinary token for the read path and the admin token only when executing. Reads are GET /repos/{owner}/{repo}/branch_protections and GET /repos/{owner}/{repo}, neither of which needs admin.

Surface gap: safe reads with no verb at all

  • GET /repos/{owner}/{repo}/contents/{filepath} - reading a file from a public repo. Not in any guardfile. pr files and pr commits are the only file-shaped reads and neither reaches repo content. This is also the one missing endpoint behind coilyco-bridge/deploy#545, the Coilyco knowledge MCP, so it unblocks a real deliverable rather than only a convenience.
  • GET /repos/{owner}/{repo}/commits?path= - last-modified for one path, the companion read that lets a caller tell a stale cache from a fresh one.

Permission gap: org labels

  • POST /orgs/{org}/labels and PATCH /orgs/{org}/labels/{id} - creating and renaming a label is taxonomy, not risk.

Measured today, on all three orgs including coilyco-gaming:

403 Forbidden: {"message":"Must be an organization owner"}

coilyco-ops is a member rather than an owner, so a guardfile verb alone will not fix this - it needs an ownership or unit grant first. It is the reason coilyco-flight-deck/infrastructure#844 ships a label-taxonomy sync that the bot cannot run.

DELETE /orgs/{org}/labels/{id} should stay out of the batch. Deleting an org label strips it from every issue carrying it, with no undo. That is exactly the create-and-edit versus delete split a guardfile exists to express.

Already admin-gated, worth re-examining

forgejo-admin.kdl currently holds two operations, both cosmetic catalog metadata:

  • edit repo - already narrowed to --description only, which was the hard part.
  • replace-all repo-topic.

Neither can destroy anything. They sit behind the admin credential because repoEdit and repoUpdateTopics want repo admin and the ops bot has push. Same shape as org labels: a permission question, not a guardfile one.

Keep admin, so the batch does not over-reach

Naming these explicitly so nothing gets swept in by proximity:

  • branch protection apply and repo settings apply - these are the controls that stop history rewrites and enforce merge policy.
  • every secret provisioner - telegram, registry, tap-bump, ci-release, scoop-write.
  • runner registration tokens, provision-coilyco-ops-bot.sh, grant-coilyco-ops-org-repo-create.sh.
  • package-retention deletes.
  • org label DELETE, per above.

Suggested order

  1. Decouple the dry-runs. Pure win, no permission change, no guardfile change, and it restores headless drift auditing.
  2. Add the two repo-content reads to forgejo.kdl. Surface gap, self-contained, unblocks deploy#545.
  3. Decide the ops grant for org labels and repo metadata. This is Kai's call and it is one decision covering both, since both fail on the same "not an owner" boundary. Options are an ownership grant, a scoped team unit permission, or a second narrowly-scoped token.
  4. Then expose the verbs that step 3 makes reachable.

Acceptance

  • dry_run=1 on both converge scripts runs headless and writes nothing.
  • An agent can read a public repo file and its last-modified commit through aosguard ops forgejo.
  • The org-label grant question is answered either way and recorded.
  • No destructive operation moved off the attended credential, and org-label delete in particular is still admin-only.

Context: the 2026-08-15 label taxonomy rename. Docs in #1067, the sync in coilyco-flight-deck/infrastructure#844, the dispatch-gate consequence in coilyco-flight-deck/umbra#292.

**Filed by Darren (director seat) at Kai's instruction, 2026-08-15.** She asked for the full set of things that are tedious because they demand the admin key while not being unsafe, so the aosguard update can land as one batch. Filed here because `.specgen/guardfiles/aosguard/forgejo.kdl` and `forgejo-admin.kdl` are authored here. The consumers are in `coilyco-flight-deck/infrastructure`. ## The distinction that decides the fix Three of these are **not** guardfile problems, and a guardfile change alone will not move them. Sorting on that first, because the batch will otherwise half-work. * **Surface gap** - the operation is safe, `coilyco-ops` is permitted to do it, and aosguard simply does not expose a verb. A guardfile edit fixes it outright. * **Permission gap** - Forgejo itself refuses the ops token, so exposing a verb produces a well-formed 403. Needs a grant change first, then the verb. * **Coupling** - a read-only path is gated only because the script resolves an admin token before it branches on dry-run. Fixed in the script, not in aosguard at all. ## Coupling: read-only audits that demand an attended TTY The strongest case, and the cheapest. Both converge scripts call `resolve_token()` **before** the dry-run branch, and `scripts/forgejo-admin-token.py` hard-refuses without a TTY: ```python if not sys.stdin.isatty() or not sys.stderr.isatty(): raise SystemExit("forgejo-admin-token: attended operator shell required") ``` * `scripts/forgejo-branch-protection.py` - `execute = not args.dry_run` at line 274, `Forgejo(resolve_token())` at line **277**. * `scripts/forgejo-repo-settings.py` - same shape, lines 180 and **183**. So `dry_run=1` writes nothing and still needs a human at a terminal. **Nobody can audit protection or merge-policy drift between attended sessions**, which is the specific reason drift goes unnoticed rather than a hypothetical. Fix: resolve the ordinary token for the read path and the admin token only when executing. Reads are `GET /repos/{owner}/{repo}/branch_protections` and `GET /repos/{owner}/{repo}`, neither of which needs admin. ## Surface gap: safe reads with no verb at all * **`GET /repos/{owner}/{repo}/contents/{filepath}`** - reading a file from a public repo. Not in any guardfile. `pr files` and `pr commits` are the only file-shaped reads and neither reaches repo content. This is also the one missing endpoint behind `coilyco-bridge/deploy#545`, the Coilyco knowledge MCP, so it unblocks a real deliverable rather than only a convenience. * **`GET /repos/{owner}/{repo}/commits?path=`** - last-modified for one path, the companion read that lets a caller tell a stale cache from a fresh one. ## Permission gap: org labels * **`POST /orgs/{org}/labels`** and **`PATCH /orgs/{org}/labels/{id}`** - creating and renaming a label is taxonomy, not risk. Measured today, on all three orgs including `coilyco-gaming`: ``` 403 Forbidden: {"message":"Must be an organization owner"} ``` `coilyco-ops` is a member rather than an owner, so **a guardfile verb alone will not fix this** - it needs an ownership or unit grant first. It is the reason `coilyco-flight-deck/infrastructure#844` ships a label-taxonomy sync that the bot cannot run. **`DELETE /orgs/{org}/labels/{id}` should stay out of the batch.** Deleting an org label strips it from every issue carrying it, with no undo. That is exactly the create-and-edit versus delete split a guardfile exists to express. ## Already admin-gated, worth re-examining `forgejo-admin.kdl` currently holds two operations, both cosmetic catalog metadata: * `edit repo` - already narrowed to `--description` only, which was the hard part. * `replace-all repo-topic`. Neither can destroy anything. They sit behind the admin credential because `repoEdit` and `repoUpdateTopics` want repo admin and the ops bot has push. Same shape as org labels: a permission question, not a guardfile one. ## Keep admin, so the batch does not over-reach Naming these explicitly so nothing gets swept in by proximity: * branch protection **apply** and repo settings **apply** - these are the controls that stop history rewrites and enforce merge policy. * every secret provisioner - telegram, registry, tap-bump, ci-release, scoop-write. * runner registration tokens, `provision-coilyco-ops-bot.sh`, `grant-coilyco-ops-org-repo-create.sh`. * package-retention deletes. * org label DELETE, per above. ## Suggested order 1. **Decouple the dry-runs.** Pure win, no permission change, no guardfile change, and it restores headless drift auditing. 2. **Add the two repo-content reads** to `forgejo.kdl`. Surface gap, self-contained, unblocks deploy#545. 3. **Decide the ops grant** for org labels and repo metadata. This is Kai's call and it is one decision covering both, since both fail on the same "not an owner" boundary. Options are an ownership grant, a scoped team unit permission, or a second narrowly-scoped token. 4. **Then expose the verbs** that step 3 makes reachable. ## Acceptance * `dry_run=1` on both converge scripts runs headless and writes nothing. * An agent can read a public repo file and its last-modified commit through `aosguard ops forgejo`. * The org-label grant question is answered either way and recorded. * No destructive operation moved off the attended credential, and `org-label delete` in particular is still admin-only. --- Context: the 2026-08-15 label taxonomy rename. Docs in #1067, the sync in `coilyco-flight-deck/infrastructure#844`, the dispatch-gate consequence in `coilyco-flight-deck/umbra#292`.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-flight-deck/agentic-os#1068
No description provided.