fix: exhaust Forgejo timeline pagination #254

Merged
andres merged 3 commits from build/240-exhaustive-timeline into main 2026-08-24 19:58:11 +00:00

Closes #240

Adds an exhaustive pagination mode for Forgejo timeline reads so attention and ruling ladders see events beyond the endpoint’s dishonest first-page total.

Worklog

  • Claim #240 and post the design of record before branching
  • Create the isolated branch from current origin/main
  • Run the whole-suite clean baseline
  • Add timeline truncation and exhaustive-pagination regressions first
  • Record the tests-only red baseline: 134 passed, 7 failed
  • Implement the exhaustive paginator and timeline-only adoption
  • Re-run the focused backend suite green
  • Add changelog.d/240.md
  • Independently review the pushed diff and close every Important finding
  • Prove compact method refusal, short-page request count, and paired strict header refusal
  • Run the full suite and sanctioned chunked ShellCheck
  • Verify exact diff scope and a clean pushed worktree

Acceptance criteria

  • A 50/50/50/1 timeline with per-page total headers returns all 151 events and exits 0; the unfixed tree returns 50 and exits 0.
  • The newest event in that fixture is asserted directly.
  • Exhaustive pagination terminates on a short final page and on an empty page after an exactly full page.
  • Exhaustive pagination never reads x-total-count; strict pagination still refuses a missing header.
  • Strict pagination retains its missing-header, moving-total, non-array, and short-gather refusals.
  • Only Forgejo forge_timeline adopts exhaustive pagination; the GitHub backend and ladder consumers remain byte-identical to main.
  • No existing assertion is deleted or weakened, and the backend test assertion count rises.
  • The full suite and sanctioned ShellCheck pass at the PR head.

Test evidence

  • Baseline at ca7ce6e: all 31 test files green; forge-backends 134 passed, 0 failed.
  • Tests-only head 8c0f5d5: forge-backends 134 passed, 7 failed. The failures prove first-page truncation, missing newest event, absent empty-page proof, header dependence, and missing mode/method refusals.
  • Focused implementation at 40ebcea: forge-backends 142 passed, 0 failed.
  • Review probes on 40ebcea: compact -XPOST and --method=POST each reached curl and failed their refusal assertions; fixed at 1164640.
  • Final head 1164640: forge-backends 147 passed, 0 failed; all 31 test files pass; sanctioned ShellCheck linted 64 tracked scripts clean; git diff --check clean.
  • Exact diff: changelog.d/240.md, lib/forge-forgejo.sh, and test/forge-backends.test.sh. lib/forge-github.sh, lib/attention.sh, and lib/ruling.sh are byte-identical to origin/main.
  • Live control re-read on 2026-08-24 at 18:12Z: crew!96 has grown to 162 events (50/50/50/12/0). The separate duty harness returns all 162, but this repo’s unfixed backend at base ca7ce6e returns 50 of 162 and exits 0; the endpoint still truncates the live item. The committed 151-event dishonest-header fixture preserves the historical failure shape deterministically.

Round log

Round at 1164640a

Round passed with no written reply.

Closes #240 Adds an exhaustive pagination mode for Forgejo timeline reads so attention and ruling ladders see events beyond the endpoint’s dishonest first-page total. ## Worklog - [x] Claim #240 and post the design of record before branching - [x] Create the isolated branch from current origin/main - [x] Run the whole-suite clean baseline - [x] Add timeline truncation and exhaustive-pagination regressions first - [x] Record the tests-only red baseline: 134 passed, 7 failed - [x] Implement the exhaustive paginator and timeline-only adoption - [x] Re-run the focused backend suite green - [x] Add changelog.d/240.md - [x] Independently review the pushed diff and close every Important finding - [x] Prove compact method refusal, short-page request count, and paired strict header refusal - [x] Run the full suite and sanctioned chunked ShellCheck - [x] Verify exact diff scope and a clean pushed worktree ## Acceptance criteria - [x] A 50/50/50/1 timeline with per-page total headers returns all 151 events and exits 0; the unfixed tree returns 50 and exits 0. - [x] The newest event in that fixture is asserted directly. - [x] Exhaustive pagination terminates on a short final page and on an empty page after an exactly full page. - [x] Exhaustive pagination never reads x-total-count; strict pagination still refuses a missing header. - [x] Strict pagination retains its missing-header, moving-total, non-array, and short-gather refusals. - [x] Only Forgejo forge_timeline adopts exhaustive pagination; the GitHub backend and ladder consumers remain byte-identical to main. - [x] No existing assertion is deleted or weakened, and the backend test assertion count rises. - [x] The full suite and sanctioned ShellCheck pass at the PR head. ## Test evidence - Baseline at ca7ce6e: all 31 test files green; forge-backends 134 passed, 0 failed. - Tests-only head 8c0f5d5: forge-backends 134 passed, 7 failed. The failures prove first-page truncation, missing newest event, absent empty-page proof, header dependence, and missing mode/method refusals. - Focused implementation at 40ebcea: forge-backends 142 passed, 0 failed. - Review probes on 40ebcea: compact -XPOST and --method=POST each reached curl and failed their refusal assertions; fixed at 1164640. - Final head 1164640: forge-backends 147 passed, 0 failed; all 31 test files pass; sanctioned ShellCheck linted 64 tracked scripts clean; git diff --check clean. - Exact diff: changelog.d/240.md, lib/forge-forgejo.sh, and test/forge-backends.test.sh. lib/forge-github.sh, lib/attention.sh, and lib/ruling.sh are byte-identical to origin/main. - Live control re-read on 2026-08-24 at 18:12Z: crew!96 has grown to 162 events (50/50/50/12/0). The separate duty harness returns all 162, but this repo’s unfixed backend at base ca7ce6e returns 50 of 162 and exits 0; the endpoint still truncates the live item. The committed 151-event dishonest-header fixture preserves the historical failure shape deterministically. ## Round log <!-- round:1164640a08dffde93f1516275f0d880aa389a71a --> **Round at 1164640a** _Round passed with no written reply._
codex-bot-andresmgsl added 1 commit 2026-08-24 17:51:46 +00:00
test: expose truncated Forgejo timelines
Some checks failed
CI / test (pull_request) Failing after 3m45s
CI / release-exercise (pull_request) Successful in 24s
CI / self-guards (pull_request) Successful in 20s
CI / action-exercise (pull_request) Successful in 20s
CI / docs-sync-exercise (pull_request) Successful in 20s
Refs guard / refs-not-closing (pull_request) Successful in 20s
labels / labels (pull_request) Successful in 22s
8c0f5d53d7
codex-bot-andresmgsl added 1 commit 2026-08-24 17:53:25 +00:00
fix: exhaust Forgejo timeline pagination
All checks were successful
CI / test (pull_request) Successful in 3m46s
CI / release-exercise (pull_request) Successful in 25s
CI / self-guards (pull_request) Successful in 21s
CI / action-exercise (pull_request) Successful in 20s
CI / docs-sync-exercise (pull_request) Successful in 20s
labels / labels (pull_request) Successful in 22s
Refs guard / refs-not-closing (pull_request) Successful in 20s
40ebcea462
codex-bot-andresmgsl added 1 commit 2026-08-24 18:01:15 +00:00
test: close exhaustive pagination review gaps
All checks were successful
CI / test (pull_request) Successful in 4m8s
CI / release-exercise (pull_request) Successful in 25s
CI / self-guards (pull_request) Successful in 21s
CI / action-exercise (pull_request) Successful in 20s
CI / docs-sync-exercise (pull_request) Successful in 20s
labels / labels (pull_request) Successful in 22s
Refs guard / refs-not-closing (pull_request) Successful in 20s
1164640a08
Author
Member

Initial build round answered whole at head 1164640a08.

  • Added a mutually exclusive --paginate-exhaustive GET mode that shares Forgejo page URL construction, HTTP and array validation, ordered accumulation, and one final jq application while deliberately ignoring x-total-count.
  • Switched only Forgejo forge_timeline to exhaustive pagination and documented the measured dishonest-header behavior plus the append-only safety argument.
  • Added red-first coverage for 151-event truncation and newest-event loss, short-page and exact-multiple termination, headerless exhaustive reads paired with strict refusal, split and compact non-GET refusal, and single-call-site scope.
  • Preserved strict pagination’s missing-header, moving-total, non-array, and incomplete-gather refusals; GitHub, attention, and ruling files are unchanged.
  • Added changelog.d/240.md.
  • Independent review found three Important proof/parsing gaps; all were reproduced and fixed before handoff.
  • Verified at this head: forge-backends 147/147, all 31 test files, sanctioned ShellCheck over 64 tracked scripts, git diff --check, exact three-file diff, clean worktree, and remote-head equality.
  • Current live evidence is recorded honestly in the PR body: crew!96 now carries 162 events and no longer reproduces the old strict truncation, while the deterministic 151-event regression pins the incident shape.

Every acceptance criterion and Worklog item is checked; no point is outstanding. I have not requested or re-requested the panel.

Initial build round answered whole at head 1164640a08dffde93f1516275f0d880aa389a71a. - Added a mutually exclusive --paginate-exhaustive GET mode that shares Forgejo page URL construction, HTTP and array validation, ordered accumulation, and one final jq application while deliberately ignoring x-total-count. - Switched only Forgejo forge_timeline to exhaustive pagination and documented the measured dishonest-header behavior plus the append-only safety argument. - Added red-first coverage for 151-event truncation and newest-event loss, short-page and exact-multiple termination, headerless exhaustive reads paired with strict refusal, split and compact non-GET refusal, and single-call-site scope. - Preserved strict pagination’s missing-header, moving-total, non-array, and incomplete-gather refusals; GitHub, attention, and ruling files are unchanged. - Added changelog.d/240.md. - Independent review found three Important proof/parsing gaps; all were reproduced and fixed before handoff. - Verified at this head: forge-backends 147/147, all 31 test files, sanctioned ShellCheck over 64 tracked scripts, git diff --check, exact three-file diff, clean worktree, and remote-head equality. - Current live evidence is recorded honestly in the PR body: crew!96 now carries 162 events and no longer reproduces the old strict truncation, while the deterministic 151-event regression pins the incident shape. Every acceptance criterion and Worklog item is checked; no point is outstanding. I have not requested or re-requested the panel.
Author
Member

📣 round answered at head 1164640a08

📣 round answered at head 1164640a08dffde93f1516275f0d880aa389a71a
codex-bot-andresmgsl changed title from WIP: fix: exhaust Forgejo timeline pagination to fix: exhaust Forgejo timeline pagination 2026-08-24 18:03:58 +00:00
Author
Member

Attention demand answered on unchanged head 1164640a08.

  • Agree: the prior final Test-evidence bullet conflated the separate duty harness with this repo’s backend and incorrectly retracted a live reproduction.
  • Corrected: the bullet now preserves the 162-event and harness results, and states that this repo’s unfixed backend at base ca7ce6e returns 50 of 162 with exit 0 as of 18:12Z.
  • Unchanged: code, tests, fixture, branch head, and reviewer requests. No panel re-request was made.
Attention demand answered on unchanged head 1164640a08dffde93f1516275f0d880aa389a71a. - Agree: the prior final Test-evidence bullet conflated the separate duty harness with this repo’s backend and incorrectly retracted a live reproduction. - Corrected: the bullet now preserves the 162-event and harness results, and states that this repo’s unfixed backend at base ca7ce6e returns 50 of 162 with exit 0 as of 18:12Z. - Unchanged: code, tests, fixture, branch head, and reviewer requests. No panel re-request was made.
Author
Member

📣 round answered at head 1164640a08

📣 round answered at head 1164640a08dffde93f1516275f0d880aa389a71a
forgejo-actions added the
state:addressing
label 2026-08-24 18:28:31 +00:00
forgejo-actions added the
scope:release-flow
label 2026-08-24 18:37:39 +00:00
codex-bot-andresmgsl requested review from glm-bot-andresmgsl 2026-08-24 19:10:49 +00:00
codex-bot-andresmgsl requested review from claude-bot-andresmgsl 2026-08-24 19:10:50 +00:00
codex-bot-andresmgsl requested review from kimi-bot-andresmgsl 2026-08-24 19:10:50 +00:00
codex-bot-andresmgsl added the
state:bots-reviewing
label 2026-08-24 19:10:51 +00:00

🔎 reviewing head 1164640a08

  • Verified scope against merge-base ca7ce6e: only changelog.d/240.md, lib/forge-forgejo.sh, test/forge-backends.test.sh changed.
  • Verified lib/forge-github.sh, lib/attention.sh, lib/ruling.sh are byte-identical to main.
  • forge-backends.test.sh: 147 passed, 0 failed.
  • Sanctioned shellcheck over 64 tracked scripts: clean.
  • Full test/run.sh: 27 files passed, 4 failed (changelog-armed, drill-recorded, facts, version); all failures are pre-existing node/npm absence on this box, reproduced on main at 46458ba.
  • Regression probe: reverting forge_timeline to --paginate made the new exhaustive-pagination tests fail (142 passed, 5 failed), confirming the tests guard the fix.
  • No existing assertions deleted; assertion count rises. Changelog fragment shape matches grouped sentinel.
🔎 reviewing head 1164640a08dffde93f1516275f0d880aa389a71a - Verified scope against merge-base ca7ce6e: only changelog.d/240.md, lib/forge-forgejo.sh, test/forge-backends.test.sh changed. - Verified lib/forge-github.sh, lib/attention.sh, lib/ruling.sh are byte-identical to main. - forge-backends.test.sh: 147 passed, 0 failed. - Sanctioned shellcheck over 64 tracked scripts: clean. - Full test/run.sh: 27 files passed, 4 failed (changelog-armed, drill-recorded, facts, version); all failures are pre-existing node/npm absence on this box, reproduced on main at 46458ba. - Regression probe: reverting forge_timeline to --paginate made the new exhaustive-pagination tests fail (142 passed, 5 failed), confirming the tests guard the fix. - No existing assertions deleted; assertion count rises. Changelog fragment shape matches grouped sentinel.

🔎 reviewing head 1164640a08

  • all 9 acceptance criteria verified by running: suite 31/31 green (backend 147/0), shellcheck 64 clean, grep counts 5 and 1, consumers byte-identical to main, checks 134->147
  • mutation evidence: --paginate restored at call site reds 5 assertions; red-first reproduced at 8c0f5d5 (134/7)
  • live control re-run: crew!96 head-exhaustive=162 rc=0 vs main-strict=50 rc=0
  • verdict: approve (non-blocking nits only), submitting
🔎 reviewing head 1164640a08dffde93f1516275f0d880aa389a71a - all 9 acceptance criteria verified by running: suite 31/31 green (backend 147/0), shellcheck 64 clean, grep counts 5 and 1, consumers byte-identical to main, checks 134->147 - mutation evidence: --paginate restored at call site reds 5 assertions; red-first reproduced at 8c0f5d5 (134/7) - live control re-run: crew!96 head-exhaustive=162 rc=0 vs main-strict=50 rc=0 - verdict: approve (non-blocking nits only), submitting

🔎 reviewing head 1164640a08

  • diff scope verified: changelog.d/240.md, lib/forge-forgejo.sh, test/forge-backends.test.sh; forge-github.sh / attention.sh / ruling.sh byte-identical to main
  • AC 1–2 verified by independent probe: base lib 50 events exit 0, head lib 151 events exit 0 in 4 requests, newest event present
  • AC 3–6 verified: short-page and empty-page termination, headerless exhaustive vs strict refusal, four strict refusals still fire, grep count pinned at 5 / one call site
  • AC 7–9: 134 → 147 assertions, full suite 29/31 files green here (labels-dispatch + labels-triggers fail only on this box's missing yq), repo shellcheck-all exit 0 over 64 scripts, forge CI green at head
  • mutation check: restoring --paginate in forge_timeline reds the truncation cases
  • verdict next
🔎 reviewing head 1164640a08dffde93f1516275f0d880aa389a71a - diff scope verified: changelog.d/240.md, lib/forge-forgejo.sh, test/forge-backends.test.sh; forge-github.sh / attention.sh / ruling.sh byte-identical to main - AC 1–2 verified by independent probe: base lib 50 events exit 0, head lib 151 events exit 0 in 4 requests, newest event present - AC 3–6 verified: short-page and empty-page termination, headerless exhaustive vs strict refusal, four strict refusals still fire, grep count pinned at 5 / one call site - AC 7–9: 134 → 147 assertions, full suite 29/31 files green here (labels-dispatch + labels-triggers fail only on this box's missing `yq`), repo shellcheck-all exit 0 over 64 scripts, forge CI green at head - mutation check: restoring `--paginate` in forge_timeline reds the truncation cases - verdict next
glm-bot-andresmgsl approved these changes 2026-08-24 19:23:38 +00:00
glm-bot-andresmgsl left a comment
Member

Approving head 1164640a08.

Reviewed the whole PR against #240's acceptance criteria, ceremony's load-bearing constraints, then the code. Everything below was run in a detached throwaway worktree at the head; nothing runnable was opined.

Acceptance criteria — each settled by running it:

  • 4-page 50/50/50/1 fixture with per-page x-total-count: forge_timeline returns 151 events, exit 0. The unfixed tree's number reproduced two ways: tests-only head 8c0f5d5 re-run by me → 134 passed, 7 failed (the recorded red baseline, exactly), and the live control below shows the truncation on the real endpoint.
  • Newest event asserted on the event itself (event-151 via any()), not on the array length.
  • Termination asserted as two cases: short final page (4 curl calls, no extra empty read) and empty page after an exactly-full gather (100 events over 3 calls).
  • Exhaustive never reads x-total-count: a header-less stub returns all 51 events; the same stub on --paginate still refuses with the #4699 message. Both asserted, both green — the exemption is proven scoped, not assumed.
  • Strict path's four refusals still asserted and still firing: missing header, moving total, non-array body (object and scalar variants), short gather.
  • grep -c 'paginate-exhaustive' lib/forge-forgejo.sh = 5 (two doc comments, parser arm, mutual-exclusion message, the timeline call) and forge_api --paginate-exhaustive = 1 — no third call site, verified live.
  • lib/forge-github.sh, lib/attention.sh, lib/ruling.sh byte-identical to main 46458ba (git diff --quiet).
  • No assertion deleted or weakened — the only removed lines are two harness lines inside timeline_stub; the file's check count rises 134 → 147.
  • Full suite 31/31 test files green at the head; the sanctioned shellcheck-all.sh lints 64 tracked scripts clean, exit 0.

Mutation evidence per the issue's test plan: restoring --paginate in forge_timeline alone (in the throwaway worktree, reverted after) reds 5 assertions — the call-site change is load-bearing, not masked by the flag existing.

Live control re-run today against this repo's own backend on crew!96 (162 events now): head's exhaustive walk returns 162, rc=0; current main's strict walk returns 50, rc=0. The defect and the fix are both demonstrated live, not just in fixtures.

Load-bearing constraints: the #188/#4699/#4712 guards are untouched on the strict path; the exemption is scoped to the one measured-dishonest endpoint with the measurement recorded in the comment (limit=10→10, 50→50, crew!96 151-vs-50, append-only rationale), exactly as the spec directs. No workflow, action, or token surface changes.

Non-blocking nits, builder's discretion:

  • The exhaustive arm's [ "$n" -eq 50 ] hard-codes the page size that forgejo_page_url also hard-codes; the two must move together. A shared constant would make that coupling explicit, though it matches the file's existing style.
  • --paginate-exhaustive has no page-count bound; a server forever returning exactly 50 rows would loop. Inherent to exhaustion as specified and sound for an append-only timeline — noted so the next reader knows it was considered.

Could not verify, and what I relied on instead:

  • CI on the forge runner (no runner access from this box). I ran the same gates locally — the full suite and the repo's own shellcheck-all — plus the live forge control, so nothing rests on CI.
  • The upstream Forgejo header bug's generality beyond this instance is taken from the issue's per-endpoint measurements; I re-confirmed the timeline endpoint's behavior live today as described above.
Approving head 1164640a08dffde93f1516275f0d880aa389a71a. Reviewed the whole PR against #240's acceptance criteria, ceremony's load-bearing constraints, then the code. Everything below was run in a detached throwaway worktree at the head; nothing runnable was opined. Acceptance criteria — each settled by running it: - 4-page 50/50/50/1 fixture with per-page x-total-count: `forge_timeline` returns 151 events, exit 0. The unfixed tree's number reproduced two ways: tests-only head 8c0f5d5 re-run by me → 134 passed, 7 failed (the recorded red baseline, exactly), and the live control below shows the truncation on the real endpoint. - Newest event asserted on the event itself (`event-151` via `any()`), not on the array length. - Termination asserted as two cases: short final page (4 curl calls, no extra empty read) and empty page after an exactly-full gather (100 events over 3 calls). - Exhaustive never reads x-total-count: a header-less stub returns all 51 events; the same stub on `--paginate` still refuses with the #4699 message. Both asserted, both green — the exemption is proven scoped, not assumed. - Strict path's four refusals still asserted and still firing: missing header, moving total, non-array body (object and scalar variants), short gather. - `grep -c 'paginate-exhaustive' lib/forge-forgejo.sh` = 5 (two doc comments, parser arm, mutual-exclusion message, the timeline call) and `forge_api --paginate-exhaustive` = 1 — no third call site, verified live. - `lib/forge-github.sh`, `lib/attention.sh`, `lib/ruling.sh` byte-identical to main 46458ba (`git diff --quiet`). - No assertion deleted or weakened — the only removed lines are two harness lines inside `timeline_stub`; the file's check count rises 134 → 147. - Full suite 31/31 test files green at the head; the sanctioned `shellcheck-all.sh` lints 64 tracked scripts clean, exit 0. Mutation evidence per the issue's test plan: restoring `--paginate` in `forge_timeline` alone (in the throwaway worktree, reverted after) reds 5 assertions — the call-site change is load-bearing, not masked by the flag existing. Live control re-run today against this repo's own backend on crew!96 (162 events now): head's exhaustive walk returns 162, rc=0; current main's strict walk returns 50, rc=0. The defect and the fix are both demonstrated live, not just in fixtures. Load-bearing constraints: the #188/#4699/#4712 guards are untouched on the strict path; the exemption is scoped to the one measured-dishonest endpoint with the measurement recorded in the comment (limit=10→10, 50→50, crew!96 151-vs-50, append-only rationale), exactly as the spec directs. No workflow, action, or token surface changes. Non-blocking nits, builder's discretion: - The exhaustive arm's `[ "$n" -eq 50 ]` hard-codes the page size that `forgejo_page_url` also hard-codes; the two must move together. A shared constant would make that coupling explicit, though it matches the file's existing style. - `--paginate-exhaustive` has no page-count bound; a server forever returning exactly 50 rows would loop. Inherent to exhaustion as specified and sound for an append-only timeline — noted so the next reader knows it was considered. Could not verify, and what I relied on instead: - CI on the forge runner (no runner access from this box). I ran the same gates locally — the full suite and the repo's own shellcheck-all — plus the live forge control, so nothing rests on CI. - The upstream Forgejo header bug's generality beyond this instance is taken from the issue's per-endpoint measurements; I re-confirmed the timeline endpoint's behavior live today as described above.
kimi-bot-andresmgsl approved these changes 2026-08-24 19:25:56 +00:00
kimi-bot-andresmgsl left a comment
Member

Approve. The fix correctly scopes the exemption to the one measured-liar endpoint while keeping the header-bound proof everywhere else.

What I verified:

  • Diff scope against merge-base ca7ce6e is exactly the three files the PR claims: changelog.d/240.md, lib/forge-forgejo.sh, test/forge-backends.test.sh.
  • lib/forge-github.sh, lib/attention.sh, and lib/ruling.sh are byte-identical to main.
  • test/forge-backends.test.sh passes 147/147, including the new 151-event truncation regression, newest-event retention, short-page and empty-page termination, missing-header exemption for exhaustive pagination, and continued strict-pagination refusals.
  • Sanctioned shellcheck over 64 tracked scripts is clean.
  • Reverting forge_timeline to --paginate locally made the new tests fail (142 passed, 5 failed), so the tests actually guard the fix.
  • Changelog fragment uses the repo's grouped shape.

What I could not verify:

  • test/run.sh reports 4 failing files (changelog-armed, drill-recorded, facts, version). Every failure is the package-json path requiring node/npm, which is absent on this box. I reproduced the same failures on main at 46458ba, so they are pre-existing and unrelated to this PR.

Note: the PR branch is based on ca7ce6e while main has advanced to 46458ba, but git merge-tree shows no conflicts and the touched files do not overlap with the main-branch commits, so the merge would be clean.

Approve. The fix correctly scopes the exemption to the one measured-liar endpoint while keeping the header-bound proof everywhere else. What I verified: - Diff scope against merge-base ca7ce6e is exactly the three files the PR claims: changelog.d/240.md, lib/forge-forgejo.sh, test/forge-backends.test.sh. - lib/forge-github.sh, lib/attention.sh, and lib/ruling.sh are byte-identical to main. - test/forge-backends.test.sh passes 147/147, including the new 151-event truncation regression, newest-event retention, short-page and empty-page termination, missing-header exemption for exhaustive pagination, and continued strict-pagination refusals. - Sanctioned shellcheck over 64 tracked scripts is clean. - Reverting forge_timeline to --paginate locally made the new tests fail (142 passed, 5 failed), so the tests actually guard the fix. - Changelog fragment uses the repo's grouped shape. What I could not verify: - test/run.sh reports 4 failing files (changelog-armed, drill-recorded, facts, version). Every failure is the package-json path requiring node/npm, which is absent on this box. I reproduced the same failures on main at 46458ba, so they are pre-existing and unrelated to this PR. Note: the PR branch is based on ca7ce6e while main has advanced to 46458ba, but git merge-tree shows no conflicts and the touched files do not overlap with the main-branch commits, so the merge would be clean.
claude-bot-andresmgsl approved these changes 2026-08-24 19:30:59 +00:00
claude-bot-andresmgsl left a comment
Member

Approve at 1164640a08dffde93f1516275f0d880aa389a71a. Every acceptance criterion on #240 holds, and I re-derived the headline ones myself rather than reading the worklog.

What I verified, and how

Reviewed in a detached worktree at the head.

  • Diff scope. git diff origin/main...HEAD is exactly changelog.d/240.md, lib/forge-forgejo.sh, test/forge-backends.test.sh. lib/forge-github.sh, lib/attention.sh and lib/ruling.sh are byte-identical to main (git diff --quiet origin/main HEAD -- <file> for each). AC 7 ✔
  • AC 1 and 2, independently. I built my own 50/50/50/1 stub outside the suite, with each page declaring x-total-count equal to its own row count, and drove forge_timeline against the base library (git show ca7ce6e:lib/forge-forgejo.sh) and against the head library:
    • base: events=50 rc=0 requests=1, newest_present=false
    • head: events=151 rc=0 requests=4, newest_present=true
  • AC 3–5. Both termination cases assert the request count, not just the length (4 reads for the short final page, 3 for the exactly-full-then-empty case). The headerless fixture returns 51 under --paginate-exhaustive and is refused with the #4699 message under --paginate — the same fixture, both directions, so the exemption is proven scoped. All four strict refusals still run and pass at the head: short gather, missing header, moving total, non-array/scalar body. ✔
  • AC 6. grep -c 'paginate-exhaustive' lib/forge-forgejo.sh = 5: doc header, doc paragraph, parser arm, exclusivity message, one call site — and grep -c 'forge_api --paginate-exhaustive' = 1. Both pinned by tests. ✔
  • AC 8. Base file at ca7ce6e: 134 passed, 0 failed. Head: 147 passed, 0 failed. No assertion is deleted; timeline_stub gains multi-page and header-suppression behaviour without weakening the four existing projection cases, which still run off the unchanged FAKE_TIMELINE/FAKE_TL_N defaults. ✔
  • Mutation evidence the issue asked for. Restoring --paginate in forge_timeline alone reds the truncation case (forge_timeline exhausts all pages…), the newest-event case, and the short-page request-count case — 142 passed, 5 failed. Running the head's test file against the base library reds 12. The call-site change is load-bearing, not masked by the flag merely existing. ✔
  • AC 9. bash .github/scripts/shellcheck-all.sh → exit 0, 64 tracked scripts, no findings. Full suite here: 29 of 31 files green.

What I could not verify, and what I relied on instead

  • test/labels-dispatch.test.sh and test/labels-triggers.test.sh fail on my boxyq: command not found, nothing to do with this diff (neither file nor any workflow YAML is touched). I relied on the forge's own CI at this head, which is green across all seven contexts including CI / test and CI / self-guards.
  • No live control run. I did not re-read heavy-duty/crew pull 96 against this tree; the committed dishonest-header fixture plus my own out-of-suite probe are what I verified the behaviour on. actionlint is also absent here, so the workflow lint is CI's word.

Non-blocking nits

  1. The page size is now a constant in two places. forgejo_page_url owns limit=50 under a comment saying no call site names a page size; the exhaustive walk then re-states it as [ "$n" -eq 50 ] || break. If that limit ever moves — or an instance whose MAX_RESPONSE_ITEMS is below 50 serves shorter pages — the walk breaks after page 1 and exits 0, with no header check left to catch it: the exact failure this PR removes, arriving through the fix. A forgejo_page_size helper both call, or [ "$n" -eq "$(forgejo_page_size)" ], would tie them together. Short-page termination itself is what the issue specifies, so I am not asking you to walk to an empty page always.
  2. -X/--method is parsed but never applied. Outside the pagination refusal, forge_api -X DELETE repos/o/r/issues/1/labels/5 now issues a plain GET of that path and returns rc 0. The trap pre-exists — base does the same for the endpoint-first argument order, and lib/forge-github.sh drops -X too — but the head makes it uniform across both orders, and a parser arm for a flag the backend does not implement invites a future caller to use it. Since forge_api is GET-only by construction, a blanket [ "$method" = GET ] || refuse would subsume the current pagination-scoped check and keep the asserted "GET" substring.
  3. One new check writes its own verdict to /dev/null. check "forge_timeline retains the newest event beyond page one" … <<<"$tl" >/dev/null — the redirection lands on check, not on jq, so the ok:/FAIL: line is discarded. Measured under mutation: the file printed 4 FAIL lines and summarised 5 failed. The counter and exit status are correct, so CI still reds; only the diagnostic disappears. It copies the idiom two lines above it, so this is the file's habit rather than yours.

One scope note, for triage rather than this PR

#240 states that forge_timeline is the only caller of the timeline endpoint. At this head it is not: actions/issueflow-reconcile/issueflow-reconcile.sh reads repos/$REPO/issues/$1/timeline twice more under strict --paginateoffsite_timeline, and the assigned-event read inside issue_activity_at that the staleness reclaim measures from. Those keep the truncation on any item past 50 events. The issue deliberately scoped adoption to forge_timeline and made "no third call site" a criterion, so changing them here would be wrong and I am not asking for it. It belongs in a discussion, and I cannot mint the issue.

**Approve** at `1164640a08dffde93f1516275f0d880aa389a71a`. Every acceptance criterion on #240 holds, and I re-derived the headline ones myself rather than reading the worklog. ## What I verified, and how Reviewed in a detached worktree at the head. - **Diff scope.** `git diff origin/main...HEAD` is exactly `changelog.d/240.md`, `lib/forge-forgejo.sh`, `test/forge-backends.test.sh`. `lib/forge-github.sh`, `lib/attention.sh` and `lib/ruling.sh` are byte-identical to `main` (`git diff --quiet origin/main HEAD -- <file>` for each). AC 7 ✔ - **AC 1 and 2, independently.** I built my own 50/50/50/1 stub outside the suite, with each page declaring `x-total-count` equal to its own row count, and drove `forge_timeline` against the base library (`git show ca7ce6e:lib/forge-forgejo.sh`) and against the head library: - base: `events=50 rc=0 requests=1`, `newest_present=false` - head: `events=151 rc=0 requests=4`, `newest_present=true` - **AC 3–5.** Both termination cases assert the request count, not just the length (4 reads for the short final page, 3 for the exactly-full-then-empty case). The headerless fixture returns 51 under `--paginate-exhaustive` and is refused with the #4699 message under `--paginate` — the same fixture, both directions, so the exemption is proven scoped. All four strict refusals still run and pass at the head: short gather, missing header, moving total, non-array/scalar body. ✔ - **AC 6.** `grep -c 'paginate-exhaustive' lib/forge-forgejo.sh` = 5: doc header, doc paragraph, parser arm, exclusivity message, one call site — and `grep -c 'forge_api --paginate-exhaustive'` = 1. Both pinned by tests. ✔ - **AC 8.** Base file at `ca7ce6e`: 134 passed, 0 failed. Head: 147 passed, 0 failed. No assertion is deleted; `timeline_stub` gains multi-page and header-suppression behaviour without weakening the four existing projection cases, which still run off the unchanged `FAKE_TIMELINE`/`FAKE_TL_N` defaults. ✔ - **Mutation evidence the issue asked for.** Restoring `--paginate` in `forge_timeline` alone reds the truncation case (`forge_timeline exhausts all pages…`), the newest-event case, and the short-page request-count case — 142 passed, 5 failed. Running the head's test file against the base library reds 12. The call-site change is load-bearing, not masked by the flag merely existing. ✔ - **AC 9.** `bash .github/scripts/shellcheck-all.sh` → exit 0, 64 tracked scripts, no findings. Full suite here: 29 of 31 files green. ## What I could not verify, and what I relied on instead - **`test/labels-dispatch.test.sh` and `test/labels-triggers.test.sh` fail on my box** — `yq: command not found`, nothing to do with this diff (neither file nor any workflow YAML is touched). I relied on the forge's own CI at this head, which is green across all seven contexts including `CI / test` and `CI / self-guards`. - **No live control run.** I did not re-read `heavy-duty/crew` pull 96 against this tree; the committed dishonest-header fixture plus my own out-of-suite probe are what I verified the behaviour on. `actionlint` is also absent here, so the workflow lint is CI's word. ## Non-blocking nits 1. **The page size is now a constant in two places.** `forgejo_page_url` owns `limit=50` under a comment saying no call site names a page size; the exhaustive walk then re-states it as `[ "$n" -eq 50 ] || break`. If that limit ever moves — or an instance whose `MAX_RESPONSE_ITEMS` is below 50 serves shorter pages — the walk breaks after page 1 and exits 0, with no header check left to catch it: the exact failure this PR removes, arriving through the fix. A `forgejo_page_size` helper both call, or `[ "$n" -eq "$(forgejo_page_size)" ]`, would tie them together. Short-page termination itself is what the issue specifies, so I am not asking you to walk to an empty page always. 2. **`-X`/`--method` is parsed but never applied.** Outside the pagination refusal, `forge_api -X DELETE repos/o/r/issues/1/labels/5` now issues a plain GET of that path and returns rc 0. The trap pre-exists — base does the same for the endpoint-first argument order, and `lib/forge-github.sh` drops `-X` too — but the head makes it uniform across both orders, and a parser arm for a flag the backend does not implement invites a future caller to use it. Since `forge_api` is GET-only by construction, a blanket `[ "$method" = GET ] || refuse` would subsume the current pagination-scoped check and keep the asserted "GET" substring. 3. **One new check writes its own verdict to `/dev/null`.** `check "forge_timeline retains the newest event beyond page one" … <<<"$tl" >/dev/null` — the redirection lands on `check`, not on `jq`, so the `ok:`/`FAIL:` line is discarded. Measured under mutation: the file printed 4 `FAIL` lines and summarised `5 failed`. The counter and exit status are correct, so CI still reds; only the diagnostic disappears. It copies the idiom two lines above it, so this is the file's habit rather than yours. ## One scope note, for triage rather than this PR #240 states that `forge_timeline` is the only caller of the timeline endpoint. At this head it is not: `actions/issueflow-reconcile/issueflow-reconcile.sh` reads `repos/$REPO/issues/$1/timeline` twice more under strict `--paginate` — `offsite_timeline`, and the `assigned`-event read inside `issue_activity_at` that the staleness reclaim measures from. Those keep the truncation on any item past 50 events. The issue deliberately scoped adoption to `forge_timeline` and made "no third call site" a criterion, so changing them here would be wrong and I am not asking for it. It belongs in a discussion, and I cannot mint the issue.
forgejo-actions added
state:needs-human
and removed
state:addressing
state:bots-reviewing
labels 2026-08-24 19:31:55 +00:00
codex-bot-andresmgsl requested review from andres 2026-08-24 19:35:56 +00:00
andres merged commit a1bac15a8b into main 2026-08-24 19:58:11 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
4 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: heavy-duty/ceremony#254
No description provided.