fix(tools): attribute read-back to acting identity and paginate fully (#865)
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>
This commit is contained in:
Hermes Agent
2026-07-21 19:02:25 -05:00
parent 10fdd49e32
commit 16481ece3d
5 changed files with 461 additions and 146 deletions

View File

@@ -1,23 +1,28 @@
#!/usr/bin/env bash
# Regression harness for issue-comment.sh top-level `tea comment` invocation and
# its BOUNDED read-back verification (#865).
# its BOUNDED, INVOCATION-ATTRIBUTED, PAGINATED read-back verification (#865).
#
# The #865 bug: `tea issue comment ...` (a nonexistent subcommand on tea
# v0.11.1) silently no-ops and exits 0, so a comment is never posted. A naive
# read-back that matches ANY historical comment by body would falsely report
# success whenever an identically-bodied comment already exists from a prior
# run. This harness proves the wrapper:
# run. Merely bounding by "id > pre-write max" is also insufficient: it accepts
# ANY newer matching comment, including one a CONCURRENT DIFFERENT identity
# posted while this tea invocation created nothing. This harness proves the
# wrapper:
# 1. uses the top-level `tea comment` form (never `tea issue comment`);
# 2. records the pre-write maximum comment id as a boundary and requires a
# strictly-newer comment on read-back, so a pre-existing identical body
# does NOT satisfy verification (fails closed);
# 3. reports success only when a genuinely new comment (id > boundary) with
# the exact body appears.
# 3. attributes the matched comment to the acting identity (GET /user login),
# so a concurrent DIFFERENT-identity write does NOT satisfy verification;
# 4. reports success only when a genuinely new comment (id > boundary) with
# the exact body AND the acting author appears.
#
# The `tea` stub NEVER creates a comment (it mimics the silent no-op); the
# "server" comment state is modeled entirely by the curl stub's responses, so
# the fresh-success vs. no-op distinction is driven purely by whether the
# post-write read-back surfaces a new id.
# post-write read-back surfaces a new, correctly-attributed id.
set -euo pipefail
@@ -42,7 +47,10 @@ git -C "$REPO_DIR" remote add origin https://git.mosaicstack.dev/mosaicstack/sta
ISSUE_NUMBER=7
API_BASE="https://git.mosaicstack.dev/api/v1/repos/mosaicstack/stack"
API_ROOT="https://git.mosaicstack.dev/api/v1"
BODY='durable "note" -- marker'
ACTING_LOGIN="primary-reviewer"
FOREIGN_LOGIN="other-writer"
CONFIGURED_GITEA_URL="https://git.mosaicstack.dev" python3 - "$CREDENTIALS_FILE" <<'PY'
import json
@@ -91,8 +99,9 @@ exit 92
SH
chmod +x "$BIN_DIR/tea"
# curl stub: serves GET .../issues/7/comments. First call = pre-write boundary,
# second call = post-write read-back. The boundary always contains a
# curl stub: serves GET /user (acting identity) and GET .../issues/7/comments
# (paginated: ?limit=&page=). The comments endpoint's first call = pre-write
# boundary, second call = post-write read-back. The boundary always contains a
# pre-existing comment (id 50) whose body is IDENTICAL to the one under test,
# which is exactly the condition a body-only match would trip over.
cat > "$BIN_DIR/curl" <<'SH'
@@ -114,6 +123,10 @@ while [[ $# -gt 0 ]]; do
esac
done
# Strip any query string so pagination params don't defeat path matching, but
# still log the full URL (including ?limit=&page=) so the test can assert the
# read-back paginated.
path="${url%%\?*}"
printf '%s %s\n' "$method" "$url" >> "$ISSUE_COMMENT_CURL_LOG"
write_response() {
@@ -123,41 +136,58 @@ write_response() {
printf '%s' "$status"
}
if [[ "$method" == "GET" && "$url" == "$ISSUE_COMMENT_API_BASE/issues/7/comments" ]]; then
if [[ "$method" == "GET" && "$path" == "$ISSUE_COMMENT_API_ROOT/user" ]]; then
write_response 200 "$(ISSUE_COMMENT_LOGIN="$ISSUE_COMMENT_ACTING_LOGIN" python3 - <<'PY'
import json
import os
print(json.dumps({"login": os.environ["ISSUE_COMMENT_LOGIN"]}))
PY
)"
elif [[ "$method" == "GET" && "$path" == "$ISSUE_COMMENT_API_BASE/issues/7/comments" ]]; then
calls_file="$ISSUE_COMMENT_CALLS"
if [[ -f "$calls_file" ]]; then
# post-write read-back
if [[ "$ISSUE_COMMENT_TEST_MODE" == "fresh-success" ]]; then
response=$(ISSUE_COMMENT_BODY="$ISSUE_COMMENT_EXPECTED_BODY" python3 - <<'PY'
response=$(ISSUE_COMMENT_BODY="$ISSUE_COMMENT_EXPECTED_BODY" \
ISSUE_COMMENT_ACTING_LOGIN="$ISSUE_COMMENT_ACTING_LOGIN" \
ISSUE_COMMENT_FOREIGN_LOGIN="$ISSUE_COMMENT_FOREIGN_LOGIN" \
ISSUE_COMMENT_TEST_MODE="$ISSUE_COMMENT_TEST_MODE" python3 - <<'PY'
import json
import os
body = os.environ["ISSUE_COMMENT_BODY"]
print(json.dumps([
{"id": 50, "body": body},
{"id": 60, "body": body},
]))
PY
)
else
# no-op: nothing new landed; the pre-existing id-50 comment remains.
response=$(ISSUE_COMMENT_BODY="$ISSUE_COMMENT_EXPECTED_BODY" python3 - <<'PY'
import json
import os
acting = os.environ["ISSUE_COMMENT_ACTING_LOGIN"]
foreign = os.environ["ISSUE_COMMENT_FOREIGN_LOGIN"]
mode = os.environ["ISSUE_COMMENT_TEST_MODE"]
body = os.environ["ISSUE_COMMENT_BODY"]
print(json.dumps([{"id": 50, "body": body}]))
if mode == "fresh-success":
# A genuinely new comment (id 60 > boundary 50) authored by the acting
# identity.
records = [
{"id": 50, "body": body, "user": {"login": acting}},
{"id": 60, "body": body, "user": {"login": acting}},
]
elif mode == "foreign-identity":
# A concurrent new comment (id 60 > boundary 50) with the SAME body but a
# DIFFERENT author. tea created nothing; attribution must reject this.
records = [
{"id": 50, "body": body, "user": {"login": acting}},
{"id": 60, "body": body, "user": {"login": foreign}},
]
else:
# no-op: nothing new landed; the pre-existing id-50 comment remains.
records = [{"id": 50, "body": body, "user": {"login": acting}}]
print(json.dumps(records))
PY
)
fi
else
: > "$calls_file"
response=$(ISSUE_COMMENT_BODY="$ISSUE_COMMENT_EXPECTED_BODY" python3 - <<'PY'
response=$(ISSUE_COMMENT_BODY="$ISSUE_COMMENT_EXPECTED_BODY" \
ISSUE_COMMENT_ACTING_LOGIN="$ISSUE_COMMENT_ACTING_LOGIN" python3 - <<'PY'
import json
import os
body = os.environ["ISSUE_COMMENT_BODY"]
print(json.dumps([{"id": 50, "body": body}]))
acting = os.environ["ISSUE_COMMENT_ACTING_LOGIN"]
print(json.dumps([{"id": 50, "body": body, "user": {"login": acting}}]))
PY
)
fi
@@ -184,7 +214,10 @@ run_comment() {
ISSUE_COMMENT_CALLS="$CALLS_FILE" \
ISSUE_COMMENT_TEST_MODE="$mode" \
ISSUE_COMMENT_EXPECTED_BODY="$BODY" \
ISSUE_COMMENT_ACTING_LOGIN="$ACTING_LOGIN" \
ISSUE_COMMENT_FOREIGN_LOGIN="$FOREIGN_LOGIN" \
ISSUE_COMMENT_API_BASE="$API_BASE" \
ISSUE_COMMENT_API_ROOT="$API_ROOT" \
"$SCRIPT_DIR/issue-comment.sh" -i "$ISSUE_NUMBER" -c "$BODY"
) > "$OUTPUT_FILE" 2>&1
}
@@ -206,11 +239,27 @@ if grep -q '^issue comment' "$TEA_LOG"; then
echo "FAIL: wrapper used the broken 'tea issue comment' subcommand" >&2
exit 1
fi
[[ "$(grep -c "^GET $API_BASE/issues/7/comments$" "$CURL_LOG")" == "2" ]]
[[ "$(grep -c "^GET $API_BASE/issues/7/comments?" "$CURL_LOG")" == "2" ]]
# Read-back must be paginated (limit + page query params present).
grep -q "^GET $API_BASE/issues/7/comments?limit=[0-9]*&page=1$" "$CURL_LOG"
# Attribution must have resolved the acting identity via GET /user.
grep -q "^GET $API_ROOT/user$" "$CURL_LOG"
# Case 2: a genuinely new comment (id 60 > boundary 50) verifies successfully.
# Case 2: a concurrent DIFFERENT-identity write (id 60 > boundary, same body,
# foreign author) must FAIL CLOSED — temporal ordering is not attribution.
if run_comment foreign-identity; then
echo "FAIL: wrapper accepted a concurrent comment authored by a different identity" >&2
cat "$OUTPUT_FILE" >&2
exit 1
fi
if grep -q 'Added and verified comment' "$OUTPUT_FILE"; then
echo "FAIL: read-back matched a different-identity comment (attribution bypassed)" >&2
exit 1
fi
# Case 3: a genuinely new comment (id 60 > boundary 50, acting author) verifies.
run_comment fresh-success
grep -q 'Added and verified comment on Gitea issue #7 (comment ID 60)' "$OUTPUT_FILE"
grep -q "^comment 7 " "$TEA_LOG"
echo "issue-comment.sh bounded read-back regression passed"
echo "issue-comment.sh bounded + attributed read-back regression passed"