feat(aosguard): give forgejo-admin labels, repo settings, and branch protection #1069

Merged
coilysiren merged 2 commits from aos/claude/ym96-forgejo-admin into main 2026-08-15 21:22:55 +00:00
Member

Kai: the narrow scoping in forgejo-admin.kdl was an agent's decision, not a policy. Cosmetics, labels, repo settings and branch protection all belong on the attended admin surface.

What was there, and why the narrowing was redundant

The wrapper held two leaves: a repo edit restricted to --description, and topic replacement. Forgejo already refuses every one of these operations to coilyco-ops - it is an org member holding push, not an owner holding repo-admin. So the credential was a sufficient gate and the per-field narrowing was a second gate stacked on it.

It cost more than it bought. Repo merge policy and branch protection get converged often enough that the fallback was hand-running a script in an attended shell or editing the web UI.

Added

  • repo edit - the whole EditRepoOption, 31 flags. That is also exactly the field set infrastructure/scripts/forgejo-repo-settings.py converges: squash on, merge-commit and rebase off, fast-forward-only, delete-branch-after-merge, ignore-whitespace.
  • org-label get/list/create/edit/delete - a taxonomy change is now one verb.
  • branch-protection list/get/create/edit/delete.
  • repo get so a converge can diff before it writes.

Branch protection needed its spec vendored

Neither committed swagger carried the branch-protection paths, and specgen lock prunes the vendored snapshot rather than fetching upstream - it fails closed with operationId not found in spec. So the two paths came from the live Forgejo swagger along with BranchProtection, CreateBranchProtectionOption, EditBranchProtectionOption and the two shared response objects, written back in the vendored file's exact format (CRLF, indent=2, ensure_ascii=False, trailing newline, gzip mtime=0) and re-locked. 58 ops to 63.

Ordinary wrapper now points here

org-label create/edit/delete become never leaves naming the admin verb. They previously answered a bare 403 Must be an organization owner, which is true and tells the caller nothing. Verified the message renders like the existing fork repo denial. Reads stay on both wrappers.

Two judgement calls, stated rather than buried

private and archived ride along on repo edit rather than being blocked. An operator holding an attended admin token can flip either in the web UI in fewer steps, so blocking them here buys nothing. The ordinary wrapper still denies archive. Say the word if you want them carved out.

org-label delete is marked irreversible in its describe and points at edit org-label as the rename path, because an edit keeps the label id and every issue keeps its association. That is the distinction that made your 2026-08-15 rename non-destructive.

Rationale moved to docs/aosguard-forgejo-admin.md - the inline version tripped the 2-line comment cap and then the 80-line doc cap on aosguard.md.

Verified

All 13 verbs materialize through ward exec aosguard-run -- ops forgejo-admin describe. pre-commit clean. One pre-existing collection error in tests/test_check_yaml_strict.py reproduces on a clean tree and is unrelated.

Follow-on from #1068.

🤖 Generated with Claude Code

Kai: the narrow scoping in `forgejo-admin.kdl` was an agent's decision, not a policy. Cosmetics, labels, repo settings and branch protection all belong on the attended admin surface. ## What was there, and why the narrowing was redundant The wrapper held two leaves: a `repo edit` restricted to `--description`, and topic replacement. **Forgejo already refuses every one of these operations to `coilyco-ops`** - it is an org member holding push, not an owner holding repo-admin. So the credential was a sufficient gate and the per-field narrowing was a second gate stacked on it. It cost more than it bought. Repo merge policy and branch protection get converged often enough that the fallback was hand-running a script in an attended shell or editing the web UI. ## Added * **`repo edit`** - the whole `EditRepoOption`, 31 flags. That is also exactly the field set `infrastructure/scripts/forgejo-repo-settings.py` converges: squash on, merge-commit and rebase off, fast-forward-only, delete-branch-after-merge, ignore-whitespace. * **`org-label` get/list/create/edit/delete** - a taxonomy change is now one verb. * **`branch-protection` list/get/create/edit/delete**. * **`repo get`** so a converge can diff before it writes. ## Branch protection needed its spec vendored Neither committed swagger carried the branch-protection paths, and `specgen lock` **prunes the vendored snapshot rather than fetching upstream** - it fails closed with `operationId not found in spec`. So the two paths came from the live Forgejo swagger along with `BranchProtection`, `CreateBranchProtectionOption`, `EditBranchProtectionOption` and the two shared response objects, written back in the vendored file's exact format (CRLF, `indent=2`, `ensure_ascii=False`, trailing newline, gzip `mtime=0`) and re-locked. 58 ops to 63. ## Ordinary wrapper now points here `org-label` create/edit/delete become `never` leaves naming the admin verb. They previously answered a bare `403 Must be an organization owner`, which is true and tells the caller nothing. Verified the message renders like the existing `fork repo` denial. Reads stay on both wrappers. ## Two judgement calls, stated rather than buried **`private` and `archived` ride along on `repo edit`** rather than being blocked. An operator holding an attended admin token can flip either in the web UI in fewer steps, so blocking them here buys nothing. The ordinary wrapper still denies archive. Say the word if you want them carved out. **`org-label delete` is marked irreversible in its describe** and points at `edit org-label` as the rename path, because an edit keeps the label id and every issue keeps its association. That is the distinction that made your 2026-08-15 rename non-destructive. Rationale moved to `docs/aosguard-forgejo-admin.md` - the inline version tripped the 2-line comment cap and then the 80-line doc cap on `aosguard.md`. ## Verified All 13 verbs materialize through `ward exec aosguard-run -- ops forgejo-admin describe`. `pre-commit` clean. One pre-existing collection error in `tests/test_check_yaml_strict.py` reproduces on a clean tree and is unrelated. Follow-on from #1068. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(aosguard): give forgejo-admin labels, repo settings, and branch protection
Some checks failed
ci / aos-cli-tests (pull_request) Successful in 20s
ci / ward-doctor (pull_request) Successful in 12s
ci / gate (pull_request) Failing after 26s
86a4ec0418
The admin wrapper held two leaves: a repo edit narrowed to --description, and
topic replacement. Forgejo already refuses every one of these to coilyco-ops,
which is an org member holding push rather than an owner holding repo-admin,
so the credential was a sufficient gate and the per-field narrowing was a
second gate on top of it. It cost more than it bought - repo merge policy and
branch protection are converged often enough that the fallback was hand-running
a script in an attended shell or editing the web UI.

- repo edit now exposes the whole EditRepoOption, which is also the field set
  infrastructure/scripts/forgejo-repo-settings.py converges
- org-label get/list/create/edit/delete, so a taxonomy change is one verb
- branch-protection list/get/create/edit/delete

Branch protection needed its two paths vendored: neither committed spec carried
them, and specgen prunes the vendored snapshot rather than fetching upstream.
Added the paths plus BranchProtection, CreateBranchProtectionOption,
EditBranchProtectionOption and the two shared response objects, then re-locked.

Org-label writes on the ordinary wrapper become never leaves naming the admin
verb. They answered a bare 403 "Must be an organization owner" before, which is
true and says nothing about where to go.

private and archived ride along on repo edit rather than being blocked; an
operator holding an attended admin token can flip either in the web UI in fewer
steps. Rationale in docs/aosguard-forgejo-admin.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Kai Siren <coilysiren@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
(cherry picked from commit eb02258358)
Merge branch 'main' into aos/claude/ym96-forgejo-admin
All checks were successful
ci / ward-doctor (pull_request) Successful in 14s
ci / aos-cli-tests (pull_request) Successful in 20s
ci / gate (pull_request) Successful in 54s
dev-base-pr / build (pull_request) Successful in 13m46s
ae324a64f6
coilysiren deleted branch aos/claude/ym96-forgejo-admin 2026-08-15 21:22:55 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
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!1069
No description provided.