Add the full PR lifecycle (close, reopen, update, edit) to the forgejo KDL spec #488

Closed
opened 2026-07-13 03:25:44 +00:00 by coilyco-ops · 2 comments
Member

Problem

A stranded-PR sweep across all 66 repos (3 coilyco orgs plus the coilysiren personal namespace) found 6 open PRs, and none of them can be resolved through ward. The PR surface exposes only view and list (specgen) plus status / merge / runs / rerun (native ward agent pr). There is no way to close a PR, so a stranded or empty PR can only be cleaned up by hand in the web UI.

Five of the six are empty leftovers on coilyco-bridge/agentic-os-kai (#721, #722, #723, #725, #728). Their head branches are already ancestors of main (verified with git merge-base --is-ancestor), so the work landed and the issues are correctly closed, but the PRs stayed open showing a 0-file diff. The sixth, coilyco-flight-deck/ward#987, was superseded by main and also needs closing. Every one of these wants exactly one verb that does not exist.

Ask

Add the missing PR lifecycle actions to the KDL spec so the PR surface is complete, keeping the existing merge-gate policy intact.

Surface at least:

  • close pr - repoEditPullRequest (PATCH /repos/{owner}/{repo}/pulls/{index}, state=closed). The verb this sweep actually needed.
  • reopen pr - same op, state=open. The inverse, so a wrong close is reversible.
  • edit pr - same op, for title and body.
  • update pr - repoUpdatePullRequest (POST /repos/{owner}/{repo}/pulls/{index}/update), to pull main into a stale PR branch. ward#987 stranded partly because nothing could refresh a branch 180 commits behind.
  • create pr - repoCreatePullRequest, if it fits the tier.
  • Read helpers worth having beside them: repoGetPullRequestFiles and repoGetPullRequestCommits. The sweep had to clone each repo to learn a PR was empty, because additions/changed_files alone did not distinguish "no work" from "already merged".

Please survey the pulls section of the Forgejo swagger and add the coherent lifecycle set, not only close.

Where

Authored in this repo, per the aos#332 carved exception. .ward/guardfile.forgejo.merge.kdl is the natural home for the mutating verbs: it already lives outside the read/write/admin tier chain and binds to the director and engineer roles only, which is the right blast radius for closing a PR. Rename or re-describe that overlay if "merge" no longer fits what it grants.

Two traps that will bite

  1. The pinned swagger lock is pruned. .ward/forgejo.swagger.lock.json carries 35 paths and contains only repoListPullRequests and repoGetPullRequest. It does not contain repoMergePullRequest, repoPullRequestIsMerged, or any pulls write op, even though guardfile.forgejo.merge.kdl grants the first two. Adding a leaf almost certainly means refreshing the lock so the new operationIds resolve. Work out how that lock is regenerated rather than hand-editing it.

  2. The read tier and the monolith disagree about pr today. guardfile.forgejo.read.kdl has explicit never view pr / never list pr denials ("pull requests are not exposed through ward; read them in the web UI") and comments that the denials inherit up so no tier exposes a pr verb. But guardfile.forgejo.kdl (the monolith) grants can view pr / can list pr, and on a live director surface ward ops forgejo pr list and pr view both work. So the deny is either dead or being bypassed. Reconcile the two, and make sure the new mutating leaves are not silently dropped by an inherited never.

Done condition

A director surface can run ward ops forgejo pr close coilyco-bridge agentic-os-kai 721 and have it work, the merge gate is unchanged, ward exec surface-check still passes, and docs/ward-ops-forgejo-reference.md is regenerated so the new verb names are greppable off-disk.

Follow-up cleanup of the 6 stranded PRs is blocked on this and will be dispatched once the verb lands.

## Problem A stranded-PR sweep across all 66 repos (3 coilyco orgs plus the `coilysiren` personal namespace) found 6 open PRs, and **none of them can be resolved through ward**. The PR surface exposes only `view` and `list` (specgen) plus `status` / `merge` / `runs` / `rerun` (native `ward agent pr`). There is no way to **close** a PR, so a stranded or empty PR can only be cleaned up by hand in the web UI. Five of the six are empty leftovers on `coilyco-bridge/agentic-os-kai` (#721, #722, #723, #725, #728). Their head branches are already ancestors of `main` (verified with `git merge-base --is-ancestor`), so the work landed and the issues are correctly closed, but the PRs stayed open showing a 0-file diff. The sixth, `coilyco-flight-deck/ward#987`, was superseded by `main` and also needs closing. Every one of these wants exactly one verb that does not exist. ## Ask Add the missing PR lifecycle actions to the KDL spec so the PR surface is complete, keeping the existing merge-gate policy intact. Surface at least: * `close` pr - `repoEditPullRequest` (PATCH `/repos/{owner}/{repo}/pulls/{index}`, `state=closed`). The verb this sweep actually needed. * `reopen` pr - same op, `state=open`. The inverse, so a wrong close is reversible. * `edit` pr - same op, for title and body. * `update` pr - `repoUpdatePullRequest` (POST `/repos/{owner}/{repo}/pulls/{index}/update`), to pull `main` into a stale PR branch. `ward#987` stranded partly because nothing could refresh a branch 180 commits behind. * `create` pr - `repoCreatePullRequest`, if it fits the tier. * Read helpers worth having beside them: `repoGetPullRequestFiles` and `repoGetPullRequestCommits`. The sweep had to clone each repo to learn a PR was empty, because `additions`/`changed_files` alone did not distinguish "no work" from "already merged". Please survey the pulls section of the Forgejo swagger and add the coherent lifecycle set, not only `close`. ## Where Authored in this repo, per the aos#332 carved exception. `.ward/guardfile.forgejo.merge.kdl` is the natural home for the mutating verbs: it already lives outside the read/write/admin tier chain and binds to the director and engineer roles only, which is the right blast radius for closing a PR. Rename or re-describe that overlay if "merge" no longer fits what it grants. ## Two traps that will bite 1. **The pinned swagger lock is pruned.** `.ward/forgejo.swagger.lock.json` carries 35 paths and contains only `repoListPullRequests` and `repoGetPullRequest`. It does **not** contain `repoMergePullRequest`, `repoPullRequestIsMerged`, or any pulls write op, even though `guardfile.forgejo.merge.kdl` grants the first two. Adding a leaf almost certainly means refreshing the lock so the new operationIds resolve. Work out how that lock is regenerated rather than hand-editing it. 2. **The read tier and the monolith disagree about pr today.** `guardfile.forgejo.read.kdl` has explicit `never view pr` / `never list pr` denials ("pull requests are not exposed through ward; read them in the web UI") and comments that the denials inherit up so no tier exposes a pr verb. But `guardfile.forgejo.kdl` (the monolith) grants `can view pr` / `can list pr`, and on a live director surface `ward ops forgejo pr list` and `pr view` both work. So the deny is either dead or being bypassed. Reconcile the two, and make sure the new mutating leaves are not silently dropped by an inherited `never`. ## Done condition A director surface can run `ward ops forgejo pr close coilyco-bridge agentic-os-kai 721` and have it work, the merge gate is unchanged, `ward exec surface-check` still passes, and `docs/ward-ops-forgejo-reference.md` is regenerated so the new verb names are greppable off-disk. Follow-up cleanup of the 6 stranded PRs is blocked on this and will be dispatched once the verb lands.
Author
Member

WARD-RESERVATION: held 🔒

reservation details

Holder: container engineer-claude-agentic-os-488 on host KAI-DESKTOP-TOWER.

Reserved by ward agent --harness claude (reserved 2026-07-13T03:26:13Z). Concurrent ward agent runs are blocked until it finishes or the reservation goes stale (3h TTL). --override-reservation overrides.

Do not comment on or edit this issue to steer the run while it is reserved. The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a new issue, dispatched fresh. That is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494).

run seed context — what this run is carrying (ward#609)
  • Resolved: coilyco-flight-deck/agentic-os#488 · branch issue-488 · harness claude · workflow pull-request-and-merge
  • Run: engineer-claude-agentic-os-488 · ward v0.633.0 · dispatched 2026-07-13T03:26:13Z
  • Comment thread: 0 included in the pre-flight read, 0 stripped (ward's own automated comments).

Static container doctrine and seed boilerplate are identical every run and omitted here (they ride ward v0.633.0).

— Claude (she/her), via ward agent

<!-- ward-agent-reservation --> WARD-RESERVATION: held 🔒 <details><summary>reservation details</summary> Holder: container `engineer-claude-agentic-os-488` on host `KAI-DESKTOP-TOWER`. Reserved by `ward agent --harness claude` (reserved 2026-07-13T03:26:13Z). Concurrent `ward agent` runs are blocked until it finishes or the reservation goes stale (3h TTL). `--override-reservation` overrides. **Do not comment on or edit this issue to steer the run while it is reserved.** The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a **new issue, dispatched fresh**. That is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494). <details><summary>run seed context — what this run is carrying (ward#609)</summary> - **Resolved:** `coilyco-flight-deck/agentic-os#488` · branch `issue-488` · harness `claude` · workflow `pull-request-and-merge` - **Run:** `engineer-claude-agentic-os-488` · ward `v0.633.0` · dispatched `2026-07-13T03:26:13Z` - **Comment thread:** 0 included in the pre-flight read, 0 stripped (ward's own automated comments). Static container doctrine and seed boilerplate are identical every run and omitted here (they ride ward v0.633.0). </details> </details> <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
Author
Member

WARD-OUTCOME: submitted

details

workflow: pull-request-and-merge; review summary: skipped - the in-container review gate was intentionally skipped per the temporary ward default pending brokered QA

  • PR - #489 - carries closes #488 and ward.workflow: pull-request-and-merge, ci / gate green on head dda8f08, branch merge-ready for the director gate.
  • Shape - the agent put the lifecycle (files/commits/create/edit/close/reopen/update) in the monolith (the surface ward.bundle.kdl actually mounts for ward ops forgejo, so the done-condition command exists), granted the pr read side in the read tier in place of the dead never pair (trap 2), and re-described the merge overlay as the PR-lifecycle overlay carrying the mutating verbs (the issue's Where). Merge itself is untouched everywhere.
  • Trap 1 - the lock was regenerated with the real cli-guard kdl-specs lock --guardfile driver at v0.94.0 (the committed lock's own provenance), not hand-edited. Diff is a strict superset: 5 new operationIds plus 3 transitive definitions, specverb.lock byte-identical. One wrinkle: the driver only discovers *.guardfile.kdl names, so the regen ran in a scratch dir with the monolith copied under a glob-matching name - the lock filename derives from the spec node, so the output is identical.
  • Validation - ward doctor (ops bundle mounts fail-closed) passes against the edited bundle, ward ops forgejo pr close coilyco-bridge agentic-os-kai 721 --dry-run resolves to PATCH .../pulls/721 with {"state": "closed"}, ward exec surface-check passes, 459 tests green, pre-commit clean.
  • Felt - clean data-only change once the mount wiring was clear. The main investigation cost was working out that ward ops forgejo mounts only the monolith (roles.kdl overlay mounting is ward#1072), which is why the mutating verbs had to land in the monolith and not only in the merge overlay the issue suggested.
  • Confidence - high on the lifecycle verbs and the lock. Medium on one seam: the merge pair is still absent from the lock (pre-existing, the lock is the monolith member's contract), so the merge overlay's own grants still cannot resolve until ward#1072-side work picks a lock story for the overlay binaries.
  • Surprise - regenerating the reference doc with ward v0.633.0 dropped the two actions rerun exec-bridge sections the v0.584.0 render had (same-path exec overlays skip fail-closed). Verified pre-existing binary behavior, not this change - noted on the PR, tracked by ward#950 / agentic-os#434.
  • Follow-ups - none filed: stranded-PR cleanup is the issue's own already-planned dispatch once this lands, and the lock/overlay seam rides ward#1072.
WARD-OUTCOME: submitted <details><summary>details</summary> workflow: pull-request-and-merge; review summary: skipped - the in-container review gate was intentionally skipped per the temporary ward default pending brokered QA * PR - https://forgejo.coilysiren.me/coilyco-flight-deck/agentic-os/pulls/489 - carries `closes #488` and `ward.workflow: pull-request-and-merge`, `ci / gate` green on head dda8f08, branch merge-ready for the director gate. * Shape - the agent put the lifecycle (files/commits/create/edit/close/reopen/update) in the monolith (the surface `ward.bundle.kdl` actually mounts for `ward ops forgejo`, so the done-condition command exists), granted the pr read side in the read tier in place of the dead `never` pair (trap 2), and re-described the merge overlay as the PR-lifecycle overlay carrying the mutating verbs (the issue's Where). Merge itself is untouched everywhere. * Trap 1 - the lock was regenerated with the real cli-guard `kdl-specs lock --guardfile` driver at v0.94.0 (the committed lock's own provenance), not hand-edited. Diff is a strict superset: 5 new operationIds plus 3 transitive definitions, `specverb.lock` byte-identical. One wrinkle: the driver only discovers `*.guardfile.kdl` names, so the regen ran in a scratch dir with the monolith copied under a glob-matching name - the lock filename derives from the `spec` node, so the output is identical. * Validation - `ward doctor` (ops bundle mounts fail-closed) passes against the edited bundle, `ward ops forgejo pr close coilyco-bridge agentic-os-kai 721 --dry-run` resolves to PATCH `.../pulls/721` with `{"state": "closed"}`, `ward exec surface-check` passes, 459 tests green, pre-commit clean. * Felt - clean data-only change once the mount wiring was clear. The main investigation cost was working out that `ward ops forgejo` mounts only the monolith (roles.kdl overlay mounting is ward#1072), which is why the mutating verbs had to land in the monolith and not only in the merge overlay the issue suggested. * Confidence - high on the lifecycle verbs and the lock. Medium on one seam: the merge pair is still absent from the lock (pre-existing, the lock is the monolith member's contract), so the merge overlay's own grants still cannot resolve until ward#1072-side work picks a lock story for the overlay binaries. * Surprise - regenerating the reference doc with ward v0.633.0 dropped the two `actions rerun` exec-bridge sections the v0.584.0 render had (same-path exec overlays skip fail-closed). Verified pre-existing binary behavior, not this change - noted on the PR, tracked by ward#950 / agentic-os#434. * Follow-ups - none filed: stranded-PR cleanup is the issue's own already-planned dispatch once this lands, and the lock/overlay seam rides ward#1072. </details>
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#488
No description provided.