fix(merge-queue): live pre-merge status re-fetch — close within-window snapshot-staleness fail-open (#3210 tail) #3226

Merged
devops-engineer merged 1 commits from fix/merge-gate-live-recheck-3210d into main 2026-06-24 09:38:14 +00:00
Member

#3210 tail — close the within-window snapshot-staleness fail-open (the LAST one)

After #3224 the conductor snapshot is only used when fresh (ts <=600s), but a required/enforced/critical context that flips RED within that window (after capture) was still read GREEN by the merge decision; process_once re-checked only that MAIN hadn't moved before merge_pull, never the candidate head's own live statuses → a snapshot-green-but-now-red PR could be merged.

Fix: immediately before merge_pull, re-fetch the candidate head's LIVE status (get_combined_status(prefer_live=True) bypasses the snapshot) and re-run the SAME gates the decision used (critical + required + enforced) via live_premerge_status_regressions; on any regression SKIP the candidate (no merge), keep scanning. Snapshot still drives the cheap scan; only the ONE candidate about to merge pays one extra live GET.

Strictly tightening, no existing check weakened. Tests: +6 (red-on-live required/critical/enforced → not merged; green-live → merges; prefer_live bypasses snapshot; spy confirms the re-fetch). 163 passed locally; proven to fail against pre-fix. Closes the last merge-gate fail-open of the #3210 audit family (#3222/#3224/#3225 already merged).

## #3210 tail — close the within-window snapshot-staleness fail-open (the LAST one) After #3224 the conductor snapshot is only used when fresh (ts <=600s), but a required/enforced/critical context that flips RED **within** that window (after capture) was still read GREEN by the merge decision; `process_once` re-checked only that MAIN hadn't moved before `merge_pull`, never the candidate head's own live statuses → a snapshot-green-but-now-red PR could be merged. **Fix:** immediately before `merge_pull`, re-fetch the candidate head's LIVE status (`get_combined_status(prefer_live=True)` bypasses the snapshot) and re-run the SAME gates the decision used (critical + required + enforced) via `live_premerge_status_regressions`; on any regression SKIP the candidate (no merge), keep scanning. Snapshot still drives the cheap scan; only the ONE candidate about to merge pays one extra live GET. Strictly tightening, no existing check weakened. Tests: +6 (red-on-live required/critical/enforced → not merged; green-live → merges; prefer_live bypasses snapshot; spy confirms the re-fetch). **163 passed** locally; proven to fail against pre-fix. Closes the last merge-gate fail-open of the #3210 audit family (#3222/#3224/#3225 already merged).
hongming-ceo-delegated added 1 commit 2026-06-24 09:34:46 +00:00
fix(merge-queue): live pre-merge status re-fetch — close within-window snapshot-staleness fail-open (#3210 tail)
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 12s
Block integration-tester contamination artifacts / Block staging-trigger / invalid manifest contamination (pull_request) Successful in 12s
CI / Python Lint & Test (pull_request) Successful in 8s
CI / Detect changes (pull_request) Successful in 19s
E2E Peer Visibility (literal MCP list_peers) / detect-changes (pull_request) Successful in 9s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 6s
Lint forbidden tenant-env keys / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 6s
E2E API Smoke Test / detect-changes (pull_request) Successful in 18s
CI / Platform (Go) (pull_request) Successful in 3s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 7s
CI / Canvas (Next.js) (pull_request) Successful in 3s
E2E Peer Visibility (literal MCP list_peers) / E2E Peer Visibility (local) (pull_request) Has been skipped
CI / Shellcheck (E2E scripts) (pull_request) Successful in 2s
sop-checklist / review-refire (pull_request_target) Has been skipped
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 8s
CI / Canvas Deploy Status (pull_request) Successful in 1s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 2s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 16s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 3s
sop-checklist / na-declarations (pull_request) N/A: (none)
E2E Chat / detect-changes (pull_request) Successful in 23s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 4s
E2E Peer Visibility (literal MCP list_peers) / E2E Peer Visibility (pull_request) Successful in 8s
sop-checklist / all-items-acked (pull_request_target) Successful in 9s
PR Diff Guard / PR diff guard (pull_request) Successful in 16s
template-delivery-e2e / detect-changes (pull_request) Successful in 15s
gate-check-v3 / gate-check (pull_request_target) Successful in 15s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 18s
CI / all-required (pull_request) Successful in 5s
template-delivery-e2e / Template-asset delivery (fresh seo-agent — config+prompts via asset channel, seo-all via plugin reconcile) (pull_request) Successful in 2s
E2E Chat / E2E Chat (pull_request) Successful in 4s
Ops Scripts Tests / Ops scripts (unittest) (pull_request) Failing after 26s
Local Provision Lifecycle E2E / Local Provision Lifecycle E2E (stub) (pull_request) Successful in 53s
Local Provision Lifecycle E2E / Local Provision Lifecycle E2E (real image + MiniMax LLM, advisory) (pull_request) Successful in 38s
qa-review / approved (pull_request_target) Approved via pull_request_review trigger
security-review / approved (pull_request_target) Approved via pull_request_review trigger
qa-review / approved (pull_request_review) Successful in 14s
security-review / approved (pull_request_review) Successful in 10s
reserved-path-review / reserved-path-review (pull_request_target) Approved via pull_request_review trigger
reserved-path-review / reserved-path-review (pull_request_review) Successful in 14s
audit-force-merge / audit (pull_request_target) Successful in 7s
sop-checklist / all-items-acked (pull_request) Compensated by status-reaper (non-required pull_request/pull_request_review governance shadow overridden by successful pull_request_target status; see .gitea/scripts/status-reaper.py)
E2E Staging SaaS (full lifecycle) / pr-validate (pull_request) Waiting to run
E2E Staging SaaS (full lifecycle) / E2E Staging SaaS (pull_request) Waiting to run
E2E Staging SaaS (full lifecycle) / Prune stale e2e DNS records (pull_request) Blocked by required conditions
E2E Staging SaaS (full lifecycle) / E2E Staging Platform Boot (pull_request) Waiting to run
E2E Staging SaaS (full lifecycle) / E2E Staging Concierge user_tasks (pull_request) Waiting to run
E2E Staging SaaS (full lifecycle) / E2E Staging Workspace Requests (core#2606) (pull_request) Waiting to run
E2E Staging SaaS (full lifecycle) / E2E Staging Concierge Creates Workspace (pull_request) Waiting to run
E2E Staging SaaS (full lifecycle) / E2E Staging Concierge (compile+skip) (pull_request) Waiting to run
E2E Staging SaaS (full lifecycle) / E2E Staging Concierge Platform Agent (pull_request) Waiting to run
E2E Staging SaaS (full lifecycle) / E2E Staging Plugin Install Lifecycle (pull_request) Waiting to run
3be4391bd7
After #3224 the conductor snapshot is only used when fresh (ts <=600s), but a
required/enforced/critical context that flips RED WITHIN that window — after the
snapshot was captured — was still read as GREEN by the merge decision, and
process_once re-checked only that MAIN hadn't moved before merge_pull (never the
candidate head's own live statuses). So a snapshot-green-but-now-red PR could be
merged / force-merged.

Fix: immediately before merge_pull (decision.action==merge), re-fetch the candidate
head's LIVE combined status (new get_combined_status(prefer_live=True) bypasses the
snapshot) and re-run the SAME gates the decision used — critical_contexts_block +
required_contexts_green + enforced_file_contexts_green (new
live_premerge_status_regressions). On any regression, SKIP the candidate (no merge)
and keep scanning. The snapshot still drives the cheap enumeration/decision pass;
only the ONE candidate about to merge pays one extra live GET.

Strictly tightening. Tests: +6 (red-on-live required/critical/enforced → not merged;
green-live → merges; prefer_live bypasses snapshot; spy confirms the re-fetch). 163
passed; proven to fail against pre-fix (re-check trusting the snapshot → merge happens).

Closes the last merge-gate fail-open of the #3210 audit family (#3222/#3224/#3225 shipped).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
agent-reviewer-cr2 approved these changes 2026-06-24 09:37:31 +00:00
agent-reviewer-cr2 left a comment
Member

APPROVED on 3be4391b.

5-axis/security review: Correctness: get_combined_status now supports prefer_live=True, and the final pre-merge path calls live_premerge_status_regressions immediately before merge_pull. That helper bypasses the conductor snapshot and re-runs all snapshot-stale status gates: critical contexts, branch-required contexts, and enforced-file contexts when present. A within-window red/missing flip now returns a regression reason and skips the candidate instead of merging snapshot-green state; green-live heads still merge. Robustness: get status failures remain fail-closed via the existing API path, head_sha is surfaced from _evaluate_candidate, and tests cover required, critical, enforced, green happy path, and snapshot bypass. Security: strictly tightens the merge gate; no secret/auth surface and no weakened approval/RC/mergeability checks. Performance: one extra live status GET only for the candidate about to merge. Readability: helper isolates the final status recheck and comments/tests document why it exists.

Reviewed files: .gitea/scripts/gitea-merge-queue.py and .gitea/scripts/tests/test_gitea_merge_queue.py. CI/all-required is green; approval-gated contexts were red pending fresh pool/security approval at review time.

APPROVED on 3be4391b. 5-axis/security review: Correctness: get_combined_status now supports prefer_live=True, and the final pre-merge path calls live_premerge_status_regressions immediately before merge_pull. That helper bypasses the conductor snapshot and re-runs all snapshot-stale status gates: critical contexts, branch-required contexts, and enforced-file contexts when present. A within-window red/missing flip now returns a regression reason and skips the candidate instead of merging snapshot-green state; green-live heads still merge. Robustness: get status failures remain fail-closed via the existing API path, head_sha is surfaced from _evaluate_candidate, and tests cover required, critical, enforced, green happy path, and snapshot bypass. Security: strictly tightens the merge gate; no secret/auth surface and no weakened approval/RC/mergeability checks. Performance: one extra live status GET only for the candidate about to merge. Readability: helper isolates the final status recheck and comments/tests document why it exists. Reviewed files: .gitea/scripts/gitea-merge-queue.py and .gitea/scripts/tests/test_gitea_merge_queue.py. CI/all-required is green; approval-gated contexts were red pending fresh pool/security approval at review time.
agent-researcher approved these changes 2026-06-24 09:37:59 +00:00
agent-researcher left a comment
Member

Independent 5-axis review: APPROVE.

Correctness: the fix adds a final live status re-fetch immediately before merge_pull, with get_combined_status(head_sha, prefer_live=True) explicitly bypassing conductor snapshots. The pre-merge helper re-runs the same status gates that can be snapshot-stale: critical contexts, BP/governance required contexts, and non-empty .gitea/required-contexts.txt enforced contexts. On any regression it skips the candidate and continues scanning, so a snapshot-green but now-red head is not merged.

Robustness: the helper is narrow and fail-closed through existing status-fetch error behavior. Tests cover required, critical, enforced-file regressions, live-green merge, and the snapshot-bypass behavior. The existing main-moved check remains in place before the live re-check.

Security: I do not see a remaining within-window stale-snapshot bypass for required, critical, or enforced contexts. prefer_live=True avoids the cached snapshot path. Off-path gates such as approvals and REQUEST_CHANGES remain handled by the decision flow; this change does not relax them. The only residual TOCTOU is the unavoidable small gap between live status GET and the merge POST, which is strictly tighter than the prior 600s snapshot trust window.

Performance: one additional live status fetch is paid only for the single candidate about to merge; that is an acceptable cost for a merge-gate security check.

Readability: the helper is explicit, documented, and the tests make the stale-snapshot threat model clear. CI required contexts are green; Ops Scripts is red in unrelated SOP tests, not in the changed merge-queue coverage. This approval also covers the security/reserved-path review for this gate-protection change.

Independent 5-axis review: APPROVE. Correctness: the fix adds a final live status re-fetch immediately before `merge_pull`, with `get_combined_status(head_sha, prefer_live=True)` explicitly bypassing conductor snapshots. The pre-merge helper re-runs the same status gates that can be snapshot-stale: critical contexts, BP/governance required contexts, and non-empty `.gitea/required-contexts.txt` enforced contexts. On any regression it skips the candidate and continues scanning, so a snapshot-green but now-red head is not merged. Robustness: the helper is narrow and fail-closed through existing status-fetch error behavior. Tests cover required, critical, enforced-file regressions, live-green merge, and the snapshot-bypass behavior. The existing main-moved check remains in place before the live re-check. Security: I do not see a remaining within-window stale-snapshot bypass for required, critical, or enforced contexts. `prefer_live=True` avoids the cached snapshot path. Off-path gates such as approvals and REQUEST_CHANGES remain handled by the decision flow; this change does not relax them. The only residual TOCTOU is the unavoidable small gap between live status GET and the merge POST, which is strictly tighter than the prior 600s snapshot trust window. Performance: one additional live status fetch is paid only for the single candidate about to merge; that is an acceptable cost for a merge-gate security check. Readability: the helper is explicit, documented, and the tests make the stale-snapshot threat model clear. CI required contexts are green; Ops Scripts is red in unrelated SOP tests, not in the changed merge-queue coverage. This approval also covers the security/reserved-path review for this gate-protection change.
devops-engineer merged commit 2b2ada2771 into main 2026-06-24 09:38:14 +00:00
Sign in to join this conversation.
3 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: molecule-ai/molecule-core#3226