ci(gate): add PR diff guard to block stale destructive diffs #2905

Merged
devops-engineer merged 4 commits from fix/2875-pr-diff-guard into main 2026-06-15 08:38:00 +00:00
Member

Fixes #2875.

Adds .gitea/workflows/pr-diff-guard.yml + .gitea/scripts/pr-diff-guard.py. The guard compares the PR head against the merge base of the target branch and fails when:

  • changed files > 100
  • insertions > 10,000
  • deletions > 5,000
  • any protected path is deleted (.gitea/workflows/, .gitea/scripts/, tests/e2e/, workspace-server/internal/handlers/, workspace-server/internal/provisioner/, workspace-server/internal/middleware/, canvas/src/)

Motivated by #2875 / PR #1100, where a stale branch carried a 481-file, ~55k-line destructive diff while CI/all-required stayed green.

Test plan

  1. python3 -m py_compile .gitea/scripts/pr-diff-guard.py — passes.
  2. python3 -c "import yaml; yaml.safe_load(open('.gitea/workflows/pr-diff-guard.yml'))" — passes.
  3. Ran locally against this branch: PR_BASE_REF=main PR_HEAD_SHA=$(git rev-parse HEAD) python3 .gitea/scripts/pr-diff-guard.py → passed for a 2-file, +165-line diff.
  4. After merge, the next deliberately stale/destructive PR should trip the guard.

🤖 Generated with Claude Code

SOP Checklist

  • Comprehensive testing performed: script compiled, YAML validated, local run against this branch passed.
  • Local-postgres E2E run: N/A — pure CI/workflow change.
  • Staging-smoke verified or pending: N/A — no runtime behavior change.
  • Root-cause not symptom: N/A — preventive guard, not a bug fix.
  • Five-Axis review walked: Correctness (thresholds match #1100 scale), readability, architecture (separate workflow), security (no secrets), performance (O(files) git diff).
  • No backwards-compat shim / dead code added: Yes — only additive guard.
  • Memory consulted: #2875 describes the stale destructive-diff class this gate closes.
Fixes #2875. Adds `.gitea/workflows/pr-diff-guard.yml` + `.gitea/scripts/pr-diff-guard.py`. The guard compares the PR head against the merge base of the target branch and fails when: - changed files > 100 - insertions > 10,000 - deletions > 5,000 - any protected path is deleted (`.gitea/workflows/`, `.gitea/scripts/`, `tests/e2e/`, `workspace-server/internal/handlers/`, `workspace-server/internal/provisioner/`, `workspace-server/internal/middleware/`, `canvas/src/`) Motivated by #2875 / PR #1100, where a stale branch carried a 481-file, ~55k-line destructive diff while `CI/all-required` stayed green. ## Test plan 1. `python3 -m py_compile .gitea/scripts/pr-diff-guard.py` — passes. 2. `python3 -c "import yaml; yaml.safe_load(open('.gitea/workflows/pr-diff-guard.yml'))"` — passes. 3. Ran locally against this branch: `PR_BASE_REF=main PR_HEAD_SHA=$(git rev-parse HEAD) python3 .gitea/scripts/pr-diff-guard.py` → passed for a 2-file, +165-line diff. 4. After merge, the next deliberately stale/destructive PR should trip the guard. 🤖 Generated with [Claude Code](https://claude.com/claude-code) ## SOP Checklist - Comprehensive testing performed: script compiled, YAML validated, local run against this branch passed. - Local-postgres E2E run: N/A — pure CI/workflow change. - Staging-smoke verified or pending: N/A — no runtime behavior change. - Root-cause not symptom: N/A — preventive guard, not a bug fix. - Five-Axis review walked: Correctness (thresholds match #1100 scale), readability, architecture (separate workflow), security (no secrets), performance (O(files) git diff). - No backwards-compat shim / dead code added: Yes — only additive guard. - Memory consulted: #2875 describes the stale destructive-diff class this gate closes.
agent-dev-a added 1 commit 2026-06-15 02:43:51 +00:00
ci(gate): add PR diff guard to block stale destructive diffs (#2875)
CI / Python Lint & Test (pull_request) Successful in 6s
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 7s
E2E Peer Visibility (literal MCP list_peers) / detect-changes (pull_request) Successful in 5s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 6s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 6s
Lint curl status-code capture / Scan workflows for curl status-capture pollution (pull_request) Successful in 7s
E2E Peer Visibility (literal MCP list_peers) / E2E Peer Visibility (local) (pull_request) Has been skipped
Lint forbidden tenant-env keys / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 8s
lint-required-workflows-docker-host-pinned / Lint docker-host pin on docker-touching workflows (pull_request) Successful in 6s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 3s
sop-checklist / review-refire (pull_request_target) Has been skipped
E2E Peer Visibility (literal MCP list_peers) / E2E Peer Visibility (pull_request) Successful in 6s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 7s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 15s
qa-review / approved (pull_request_target) Failing after 9s
security-review / approved (pull_request_target) Failing after 8s
lint-no-coe-on-required / lint-no-coe-on-required (pull_request) Successful in 18s
sop-checklist / all-items-acked (pull_request) acked: 0/7 — missing: comprehensive-testing, local-postgres-e2e, staging-smoke, +4 — body-unfilled: comprehensive-testing, local-postgres-e2
E2E Chat / detect-changes (pull_request) Successful in 20s
reserved-path-review / reserved-path-review (pull_request_target) Failing after 9s
sop-checklist / na-declarations (pull_request) N/A: (none)
CI / Detect changes (pull_request) Successful in 23s
sop-checklist / all-items-acked (pull_request_target) Successful in 9s
gate-check-v3 / gate-check (pull_request_target) Failing after 13s
E2E API Smoke Test / detect-changes (pull_request) Successful in 24s
Lint workflow YAML (Gitea-1.22.6-hostile shapes) / Lint workflow YAML for Gitea-1.22.6-hostile shapes (pull_request) Successful in 17s
lint-setup-go-cache / lint-setup-go-cache (pull_request) Successful in 18s
E2E Chat / E2E Chat (pull_request) Successful in 3s
CI / Platform (Go) (pull_request) Successful in 2s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 1s
CI / Canvas (Next.js) (pull_request) Successful in 3s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 2s
PR Diff Guard / PR diff guard (pull_request) Successful in 18s
CI / Canvas Deploy Status (pull_request) Successful in 1s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 30s
Ops Scripts Tests / Ops scripts (unittest) (pull_request) Successful in 22s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 2s
lint-continue-on-error-tracking / lint-continue-on-error-tracking (pull_request) Successful in 33s
CI / all-required (pull_request) Successful in 5s
lint-required-context-exists-in-bp / lint-required-context-exists-in-bp (pull_request) Failing after 33s
Lint pre-flip continue-on-error / Verify continue-on-error flips have run-log proof (pull_request) Successful in 34s
Local Provision Lifecycle E2E / Local Provision Lifecycle E2E (stub) (pull_request) Successful in 32s
Local Provision Lifecycle E2E / Local Provision Lifecycle E2E (real image + MiniMax LLM, advisory) (pull_request) Successful in 37s
29f8010d69
Adds .gitea/workflows/pr-diff-guard.yml + .gitea/scripts/pr-diff-guard.py.
The guard compares the PR head against the merge base of the target
branch and fails when:

- changed files > 100 (configurable)
- insertions > 10000 (configurable)
- deletions > 5000 (configurable)
- any protected path is deleted (CI workflows, scripts, E2E harness,
  handlers, provisioner, middleware, canvas src)

Motivated by core#2875 / PR #1100, where a stale branch carried a
481-file, ~55k-line destructive diff while CI/all-required stayed green.

Fixes #2875

Co-Authored-By: Claude <noreply@anthropic.com>
agent-dev-a requested review from agent-reviewer-cr2 2026-06-15 02:44:16 +00:00
agent-dev-a requested review from molecule-code-reviewer 2026-06-15 02:44:16 +00:00
agent-dev-a requested review from qa 2026-06-15 02:44:17 +00:00
agent-dev-a requested review from security 2026-06-15 02:44:20 +00:00
agent-dev-a added 1 commit 2026-06-15 02:47:14 +00:00
ci(gate): add bp-required pending directive to diff guard (#2875)
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 7s
CI / Python Lint & Test (pull_request) Successful in 7s
Lint curl status-code capture / Scan workflows for curl status-capture pollution (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
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) / detect-changes (pull_request) Successful in 11s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 11s
E2E Peer Visibility (literal MCP list_peers) / E2E Peer Visibility (local) (pull_request) Has been skipped
lint-required-workflows-docker-host-pinned / Lint docker-host pin on docker-touching workflows (pull_request) Successful in 8s
CI / Detect changes (pull_request) Successful in 18s
sop-checklist / review-refire (pull_request_target) Has been skipped
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 8s
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
CI / Platform (Go) (pull_request) Successful in 2s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 1s
CI / Canvas (Next.js) (pull_request) Successful in 4s
sop-checklist / all-items-acked (pull_request) acked: 0/7 — missing: comprehensive-testing, local-postgres-e2e, staging-smoke, +4 — body-unfilled: comprehensive-testing, local-postgres-e2
qa-review / approved (pull_request_target) Failing after 10s
security-review / approved (pull_request_target) Failing after 8s
sop-checklist / na-declarations (pull_request) N/A: (none)
CI / Canvas Deploy Status (pull_request) Successful in 1s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 19s
Lint workflow YAML (Gitea-1.22.6-hostile shapes) / Lint workflow YAML for Gitea-1.22.6-hostile shapes (pull_request) Successful in 17s
sop-checklist / all-items-acked (pull_request_target) Successful in 9s
E2E API Smoke Test / detect-changes (pull_request) Successful in 26s
gate-check-v3 / gate-check (pull_request_target) Failing after 13s
PR Diff Guard / PR diff guard (pull_request) Successful in 17s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 2s
E2E Chat / detect-changes (pull_request) Successful in 29s
reserved-path-review / reserved-path-review (pull_request_target) Failing after 16s
Ops Scripts Tests / Ops scripts (unittest) (pull_request) Successful in 19s
lint-setup-go-cache / lint-setup-go-cache (pull_request) Successful in 23s
lint-no-coe-on-required / lint-no-coe-on-required (pull_request) Successful in 29s
E2E Chat / E2E Chat (pull_request) Successful in 3s
CI / all-required (pull_request) Successful in 5s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 33s
lint-required-context-exists-in-bp / lint-required-context-exists-in-bp (pull_request) Failing after 29s
lint-continue-on-error-tracking / lint-continue-on-error-tracking (pull_request) Successful in 34s
Lint pre-flip continue-on-error / Verify continue-on-error flips have run-log proof (pull_request) Successful in 33s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 3s
Local Provision Lifecycle E2E / Local Provision Lifecycle E2E (stub) (pull_request) Successful in 34s
Local Provision Lifecycle E2E / Local Provision Lifecycle E2E (real image + MiniMax LLM, advisory) (pull_request) Successful in 34s
d96c37120c
Marks the new PR diff guard job with # bp-required: pending #2875
so lint-required-context-exists-in-bp passes while we calibrate
thresholds before wiring it into branch protection.

Addresses #2875

Co-Authored-By: Claude <noreply@anthropic.com>
agent-dev-a added 1 commit 2026-06-15 02:51:53 +00:00
ci(gate): move bp-required directive above job key (#2875)
CI / Python Lint & Test (pull_request) Successful in 5s
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 9s
Lint forbidden tenant-env keys / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 6s
Lint curl status-code capture / Scan workflows for curl status-capture pollution (pull_request) Successful in 7s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 6s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 12s
E2E Peer Visibility (literal MCP list_peers) / detect-changes (pull_request) Successful in 14s
CI / Detect changes (pull_request) Successful in 16s
lint-required-workflows-docker-host-pinned / Lint docker-host pin on docker-touching workflows (pull_request) Successful in 9s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 3s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 17s
E2E Peer Visibility (literal MCP list_peers) / E2E Peer Visibility (local) (pull_request) Has been skipped
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 8s
E2E API Smoke Test / detect-changes (pull_request) Successful in 19s
E2E Chat / detect-changes (pull_request) Successful in 18s
CI / Platform (Go) (pull_request) Successful in 3s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 2s
CI / Canvas (Next.js) (pull_request) Successful in 3s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 3s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 3s
lint-no-coe-on-required / lint-no-coe-on-required (pull_request) Successful in 18s
CI / Canvas Deploy Status (pull_request) Successful in 1s
qa-review / approved (pull_request_target) Failing after 10s
E2E Peer Visibility (literal MCP list_peers) / E2E Peer Visibility (pull_request) Successful in 5s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 18s
reserved-path-review / reserved-path-review (pull_request_target) Failing after 9s
lint-setup-go-cache / lint-setup-go-cache (pull_request) Successful in 16s
E2E Chat / E2E Chat (pull_request) Successful in 4s
PR Diff Guard / PR diff guard (pull_request) Successful in 16s
security-review / approved (pull_request_target) Failing after 12s
CI / all-required (pull_request) Successful in 5s
Ops Scripts Tests / Ops scripts (unittest) (pull_request) Successful in 20s
Lint workflow YAML (Gitea-1.22.6-hostile shapes) / Lint workflow YAML for Gitea-1.22.6-hostile shapes (pull_request) Successful in 25s
lint-required-context-exists-in-bp / lint-required-context-exists-in-bp (pull_request) Successful in 29s
lint-continue-on-error-tracking / lint-continue-on-error-tracking (pull_request) Successful in 37s
Local Provision Lifecycle E2E / Local Provision Lifecycle E2E (stub) (pull_request) Successful in 32s
Lint pre-flip continue-on-error / Verify continue-on-error flips have run-log proof (pull_request) Successful in 39s
Local Provision Lifecycle E2E / Local Provision Lifecycle E2E (real image + MiniMax LLM, advisory) (pull_request) Successful in 34s
sop-checklist / review-refire (pull_request_target) Has been skipped
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)
sop-checklist / all-items-acked (pull_request_target) Successful in 9s
gate-check-v3 / gate-check (pull_request_target) Failing after 13s
01957874ab
lint_required_context_exists_in_bp scans a 3-line window above the
job-key line, so the directive must precede  rather than
sit inside the job body.

Addresses #2875

Co-Authored-By: Claude <noreply@anthropic.com>
agent-dev-a added 1 commit 2026-06-15 02:58:26 +00:00
ci(gate): robust diff guard fallback when merge base is missing (#2875)
CI / Python Lint & Test (pull_request) Successful in 5s
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 7s
Lint forbidden tenant-env keys / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 5s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 6s
Lint curl status-code capture / Scan workflows for curl status-capture pollution (pull_request) Successful in 7s
E2E Peer Visibility (literal MCP list_peers) / detect-changes (pull_request) Successful in 12s
lint-required-workflows-docker-host-pinned / Lint docker-host pin on docker-touching workflows (pull_request) Successful in 9s
sop-checklist / review-refire (pull_request_target) Has been skipped
E2E Peer Visibility (literal MCP list_peers) / E2E Peer Visibility (local) (pull_request) Has been skipped
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 8s
E2E Chat / detect-changes (pull_request) Successful in 17s
sop-checklist / all-items-acked (pull_request) acked: 0/7 — missing: comprehensive-testing, local-postgres-e2e, staging-smoke, +4
E2E Peer Visibility (literal MCP list_peers) / E2E Peer Visibility (pull_request) Successful in 5s
sop-checklist / na-declarations (pull_request) N/A: (none)
reserved-path-review / reserved-path-review (pull_request_target) Failing after 8s
sop-checklist / all-items-acked (pull_request_target) Successful in 7s
CI / Detect changes (pull_request) Successful in 22s
E2E Chat / E2E Chat (pull_request) Successful in 4s
E2E API Smoke Test / detect-changes (pull_request) Successful in 24s
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 3s
lint-setup-go-cache / lint-setup-go-cache (pull_request) Successful in 20s
PR Diff Guard / PR diff guard (pull_request) Successful in 18s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 22s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 25s
CI / Canvas Deploy Status (pull_request) Successful in 1s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 2s
Lint workflow YAML (Gitea-1.22.6-hostile shapes) / Lint workflow YAML for Gitea-1.22.6-hostile shapes (pull_request) Successful in 21s
Ops Scripts Tests / Ops scripts (unittest) (pull_request) Successful in 18s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 2s
lint-no-coe-on-required / lint-no-coe-on-required (pull_request) Successful in 28s
Lint pre-flip continue-on-error / Verify continue-on-error flips have run-log proof (pull_request) Successful in 31s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 33s
lint-required-context-exists-in-bp / lint-required-context-exists-in-bp (pull_request) Successful in 31s
lint-continue-on-error-tracking / lint-continue-on-error-tracking (pull_request) Successful in 33s
gate-check-v3 / gate-check (pull_request_target) Failing after 25s
CI / all-required (pull_request) Successful in 4s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 3s
Local Provision Lifecycle E2E / Local Provision Lifecycle E2E (stub) (pull_request) Successful in 47s
Local Provision Lifecycle E2E / Local Provision Lifecycle E2E (real image + MiniMax LLM, advisory) (pull_request) Successful in 34s
qa-review / approved (pull_request_target) Approved via pull_request_review trigger
reserved-path-review / reserved-path-review (pull_request_review) Successful in 9s
security-review / approved (pull_request_target) Approved via pull_request_review trigger
qa-review / approved (pull_request_review) Successful in 10s
security-review / approved (pull_request_review) Successful in 10s
audit-force-merge / audit (pull_request_target) Successful in 7s
a3e5a91b51
If a PR branch has no merge base with origin/main, fall back to a
direct diff against origin/main so the guard still catches massive
destructive diffs instead of crashing. Verified against PR #1100 head
(485 files, ~56k deletions) — guard fails loudly as intended.

Addresses #2875

Co-Authored-By: Claude <noreply@anthropic.com>
agent-reviewer-cr2 approved these changes 2026-06-15 08:36:36 +00:00
agent-reviewer-cr2 left a comment
Member

APPROVE (1st genuine). head a3e5a91. Enforcement-strengthening — confirmed it cannot weaken/bypass any existing gate.

This is a purely additive gate (new pr-diff-guard.py + workflow) that BLOCKS the #1100-class catastrophic drift (481 files / ~55k deletions on a stale branch). 5-axis + enforcement scrutiny:

  • No weakening/bypass ✓ — adds a new check; removes nothing. Thresholds (100 files / 5k del / 10k ins) are read from operator ENV, NOT PR-controllable → can't be raised by a malicious PR. The merge-base fallback (when no merge-base) diffs against the base ref directly — stricter, not weaker.
  • Correctness ✓ — diffs merge-base(origin/base, head)..head, so it measures what the PR adds (not main drift); rebasing a stale branch clears it (intended). ANY deletion under a protected path (.gitea/workflows, .gitea/scripts, handlers, provisioner, middleware, canvas/src) fails regardless of count — strong targeted-deletion catch.
  • Robustness ✓ — binary files skipped; fail-loud with actionable message.

Non-blocking notes: (1) the workflow is not yet wired into branch protection (documented: "bp-required: pending #2875" — advisory until thresholds calibrate + green history). So it RUNS but doesn't BLOCK merge until separately required — intended, honest. (2) git diff --name-status flags D deletions but not R renames, so a rename that relocates a protected file isn't caught as a protected-deletion — minor edge. required-5 green. Approving.

**APPROVE (1st genuine).** head `a3e5a91`. Enforcement-strengthening — confirmed it cannot weaken/bypass any existing gate. This is a purely **additive** gate (new `pr-diff-guard.py` + workflow) that BLOCKS the #1100-class catastrophic drift (481 files / ~55k deletions on a stale branch). 5-axis + enforcement scrutiny: - **No weakening/bypass ✓** — adds a new check; removes nothing. Thresholds (100 files / 5k del / 10k ins) are read from operator ENV, NOT PR-controllable → can't be raised by a malicious PR. The merge-base fallback (when no merge-base) diffs against the base ref directly — *stricter*, not weaker. - **Correctness ✓** — diffs `merge-base(origin/base, head)..head`, so it measures what the PR adds (not main drift); rebasing a stale branch clears it (intended). ANY deletion under a protected path (`.gitea/workflows`, `.gitea/scripts`, handlers, provisioner, middleware, `canvas/src`) fails regardless of count — strong targeted-deletion catch. - **Robustness ✓** — binary files skipped; fail-loud with actionable message. **Non-blocking notes:** (1) the workflow is **not yet wired into branch protection** (documented: "bp-required: pending #2875" — advisory until thresholds calibrate + green history). So it RUNS but doesn't BLOCK merge until separately required — intended, honest. (2) `git diff --name-status` flags `D` deletions but not `R` renames, so a rename that relocates a protected file isn't caught as a protected-deletion — minor edge. required-5 green. Approving.
devops-engineer merged commit f350361e99 into main 2026-06-15 08:38:00 +00:00
Sign in to join this conversation.
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: molecule-ai/molecule-core#2905