All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
Round 2 remediation for the read-back verification in issue-comment.sh and pr-review.sh. BLOCKER A (invocation attribution): id-above-boundary + content/state match only proves temporal ordering — a concurrent write from a different identity could satisfy it while this tea invocation created nothing. Both wrappers now resolve the acting identity once via curl GET /api/v1/user and additionally require the accepted record's author login to equal that identity. Residual same-identity same-body/state concurrency is documented in-code (tea 0.11.1 emits no reliable created-record id to close it further). BLOCKER B (pagination): the comments and reviews list reads now walk every page (?limit=&page=1,2,… until a short/empty page) for both the pre-write boundary and the post-write read-back, so a record beyond page 1 is still found. Adds regressions: concurrent different-identity write fails closed (comments and reviews); a matching review beyond page 1 is still found. README updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
27 lines
5.1 KiB
Markdown
27 lines
5.1 KiB
Markdown
# Git provider wrappers
|
|
|
|
These scripts provide host-aware GitHub and Gitea issue, pull-request, milestone, and CI operations.
|
|
|
|
## Durable review provenance
|
|
|
|
A successful provider write command—or a wrapper message based only on that command's exit code—is **not** durable review provenance. Review comments, approvals, and change requests count as durable provenance only after the wrapper reads the created provider record back and verifies that it was created by _this_ write.
|
|
|
|
`pr-review.sh` therefore fails closed when a Gitea comment cannot be written, its created comment ID cannot be identified, or provider read-back does not match. It reports comment success only after that read-back verification passes. The `approve` and `request-changes` actions apply the same discipline to the review **state** itself: they record the maximum existing review id _before_ invoking `tea pr approve`/`reject`, then require a review whose id is strictly greater than that boundary, whose **author login equals the acting identity** (resolved via `GET /api/v1/user` for the token in use), whose state matches the requested action (`APPROVED` / `REQUEST_CHANGES`), and whose reviewed commit equals the PR's current head. tea's exit code alone is never treated as evidence the review landed.
|
|
|
|
`issue-comment.sh` applies the same fail-closed, boundary-bounded read-back to issue comments: it records the maximum existing comment id _before_ posting via `tea comment`, then re-fetches the issue's comments via the Gitea REST API and requires a comment whose id is strictly greater than that boundary, **whose author login equals the acting identity**, **and** whose body exactly matches what was submitted. Bounding the read-back by the pre-write id is essential — a body-only match across all history would falsely report success if `tea comment` silently no-ops (the #865 bug) while an identically-bodied comment already existed from a prior run. Gitea comment and review ids are monotonic, so `id > boundary` reliably means "created after this write began".
|
|
|
|
**Invocation attribution, not just temporal ordering.** `id > boundary` alone only proves a record was created after the write began; it would still be satisfied by a _concurrent_ write from a _different_ identity while this `tea` invocation created nothing. Both wrappers therefore additionally require the accepted record's author login to equal the identity the API token authenticates as, narrowing the match to this invocation's writer. The one residual window — a concurrent write by the _same_ identity with an identical body/state inside the boundary window — cannot be eliminated without a tea-emitted created-record id, which tea 0.11.1 does not reliably provide; it is strictly narrower than temporal-only matching and is documented in-code.
|
|
|
|
**Full pagination.** Gitea paginates list endpoints, so a single-page read of the comments or reviews list would false-negative once the freshly created record lands beyond the first page. Both the pre-write boundary computation and the post-write read-back walk every page (`?limit=&page=1,2,…` until a short/empty page) so the match is exhaustive regardless of how many comments or reviews already exist.
|
|
|
|
## `tea` invocation notes (Gitea)
|
|
|
|
- tea v0.11.1 has **no `comment` subcommand under `tea pr` or `tea issue`**. The correct invocation is the **top-level** `tea comment <index> <body> [--repo ...] [--login ...]`. The `tea pr comment` / `tea issue comment` forms don't error — tea silently falls through to a no-op and still exits 0, producing a false-success write (#865). Always use the top-level form.
|
|
- `tea pr approve` and `tea pr reject` take an optional review comment/reason as a **trailing positional argument**, not a `--comment`/`-comment` flag (that flag does not exist on those subcommands). `pr-review.sh` avoids this positional form entirely for the approve/reject actions and instead posts any review comment through the same durable, read-back-verified comment API used for the `comment` action (see #835/#812) — the trailing-positional form remains available to callers who invoke `tea` directly, but is not used by these wrappers.
|
|
|
|
### `--login` passthrough
|
|
|
|
Both `pr-review.sh` and `issue-comment.sh` accept an optional `--login <name>` flag that overrides the automatically detected Gitea `tea` login for that single invocation. The override is appended to the `tea` command line **after** the detected default (`get_gitea_repo_args()` / `get_gitea_login[_for_host]()`), because tea honors only the **last** `--login` flag on its command line — an override placed before the default would be silently clobbered by it. Callers who need a different login than the host default should pass `--login <reviewer-login>` rather than relying on ordering tricks or re-invoking `tea login` globally.
|
|
|
|
As a durable successor to this mechanism, consider giving each reviewer/approver slot its own dedicated Gitea login credential, so that author≠reviewer holds at the credential level rather than relying on wrapper-level `--login` bookkeeping. This is a recommendation for future hardening, not something implemented by this flag.
|