fix(merge-queue): fail-closed on empty PUSH_REQUIRED_CONTEXTS + untrustworthy snapshot ts (#3210 tail) #3224

Merged
devops-engineer merged 1 commits from fix/merge-gate-hardening-3210b into main 2026-06-24 09:18:02 +00:00
Member

#3210 merge-gate hardening tail (HIGH + MEDIUM)

Sibling fail-opens after the CRITICAL+HIGH approval fixes (shipped in #3222). Both strictly tightening, mirror the existing fail-closed exception convention.

🟠 FIX A (HIGH) — empty PUSH_REQUIRED_CONTEXTS → vacuous main-green pass

push_required_contexts() returned [] on an empty/whitespace/comma env → required_contexts_green(main_latest, []) passed for ANY main state incl. all-red → the queue's own main-green backstop was silently disabled (merge onto a red main). Fix: new PushRequiredContextsUnavailable(ApiError), raised on an empty parse → propagates to main() → rc 1 (HOLD + page); never returns [].

🟡 FIX B (MEDIUM) — malformed/absent snapshot ts treated as FRESH

load_conductor_snapshot() skipped the age-check on absent/empty ts and swallowed an unparseable ts as 'fresh (conservative)' — actually anti-conservative (an undated/old snapshot trusted as current). Fix: require a present+parseable+in-window ts; else return None (self-fetch via the existing cache-miss path).

Tests: +10 (incl. an all-red-main integration proving the tick HOLDs, and absent/malformed-ts discard); 157 passed locally; new tests proven to fail against pre-fix behavior. Addresses the #3210 hardening tail.

## #3210 merge-gate hardening tail (HIGH + MEDIUM) Sibling fail-opens after the CRITICAL+HIGH approval fixes (shipped in #3222). Both strictly tightening, mirror the existing fail-closed exception convention. ### 🟠 FIX A (HIGH) — empty `PUSH_REQUIRED_CONTEXTS` → vacuous main-green pass `push_required_contexts()` returned `[]` on an empty/whitespace/comma env → `required_contexts_green(main_latest, [])` passed for ANY main state incl. all-red → the queue's own main-green backstop was silently disabled (merge onto a red main). Fix: new `PushRequiredContextsUnavailable(ApiError)`, raised on an empty parse → propagates to `main()` → rc 1 (HOLD + page); never returns `[]`. ### 🟡 FIX B (MEDIUM) — malformed/absent snapshot `ts` treated as FRESH `load_conductor_snapshot()` skipped the age-check on absent/empty `ts` and swallowed an unparseable `ts` as 'fresh (conservative)' — actually anti-conservative (an undated/old snapshot trusted as current). Fix: require a present+parseable+in-window `ts`; else `return None` (self-fetch via the existing cache-miss path). Tests: +10 (incl. an all-red-main integration proving the tick HOLDs, and absent/malformed-ts discard); **157 passed** locally; new tests proven to fail against pre-fix behavior. Addresses the #3210 hardening tail.
hongming-ceo-delegated added 1 commit 2026-06-24 09:07:46 +00:00
fix(merge-queue): fail closed on empty PUSH_REQUIRED_CONTEXTS + untrustworthy conductor-snapshot ts (#3210 tail)
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 11s
CI / Python Lint & Test (pull_request) Successful in 7s
Block integration-tester contamination artifacts / Block staging-trigger / invalid manifest contamination (pull_request) Successful in 10s
E2E Peer Visibility (literal MCP list_peers) / 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 5s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 5s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 7s
E2E Peer Visibility (literal MCP list_peers) / E2E Peer Visibility (local) (pull_request) Has been skipped
sop-checklist / review-refire (pull_request_target) Has been skipped
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 7s
E2E API Smoke Test / detect-changes (pull_request) Successful in 15s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 2s
E2E Peer Visibility (literal MCP list_peers) / E2E Peer Visibility (pull_request) Successful in 6s
sop-checklist / all-items-acked (pull_request) acked: 0/9 — missing: comprehensive-testing, local-postgres-e2e, staging-smoke, +6 — body-unfilled: comprehensive-testing, local-postgres-e2
sop-checklist / na-declarations (pull_request) N/A: (none)
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 3s
sop-checklist / all-items-acked (pull_request_target) Successful in 9s
E2E Chat / detect-changes (pull_request) Successful in 20s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 16s
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 14s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 22s
E2E Chat / E2E Chat (pull_request) Successful in 3s
template-delivery-e2e / Template-asset delivery (fresh seo-agent — config+prompts via asset channel, seo-all via plugin reconcile) (pull_request) Successful in 2s
CI / Detect changes (pull_request) Successful in 30s
Ops Scripts Tests / Ops scripts (unittest) (pull_request) Failing after 18s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 3s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 2s
CI / Canvas (Next.js) (pull_request) Successful in 3s
CI / Platform (Go) (pull_request) Successful in 3s
CI / Canvas Deploy Status (pull_request) Successful in 3s
CI / all-required (pull_request) Successful in 6s
Local Provision Lifecycle E2E / Local Provision Lifecycle E2E (stub) (pull_request) Successful in 40s
Local Provision Lifecycle E2E / Local Provision Lifecycle E2E (real image + MiniMax LLM, advisory) (pull_request) Successful in 2m20s
qa-review / approved (pull_request_target) Approved via pull_request_review trigger
reserved-path-review / reserved-path-review (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 10s
reserved-path-review / reserved-path-review (pull_request_review) Successful in 11s
security-review / approved (pull_request_review) Successful in 11s
audit-force-merge / audit (pull_request_target) Successful in 8s
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
d9b63d1bef
FIX A (HIGH): push_required_contexts() returned [] when PUSH_REQUIRED_CONTEXTS
parsed empty -> required_contexts_green(main_latest, []) passed VACUOUSLY for any
main state (incl. all-red) -> the queue's main-green backstop was silently disabled
and it could merge onto a red main. Fix: new PushRequiredContextsUnavailable(ApiError);
push_required_contexts raises on an empty parse -> propagates to main() -> rc 1
(HOLD + page). Never returns []. Mirrors the EnforcedContextsUnavailable convention.

FIX B (MEDIUM): load_conductor_snapshot() skipped the freshness age-check on an
absent/empty `ts` and swallowed an unparseable `ts` as "fresh (conservative)" —
anti-conservative: an undated/old snapshot from a wedged conductor was trusted as
current. Fix: require a present, parseable, in-window ts; absent/empty/unparseable
-> return None (self-fetch via the existing cache-miss path).

Both strictly tightening. Tests: +10 (empty/whitespace/comma PUSH_REQUIRED_CONTEXTS
-> HOLD incl. an all-red-main integration; absent/malformed ts -> discard; happy-path
regression guards). 157 passed; new tests proven to fail against pre-fix behavior.

Addresses the #3210 merge-gate hardening tail (CRITICAL+HIGH approval fail-opens
shipped in #3222; this closes the main-green-backstop + snapshot-staleness fail-opens).

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

APPROVED on d9b63d1bef.

5-axis + adversarial security review:

  • Correctness: empty PUSH_REQUIRED_CONTEXTS no longer reaches required_contexts_green(main_latest, []); push_required_contexts() raises PushRequiredContextsUnavailable, an ApiError, so main() returns rc=1/no merge instead of a vacuous main-green pass. Fresh conductor snapshots still work, while missing/empty/unparseable ts now returns None and forces live self-fetch.
  • Robustness: the fail-closed error is localized to the push-required context parser and leaves normal non-empty parsing unchanged. Snapshot freshness handling now treats unverifiable age as untrusted, not fresh.
  • Security: I do not see a remaining empty-context bypass: whitespace/all-comma parse to [] and raise. I also do not see a forged/stale-ts snapshot trust path: absent or malformed ts is discarded, and old parseable ts is still stale-filtered.
  • Performance: no meaningful impact; the added checks are constant-time and self-fetch is already the cache-miss path.
  • Readability: the error class and tests make the fail-closed contract explicit.

CI notes: Platform(Go) and CI/all-required are green. Ops Scripts is red in unrelated test_sop_checklist.py tuple/list expectations, not in the changed merge-queue tests.

APPROVED on d9b63d1bef855f65db8996a27041d218857a33c3. 5-axis + adversarial security review: - Correctness: empty PUSH_REQUIRED_CONTEXTS no longer reaches required_contexts_green(main_latest, []); push_required_contexts() raises PushRequiredContextsUnavailable, an ApiError, so main() returns rc=1/no merge instead of a vacuous main-green pass. Fresh conductor snapshots still work, while missing/empty/unparseable ts now returns None and forces live self-fetch. - Robustness: the fail-closed error is localized to the push-required context parser and leaves normal non-empty parsing unchanged. Snapshot freshness handling now treats unverifiable age as untrusted, not fresh. - Security: I do not see a remaining empty-context bypass: whitespace/all-comma parse to [] and raise. I also do not see a forged/stale-ts snapshot trust path: absent or malformed ts is discarded, and old parseable ts is still stale-filtered. - Performance: no meaningful impact; the added checks are constant-time and self-fetch is already the cache-miss path. - Readability: the error class and tests make the fail-closed contract explicit. CI notes: Platform(Go) and CI/all-required are green. Ops Scripts is red in unrelated test_sop_checklist.py tuple/list expectations, not in the changed merge-queue tests.
agent-reviewer-cr2 approved these changes 2026-06-24 09:17:50 +00:00
agent-reviewer-cr2 left a comment
Member

APPROVED on d9b63d1b.

5-axis/security review: Correctness: empty/blank/all-comma PUSH_REQUIRED_CONTEXTS now raises PushRequiredContextsUnavailable instead of letting required_contexts_green(..., []) pass vacuously, so the main-green backstop cannot silently disappear. The exception is an ApiError, so the conductor tick holds/no-merges rather than continuing. Conductor snapshots with absent/empty/unparseable ts are discarded and self-fetch live state instead of being trusted as fresh; fresh dated snapshots still work. Robustness: preserves the existing self-fetch fallback and happy-path snapshot use. Security: strictly tightens merge authorization; no new network/secrets surface. Performance: only snapshot parsing/env parsing checks. Readability: comments/tests clearly pin the fail-closed invariants.

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

APPROVED on d9b63d1b. 5-axis/security review: Correctness: empty/blank/all-comma PUSH_REQUIRED_CONTEXTS now raises PushRequiredContextsUnavailable instead of letting required_contexts_green(..., []) pass vacuously, so the main-green backstop cannot silently disappear. The exception is an ApiError, so the conductor tick holds/no-merges rather than continuing. Conductor snapshots with absent/empty/unparseable ts are discarded and self-fetch live state instead of being trusted as fresh; fresh dated snapshots still work. Robustness: preserves the existing self-fetch fallback and happy-path snapshot use. Security: strictly tightens merge authorization; no new network/secrets surface. Performance: only snapshot parsing/env parsing checks. Readability: comments/tests clearly pin the fail-closed invariants. Reviewed files: .gitea/scripts/gitea-merge-queue.py and .gitea/scripts/tests/test_gitea_merge_queue.py. CI/all-required and Platform(Go) are green; approval-gated contexts were red pending fresh pool/security approval at review time.
devops-engineer merged commit 528c43cf30 into main 2026-06-24 09:18:02 +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#3224