Commit graph

2 commits

Author SHA1 Message Date
714a2e0413 feat(forge): the reconciler verb surface on both backends
github is the existing gh invocation extracted 1:1 (term 5). forgejo is
/api/v1, and encodes three asymmetries measured against this instance on a
scratch repo — never a live board:

1. Adding labels takes NAMES; removing one takes a numeric ID.
     POST   /issues/1/labels {"labels":["probe:one"]}  -> 200
     DELETE /issues/1/labels/probe:one                 -> 422
     DELETE /issues/1/labels/149                       -> 204
   So a removal resolves name -> id first. gh hides this; the shim cannot.

2. Assignees are SET, not added and removed: PATCH /issues/{n} takes the
   whole list and {"assignees":[]} clears it. --remove-assignee is therefore
   a read-modify-write, not a delete.

3. There is no statusCheckRollup. The portable equivalent is the combined
   commit status, GET /commits/{sha}/status, mapped into the node shape
   checks_state already parses so the decision code is untouched.

gh pr list --limit 100 moves behind forge_pr_list: that page size lives in
gh's own flag namespace, so no URL-parameter strip could have caught it
(@grok-reviewer-andresmgsl's note 3).

Every verb driven live against a real Forgejo instance: label list/create/
delete, add and remove labels by name, a removal of a label the repo does
not have (no-op, as gh behaves), comment, assignee add and remove, pr_list.

Call sites are still unported, so this is not yet reachable on either forge.

Refs #188
2026-08-02 19:13:14 +00:00
ab23a3b1b6 feat(forge): two backends behind one call surface, and the shim owns paging
Term 1's foundation. lib/forge.sh gains forge_select, which sources exactly
one of lib/forge-github.sh or lib/forge-forgejo.sh; both define the same
verbs, so no branching reaches the 61 call sites. The github backend is the
current gh invocation extracted 1:1 — term 5 is kept by making that path
boring.

The page size moves OUT of the call sites and into the backend, because it
is not portable and fails silently. Measured 2026-08-02:

  ?per_page=100   GitHub 100 items   Forgejo 30 items  (ignored)
  ?limit=100      GitHub  30 items   Forgejo 50 items  (capped)

Both answer HTTP 200 with valid JSON. Every call site here is GitHub-shaped,
so a verbatim port would have swept 30 of rig's 137 issues and printed
"reconciled." — criterion 2 failing green, the same failure class as the
blind sweep. Both page_url helpers strip a stray page-size parameter in
either dialect, so a call site cannot reintroduce it by accident.

Forgejo caps a page at 50 whatever is asked, so pagination is mandatory, not
an optimisation. The gather is then PROVEN complete against x-total-count
rather than assumed complete because a loop ended.

@kimi-reviewer-andresmgsl's hardening (#4699): a missing x-total-count is
itself a loud refusal. Header exposure is a server setting, and an assert
that cannot run must not silently pass — that is the failure class
re-entering through the guard built to stop it.

Call sites are not ported yet; that is the next commit.

Refs #188
2026-08-02 19:00:58 +00:00