feat(ops): widen forgejo guardfile to native search/list endpoints (users, topics, commits, branches, tags, org-members) #368

Closed
opened 2026-06-26 03:00:21 +00:00 by coilysiren · 2 comments
Owner

Goal

Widen the Forgejo ops surface to cover the native search/list endpoints Forgejo's REST
API exposes but ward's curated swagger lock currently omits. These are pure ward-kdl
guardfile + spec-lock additions - no new behavior, just allowlisting endpoints that
already exist server-side. They follow the exact pattern already in the guardfile
(can search repo, can list org-repo, can list user-repo).

Endpoints to add (all read-only)

  • GET /users/search - user finder (can search user).
  • GET /topics/search - topic finder (can search topic).
  • GET /repos/{owner}/{repo}/commits - commit list (can list commit); supports
    sha, path, page/limit. (No full-text message search server-side - that's fine,
    this is the list surface, not a grep.)
  • GET /repos/{owner}/{repo}/branches - branch list (can list branch).
  • GET /repos/{owner}/{repo}/tags - tag list (can list tag).
  • Org member search - GET /orgs/{org}/members (and /members/search if the spec
    carries it); can list org-member / can search org-member. Confirm the exact
    operationId against the upstream spec.

Mechanics (the two-step ward-kdl pattern)

  1. Curated spec lock - add these paths to cmd/ward-kdl/forgejo.swagger.lock.json
    (today it carries 21 paths; the operationId convention can't resolve a verb whose
    path isn't in the lock). Pull the path objects from the upstream Forgejo swagger.
  2. Guardfile grants - add the can ... leaves to the combined guardfile
    cmd/ward-kdl/ward-kdl.forgejo.guardfile.kdl (this is the one ward embeds). The
    read-tier ward-kdl-read/...read.guardfile.kdl already wildcards can get * / can list *, so the new list verbs flow into it automatically once the lock carries
    the paths; the search verbs are a distinct verb (the read tier has no can search *), so either add explicit can search ... leaves or extend the read wildcard - match
    how can search repo / can search issue are handled today.
  3. Run make sync-ops-assets to mirror the guardfile + lock into cmd/ward/opsassets/
    for embedding, and make build-ward-kdl / the tier builds as the existing targets require.

Scope guardrails (match the existing guardfile conventions)

  • Every {owner} leaf stays under the restrict owner matches coily* scope gate already
    declared in the guardfile - do not add an owner-unscoped leaf.
  • Read-only only. Do not add create/edit/delete on any of these resources.
  • Each new leaf gets a describe line, like every existing leaf.
  • Regenerate the per-area reference doc (docs/ward-kdl/ward-kdl.forgejo.guardfile.md)
    and update docs/FEATURES.md if the search surface is a materially reshaped feature
    (house rule).

Acceptance

  • ward ops forgejo user search, topic search, commit list, branch list, tag list, and the org-member finder all resolve and return results against a coily* owner.
  • The curated swagger lock carries the new paths; make sync-ops-assets leaves the
    embedded opsassets/ copies in sync (the skew check passes).
  • Owner-scope gate still applies; no write verb was added.
  • make build test vet lint green before push.

Why

These are the Tier-A "just widen the guardfile" items from a design walk: the endpoints
are native Forgejo REST, so they belong as generated specverb leaves, not hand-written Go.

Filed from a read-only architect session.

## Goal Widen the Forgejo ops surface to cover the native search/list endpoints Forgejo's REST API exposes but ward's curated swagger lock currently omits. These are pure ward-kdl guardfile + spec-lock additions - no new behavior, just allowlisting endpoints that already exist server-side. They follow the exact pattern already in the guardfile (`can search repo`, `can list org-repo`, `can list user-repo`). ## Endpoints to add (all read-only) - **`GET /users/search`** - user finder (`can search user`). - **`GET /topics/search`** - topic finder (`can search topic`). - **`GET /repos/{owner}/{repo}/commits`** - commit list (`can list commit`); supports `sha`, `path`, `page`/`limit`. (No full-text message search server-side - that's fine, this is the list surface, not a grep.) - **`GET /repos/{owner}/{repo}/branches`** - branch list (`can list branch`). - **`GET /repos/{owner}/{repo}/tags`** - tag list (`can list tag`). - **Org member search** - `GET /orgs/{org}/members` (and `/members/search` if the spec carries it); `can list org-member` / `can search org-member`. Confirm the exact operationId against the upstream spec. ## Mechanics (the two-step ward-kdl pattern) 1. **Curated spec lock** - add these paths to `cmd/ward-kdl/forgejo.swagger.lock.json` (today it carries 21 paths; the operationId convention can't resolve a verb whose path isn't in the lock). Pull the path objects from the upstream Forgejo swagger. 2. **Guardfile grants** - add the `can ...` leaves to the combined guardfile `cmd/ward-kdl/ward-kdl.forgejo.guardfile.kdl` (this is the one ward embeds). The read-tier `ward-kdl-read/...read.guardfile.kdl` already wildcards `can get *` / `can list *`, so the new **list** verbs flow into it automatically once the lock carries the paths; the **search** verbs are a distinct verb (the read tier has no `can search *`), so either add explicit `can search ...` leaves or extend the read wildcard - match how `can search repo` / `can search issue` are handled today. 3. Run `make sync-ops-assets` to mirror the guardfile + lock into `cmd/ward/opsassets/` for embedding, and `make build-ward-kdl` / the tier builds as the existing targets require. ## Scope guardrails (match the existing guardfile conventions) - Every `{owner}` leaf stays under the `restrict owner matches coily*` scope gate already declared in the guardfile - do not add an owner-unscoped leaf. - Read-only only. Do not add create/edit/delete on any of these resources. - Each new leaf gets a `describe` line, like every existing leaf. - Regenerate the per-area reference doc (`docs/ward-kdl/ward-kdl.forgejo.guardfile.md`) and update `docs/FEATURES.md` if the search surface is a materially reshaped feature (house rule). ## Acceptance - `ward ops forgejo user search`, `topic search`, `commit list`, `branch list`, `tag list`, and the org-member finder all resolve and return results against a coily* owner. - The curated swagger lock carries the new paths; `make sync-ops-assets` leaves the embedded `opsassets/` copies in sync (the skew check passes). - Owner-scope gate still applies; no write verb was added. - `make build test vet lint` green before push. ## Why These are the Tier-A "just widen the guardfile" items from a design walk: the endpoints are native Forgejo REST, so they belong as generated specverb leaves, not hand-written Go. Filed from a read-only architect session.
Author
Owner

🔒 Reserved by ward agent --driver claude — container ward-ward-issue-368-claude-c88e9f5a on host e777a007549a is carrying this issue (reserved 2026-06-26T03:00:25Z). Concurrent ward agent runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); --force overrides.

— Claude (she/her), via ward agent

<!-- ward-agent-reservation --> 🔒 Reserved by `ward agent --driver claude` — container `ward-ward-issue-368-claude-c88e9f5a` on host `e777a007549a` is carrying this issue (reserved 2026-06-26T03:00:25Z). Concurrent `ward agent` runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); `--force` overrides. <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
Author
Owner

WARD-OUTCOME: done - six read-only forgejo finder/list leaves (users/topics search, commits/branches/tags/org-members list) landed on main, lock + docs regenerated.

This one was mostly mechanical once I trusted the toolchain: add can leaves to the curated guardfile, let make build-ward-kdl fetch the live swagger and prune the new paths into the lock. The honest surprise was the convention resolver — list branch quietly resolved to branch_protections (the shallower branch-prefixed match) and list commit to the get-shaped repoGetAllCommits, so both needed op pins. --dry-run was the hero here: it let me confirm every URL and prove the coily* owner gate still rejects non-coily owners without needing live SSM credentials (which this container can't reach).

The other friction was the gate, not the code: golangci-lint flagged 33 pre-existing Go issues under a newer linter than the repo pins, but my diff touches zero Go files so none are mine; and kdlfmt/trufflehog weren't installed, so I pulled them in (npm + release binary) to run the pre-commit suite for real rather than skip it. Fairly confident in the result — resolution, lock pruning, scope gate, and the full hook suite all verified green. Only rough edge worth noting: I couldn't make a truly authenticated live call (no SSM here), so "returns results" is proven by dry-run + path resolution, not an end-to-end 200.

WARD-OUTCOME: done - six read-only forgejo finder/list leaves (users/topics search, commits/branches/tags/org-members list) landed on main, lock + docs regenerated. This one was mostly mechanical once I trusted the toolchain: add `can` leaves to the curated guardfile, let `make build-ward-kdl` fetch the live swagger and prune the new paths into the lock. The honest surprise was the convention resolver — `list branch` quietly resolved to `branch_protections` (the shallower branch-prefixed match) and `list commit` to the `get`-shaped `repoGetAllCommits`, so both needed `op` pins. `--dry-run` was the hero here: it let me confirm every URL and prove the coily* owner gate still rejects non-coily owners without needing live SSM credentials (which this container can't reach). The other friction was the gate, not the code: golangci-lint flagged 33 pre-existing Go issues under a newer linter than the repo pins, but my diff touches zero Go files so none are mine; and kdlfmt/trufflehog weren't installed, so I pulled them in (npm + release binary) to run the pre-commit suite for real rather than skip it. Fairly confident in the result — resolution, lock pruning, scope gate, and the full hook suite all verified green. Only rough edge worth noting: I couldn't make a truly authenticated live call (no SSM here), so "returns results" is proven by dry-run + path resolution, not an end-to-end 200.
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/ward#368
No description provided.