fix(gate): CR2 RC 8365 — APPROVED event value + fresh-context proof (#2163)
ci-arm64-advisory / fast-checks (pull_request) Waiting to run
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (pull_request) Failing after 2s
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 10s
CI / Python Lint & Test (pull_request) Successful in 9s
CI / Detect changes (pull_request) Successful in 37s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 10s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 21s
E2E API Smoke Test / detect-changes (pull_request) Successful in 29s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 29s
E2E Chat / detect-changes (pull_request) Successful in 30s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 12s
qa-review / approved (pull_request_target) Failing after 12s
Lint forbidden tenant-env keys / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 19s
sop-checklist / review-refire (pull_request_target) Has been skipped
gate-check-v3 / gate-check (pull_request_target) Failing after 14s
security-review / approved (pull_request_target) Failing after 9s
sop-tier-check / tier-check (pull_request_target) Successful in 7s
sop-checklist / all-items-acked (pull_request) acked: 0/7 — missing: comprehensive-testing, local-postgres-e2e, staging-smoke, +4
sop-checklist / na-declarations (pull_request) N/A: (none)
CI / Platform (Go) (pull_request) Successful in 1s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 1s
CI / Canvas (Next.js) (pull_request) Successful in 2s
sop-checklist / all-items-acked (pull_request_target) Successful in 20s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 1s
CI / all-required (pull_request) Successful in 4s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 1s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 8s
E2E Chat / E2E Chat (pull_request) Successful in 12s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m0s
Ops Scripts Tests / Ops scripts (unittest) (pull_request) Successful in 1m6s
qa-review / approved (pull_request_review) Has been skipped
security-review / approved (pull_request_review) Has been skipped
sop-tier-check / tier-check (pull_request_review) Successful in 4s
audit-force-merge / audit (pull_request_target) Successful in 9s

- test_gate_auto_fire_live.py: change review event from \"APPROVE\" to
  \"APPROVED\" to match Gitea API contract.
- Add _get_status_updated_at() to capture pre-existing status timestamps
  before review submission.
- Add _poll_fresh_statuses() that only accepts statuses whose updated_at
  differs from the pre-existing record, proving the context was posted
  AFTER the review rather than tolerating stale contexts.
- Remove misleading \"tolerate stale contexts\" comment.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Molecule AI Dev Engineer A (Kimi)
2026-06-03 10:20:20 +00:00
parent 6431df9212
commit 77573074e4
@@ -115,18 +115,37 @@ def _find_suitable_pr() -> dict:
pytest.skip("No open PR found whose head contains the pull_request_review trigger")
def _submit_approved_review(pr_number: int) -> None:
code, _ = _api(
def _submit_approved_review(pr_number: int) -> dict:
code, review = _api(
"POST",
f"/repos/{REPO}/pulls/{pr_number}/reviews",
{"body": "Live-fire test APPROVED review", "event": "APPROVE"},
{"body": "Live-fire test APPROVED review", "event": "APPROVED"},
)
# 200 = created, 422 = review already exists (idempotent enough for our purposes)
if code not in (200, 201, 422):
pytest.fail(f"POST /pulls/{pr_number}/reviews returned HTTP {code}")
return review
def _poll_status_contexts(sha: str, timeout_sec: int = LIVEFIRE_TIMEOUT_SEC) -> dict[str, str]:
def _get_status_updated_at(sha: str) -> dict[str, str]:
"""Return mapping context -> updated_at for required contexts on this SHA."""
code, statuses = _api("GET", f"/repos/{REPO}/statuses/{sha}?limit=100")
if code != 200:
return {}
result: dict[str, str] = {}
for st in statuses:
ctx = st.get("context", "")
if ctx in REQUIRED_CONTEXTS:
result[ctx] = st.get("updated_at", st.get("created_at", ""))
return result
def _poll_fresh_statuses(
sha: str,
prior_updated_at: dict[str, str],
timeout_sec: int = LIVEFIRE_TIMEOUT_SEC,
) -> dict[str, str]:
"""Poll until required contexts appear with updated_at fresher than prior."""
deadline = time.monotonic() + timeout_sec
found: dict[str, str] = {}
while time.monotonic() < deadline:
@@ -135,7 +154,10 @@ def _poll_status_contexts(sha: str, timeout_sec: int = LIVEFIRE_TIMEOUT_SEC) ->
for st in statuses:
ctx = st.get("context", "")
if ctx in REQUIRED_CONTEXTS:
found[ctx] = st.get("state", st.get("status", ""))
updated_at = st.get("updated_at", st.get("created_at", ""))
# Fresh if the context was absent before, OR its timestamp changed.
if ctx not in prior_updated_at or updated_at != prior_updated_at[ctx]:
found[ctx] = st.get("state", st.get("status", ""))
if all(ctx in found for ctx in REQUIRED_CONTEXTS):
return found
time.sleep(5)
@@ -145,27 +167,29 @@ def _poll_status_contexts(sha: str, timeout_sec: int = LIVEFIRE_TIMEOUT_SEC) ->
@skip_no_token
class TestGateAutoFireLive:
def test_auto_fire_posts_required_contexts(self):
"""Submit APPROVED review; assert BP-required contexts appear within timeout."""
"""Submit APPROVED review; assert BP-required contexts appear fresh within timeout."""
pr = _find_suitable_pr()
pr_number = pr["number"]
head_sha = pr["head"]["sha"]
# Pre-check: ensure contexts are not already present from a previous run.
# We tolerate stale contexts; the test looks for a fresh appearance.
# Capture pre-existing status timestamps so we can prove FRESH contexts
# were posted after the review submission (not stale from a prior run).
prior_updated_at = _get_status_updated_at(head_sha)
_submit_approved_review(pr_number)
found = _poll_status_contexts(head_sha)
found = _poll_fresh_statuses(head_sha, prior_updated_at)
missing = [ctx for ctx in REQUIRED_CONTEXTS if ctx not in found]
if missing:
pytest.fail(
f"After {LIVEFIRE_TIMEOUT_SEC}s, contexts still missing: {missing}. "
f"Found: {found}. "
f"After {LIVEFIRE_TIMEOUT_SEC}s, fresh contexts still missing: {missing}. "
f"Found: {found}. Prior timestamps: {prior_updated_at}. "
f"PR #{pr_number} head={head_sha}. "
f"This indicates the pull_request_review trigger did not fire at runtime."
)
# The contexts appeared — that's the proof of auto-fire.
# The contexts appeared fresh — that's the proof of auto-fire.
# We do NOT assert success vs failure; the evaluator decides that.
# The point of #2159 is that the workflows QUEUE and POST at all.
for ctx, state in found.items():