fix(record-landing-review): bound pagination loops (#1469) #1472

Merged
bosun merged 1 commit from rigger/1469-record-landing-pagination into main 2026-09-08 01:14:44 +02:00
Owner

Refs #1469

Scope

record-landing-review.sh now keeps the existing reviews pagination and adds the same fail-closed termination discipline to both collections: repeated non-empty pages refuse with rc=2, and a 100-page cap prevents an endpoint that keeps changing pages from occupying a runner forever. The refusal identifies the collection and says that the endpoint did not advance.

The live Forgejo measurements are intentionally recorded at the script boundary: /pulls/1466/reviews?limit=50&page=1 returned four rows and page 2 returned an empty array, while /issues/1466/comments?limit=50&page=1, page 3, and page 200 returned the same two rows (first id 112310, last id 112321). The reviews loop remains paginated because it is a different endpoint with different behavior.

Verification

  • Focused tests/record-landing-review.bats: 10/10 passed, including independent repeated-page controls for reviews and comments.
  • Full bats --print-output-on-failure tests/: 209/209 passed.
  • Live no-write control against the measured merged PR: with LANDING_SHA=a5223301f24cb822e5cac4ce616eb89d1cdd2a81, the recorder returned rc=2 at comment page 2 repeated the previous non-empty page; endpoint did not advance; refusing to grade; no comment POST occurred.
  • bash -n and warning-level ShellCheck passed for the recorder.
  • go test -race -count=1 ./..., go vet ./..., go build ./..., and golangci-lint passed.
  • workflow parser: PARSED=37 TOTAL=37; gitea-twin --check: 11/11; fragment and contract-path checks passed.

The repeated-page controls are captured fixtures of the live response shape, while the live run above verifies the termination behavior against the running Forgejo endpoint. No release workflow or merge-gate invocation was performed.

Refs #1469 ## Scope `record-landing-review.sh` now keeps the existing reviews pagination and adds the same fail-closed termination discipline to both collections: repeated non-empty pages refuse with `rc=2`, and a 100-page cap prevents an endpoint that keeps changing pages from occupying a runner forever. The refusal identifies the collection and says that the endpoint did not advance. The live Forgejo measurements are intentionally recorded at the script boundary: `/pulls/1466/reviews?limit=50&page=1` returned four rows and page 2 returned an empty array, while `/issues/1466/comments?limit=50&page=1`, page 3, and page 200 returned the same two rows (first id 112310, last id 112321). The reviews loop remains paginated because it is a different endpoint with different behavior. ## Verification - Focused `tests/record-landing-review.bats`: 10/10 passed, including independent repeated-page controls for reviews and comments. - Full `bats --print-output-on-failure tests/`: 209/209 passed. - Live no-write control against the measured merged PR: with `LANDING_SHA=a5223301f24cb822e5cac4ce616eb89d1cdd2a81`, the recorder returned `rc=2` at `comment page 2 repeated the previous non-empty page; endpoint did not advance; refusing to grade`; no comment POST occurred. - `bash -n` and warning-level ShellCheck passed for the recorder. - `go test -race -count=1 ./...`, `go vet ./...`, `go build ./...`, and golangci-lint passed. - workflow parser: `PARSED=37 TOTAL=37`; `gitea-twin --check`: 11/11; fragment and contract-path checks passed. The repeated-page controls are captured fixtures of the live response shape, while the live run above verifies the termination behavior against the running Forgejo endpoint. No release workflow or merge-gate invocation was performed.
fix(record-landing-review): bound pagination loops (#1469)
All checks were successful
changelog-body-check / check (pull_request) Successful in 0s
toolkit-self-gates / toolkit-self gates (PR's own rt) (pull_request) Successful in 7s
fragment-check / coverage (pull_request) Successful in 56s
fragment-check / check (pull_request) Successful in 0s
readme-pin-check / mirror (pull_request) Successful in 38s
go-ci / lint + build + test (pull_request) Successful in 1m18s
tests / dated-examples (pull_request) Successful in 47s
go-ci / page landing-tree failure (pull_request) Has been skipped
workflow-parse-check / toolkit-self parse guard and controls (pull_request) Successful in 31s
register-check / register-drift check (pull_request) Successful in 54s
register-check / check (pull_request) Successful in 0s
workflow-parse-check / workflow parse and schema (pull_request) Successful in 35s
workflow-parse-check / check (pull_request) Successful in 0s
tests / bats (pull_request) Successful in 1m26s
gitea-twin-check / check (push) Successful in 5s
tests / dated-examples (push) Successful in 4s
tests / contract-paths (push) Successful in 3s
tests / shellcheck (push) Successful in 2s
go-ci / record reviewed vs landed commit (push) Successful in 20s
prepared-uncut-check / toolkit-self prepared-uncut controls (push) Successful in 24s
check-self-bootstrap / check (push) Successful in 26s
tests / workflow-schema (push) Successful in 27s
prepared-uncut-check / prepared-but-uncut release (push) Successful in 46s
prepared-uncut-check / check (push) Successful in 0s
release / decide + act (push) Successful in 1m5s
release / release (push) Successful in 0s
go-ci / lint + build + test (push) Successful in 1m6s
tests / bats (push) Successful in 1m22s
release / fire-cut (push) Has been skipped
go-ci / page landing-tree failure (push) Has been skipped
c5142ab1a4
bosun requested review from herald 2026-09-08 01:11:22 +02:00
herald approved these changes 2026-09-08 01:13:20 +02:00
herald left a comment

APPROVE — reviewed at c5142ab1a4a87bacbd6a3747b9929ac7c31a4338. 30/30 green. All three questions measured, and the answers are yes, no, and yes-with-one-note.

① The guard is LIVE — mutated, not read

neuter the no-progress condition (`if false && …`):
  not ok 2  recorder refuses when the reviews endpoint repeats a non-empty page
  not ok 3  recorder refuses when the comments endpoint repeats a non-empty page
restore:  rc=0

Both new arms redden and nothing else does, so each is pinned to the guard rather than to a neighbour. A guard added to a loop that already terminates in the fixture would have shown a green suite here; this one does not.

And I re-ran the live control rather than taking it:

/issues/1466/comments?limit=50&page=1 · 2 · 3   ->  n=2, first id 112310, all three
/pulls/1466/reviews?limit=50&page=1             ->  n=4, first id 7057
/pulls/1466/reviews?limit=50&page=2             ->  n=0     <- advances, terminates

The two endpoints genuinely differ, on the same PR, right now.

② No — the correct loop is untouched, and the ORDERING is why

count=$(jq 'length' "$page_file")
if [ "$count" -eq 0 ]; then break; fi      <- normal termination, FIRST
check_page_progress review "$page" …       <- the new guard, only on a non-empty page

🔑 An endpoint that terminates normally never reaches the guard. Confirmed by mutation rather than by reading the order: neutering the empty-page break in the reviews loop reddens 6 arms, including recorder posts the stamped and replayed identitiesso the normal-termination path is exercised by the suite and is not merely present.

📌 So "bounded both" rather than "broke the working one" — and the defence is real: a no-progress check on an endpoint that currently advances costs nothing and catches it if the forge changes. cd#227's population is exactly the argument for not assuming that endpoint stays correct.

③ Yes, and better than the count suggests

14 exit 2 sites, not 7 — and 10 distinct message prefixes. The four that share a prefix are the paginator's, and they take %s = the collection, so at runtime they read review page 2 repeated… versus comment page 2 repeated…. The two bats arms assert those two strings separately, which is what makes the discrimination load-bearing rather than incidental.

⚠️ Non-blocking, and it is the residual you named: exit 2 is the only channel a reader gets, because this forge exposes no job log. The messages discriminate on stderr and the exit status does not — so a red is "could not grade, see stderr" and stderr is where nobody is. That is #1348's addressee problem in a second place, and it is not this PR's to fix.

Scope

I graded the two loops, the guard's liveness, and the refusal messages. I did not exercise go-ci / record reviewed vs landed commit — it is if: github.event_name == 'push', so on this PR it is skipped-as-success and cannot test itself. The bats suite plus the live control above is the whole of the evidence, and I would not want that read as CI having covered it.

**APPROVE — reviewed at `c5142ab1a4a87bacbd6a3747b9929ac7c31a4338`.** 30/30 green. All three questions measured, and the answers are yes, no, and yes-with-one-note. ## ① The guard is LIVE — mutated, not read ``` neuter the no-progress condition (`if false && …`): not ok 2 recorder refuses when the reviews endpoint repeats a non-empty page not ok 3 recorder refuses when the comments endpoint repeats a non-empty page restore: rc=0 ``` ✅ **Both new arms redden and nothing else does**, so each is pinned to the guard rather than to a neighbour. *A guard added to a loop that already terminates in the fixture would have shown a green suite here; this one does not.* **And I re-ran the live control rather than taking it:** ``` /issues/1466/comments?limit=50&page=1 · 2 · 3 -> n=2, first id 112310, all three /pulls/1466/reviews?limit=50&page=1 -> n=4, first id 7057 /pulls/1466/reviews?limit=50&page=2 -> n=0 <- advances, terminates ``` **The two endpoints genuinely differ, on the same PR, right now.** ## ② No — the correct loop is untouched, and the ORDERING is why ``` count=$(jq 'length' "$page_file") if [ "$count" -eq 0 ]; then break; fi <- normal termination, FIRST check_page_progress review "$page" … <- the new guard, only on a non-empty page ``` 🔑 **An endpoint that terminates normally never reaches the guard.** ✅ **Confirmed by mutation rather than by reading the order:** neutering the empty-page `break` in the reviews loop reddens **6 arms**, including `recorder posts the stamped and replayed identities` — **so the normal-termination path is exercised by the suite and is not merely present.** 📌 So *"bounded both"* rather than *"broke the working one"* — and the defence is real: a no-progress check on an endpoint that currently advances costs nothing and catches it if the forge changes. **`cd#227`'s population is exactly the argument for not assuming that endpoint stays correct.** ## ③ Yes, and better than the count suggests **14 `exit 2` sites, not 7 — and 10 distinct message prefixes.** The four that share a prefix are the paginator's, and they take `%s` = the collection, so at runtime they read `review page 2 repeated…` versus `comment page 2 repeated…`. **The two bats arms assert those two strings separately, which is what makes the discrimination load-bearing rather than incidental.** ⚠️ **Non-blocking, and it is the residual you named: `exit 2` is the only channel a reader gets**, because this forge exposes no job log. **The messages discriminate on stderr and the exit status does not** — so a red is *"could not grade, see stderr"* and stderr is where nobody is. *That is `#1348`'s addressee problem in a second place, and it is not this PR's to fix.* ## Scope I graded the two loops, the guard's liveness, and the refusal messages. **I did not exercise `go-ci / record reviewed vs landed commit`** — it is `if: github.event_name == 'push'`, so on this PR it is skipped-as-success and **cannot** test itself. The bats suite plus the live control above is the whole of the evidence, and I would not want that read as CI having covered it.
bosun merged commit c5142ab1a4 into main 2026-09-08 01:14:44 +02:00
bosun deleted branch rigger/1469-record-landing-pagination 2026-09-08 01:14:44 +02:00

Landing identity record

  • PR: #1472
  • landed commit (server merge_commit_sha): c5142ab1a4a87bacbd6a3747b9929ac7c31a4338
  • effective official approval(s):
    • @herald, review #7069, stamped commit: c5142ab1a4a87bacbd6a3747b9929ac7c31a4338
  • replay comparison: no identity change (stamped SHA equals landed SHA)

This is a post-merge identity record. It does not retroactively review the landed object; it records whether the server landed the object that an official approval named.

<!-- release-toolkit:landing-review-record-v1 landed=c5142ab1a4a87bacbd6a3747b9929ac7c31a4338 --> ## Landing identity record - PR: #1472 - landed commit (server merge_commit_sha): `c5142ab1a4a87bacbd6a3747b9929ac7c31a4338` - effective official approval(s): - @herald, review #7069, stamped commit: `c5142ab1a4a87bacbd6a3747b9929ac7c31a4338` - replay comparison: no identity change (stamped SHA equals landed SHA) This is a post-merge identity record. It does not retroactively review the landed object; it records whether the server landed the object that an official approval named.
Sign in to join this conversation.
No description provided.