0bd595c5dd
ci-required-drift / drift (pull_request) Has been cancelled
Block integration-tester contamination artifacts / Block staging-trigger / invalid manifest contamination (pull_request) Successful in 11s
CI / Python Lint & Test (pull_request) Successful in 10s
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 12s
E2E Peer Visibility (literal MCP list_peers) / detect-changes (pull_request) Successful in 14s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 16s
Harness Replays / detect-changes (pull_request) Successful in 22s
Lint curl status-code capture / Scan workflows for curl status-capture pollution (pull_request) Successful in 29s
lint-bp-context-emit-match / lint-bp-context-emit-match (pull_request) Failing after 37s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 32s
lint-continue-on-error-tracking / lint-continue-on-error-tracking (pull_request) Successful in 44s
Lint forbidden tenant-env keys / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 30s
Lint forbidden hand-written mcp__ tool-id literals / Scan for hand-written mcp__ tool-id literals (pull_request) Successful in 13s
lint-no-coe-on-required / lint-no-coe-on-required (pull_request) Successful in 22s
Lint publish-runner timeout-minutes / Lint publish-runner timeout-minutes (pull_request) Successful in 16s
lint-required-workflows-docker-host-pinned / Lint docker-host pin on docker-touching workflows (pull_request) Successful in 11s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 22s
Lint workflow YAML (Gitea-1.22.6-hostile shapes) / Lint workflow YAML for Gitea-1.22.6-hostile shapes (pull_request) Successful in 26s
E2E Chat / detect-changes (pull_request) Successful in 1m36s
CI / Detect changes (pull_request) Successful in 1m40s
Lint schedule budget / Zero-cron ratchet (pull_request) Successful in 27s
E2E API Smoke Test / detect-changes (pull_request) Successful in 1m39s
lint-staging-tenant-cd-gate-chain / lint-staging-tenant-cd-gate-chain (pull_request) Successful in 29s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 1m36s
lint-setup-go-cache / lint-setup-go-cache (pull_request) Successful in 30s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 12s
SECRET_PATTERNS drift lint / Detect SECRET_PATTERNS drift (pull_request) Successful in 19s
gate-check-v3 / gate-check (pull_request_target) Successful in 16s
sop-checklist / review-refire (pull_request_target) Has been skipped
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)
lint-mask-pr-atomicity / lint-mask-pr-atomicity (pull_request) Successful in 1m27s
sop-checklist / all-items-acked (pull_request_target) Successful in 7s
Lint pre-flip continue-on-error / Verify continue-on-error flips have run-log proof (pull_request) Successful in 1m11s
Local Provision Lifecycle E2E / Local Provision Lifecycle E2E (stub) (pull_request) Successful in 44s
lint-required-context-exists-in-bp / lint-required-context-exists-in-bp (pull_request) Successful in 1m15s
template-delivery-e2e / detect-changes (pull_request) Successful in 34s
Ops Scripts Tests / Ops scripts (unittest) (pull_request) Successful in 34s
PR Diff Guard / PR diff guard (pull_request) Successful in 35s
E2E Peer Visibility (literal MCP list_peers) / E2E Peer Visibility (local) (pull_request) Has been skipped
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 3s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 2s
CI / Platform (Go) (pull_request) Successful in 3s
CI / Canvas (Next.js) (pull_request) Successful in 3s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 4s
E2E Chat / E2E Chat (pull_request) Successful in 4s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 4s
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 Peer Visibility (literal MCP list_peers) / E2E Peer Visibility (pull_request) Successful in 12s
CI / Canvas Deploy Status (pull_request) Successful in 2s
CI / all-required (pull_request) Successful in 5s
Local Provision Lifecycle E2E / Local Provision Lifecycle E2E (real image + MiniMax LLM, advisory) (pull_request) Successful in 32s
Harness Replays / Harness Replays (pull_request) Successful in 1m17s
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 9s
reserved-path-review / reserved-path-review (pull_request_target) Approved via pull_request_review trigger
security-review / approved (pull_request_review) Successful in 8s
reserved-path-review / reserved-path-review (pull_request_review) Successful in 10s
gitea-merge-queue / queue (pull_request_review) Has been cancelled
Root cause: `lint-continue-on-error-tracking` (Tier 2e) requires every job-level `continue-on-error: true` to reference an OPEN tracker <=14 days old (forced 14-day renewal cadence). The shared tracker mc#2654 (created 2026-06-12) is now CLOSED and 22 days old, and 20 masks across 16 workflows still resolved to it -> every one violated the lint -> the whole check went RED. Verified NOT the operator's hypothesised leak: no BP-required status context carries continue-on-error (none of the 7 required emitters —CI/all-required, E2E API Smoke Test, Handlers Postgres Integration, Secret scan, qa-review/approved, security-review/approved, reserved-path-review— is in the continue-on-error set). And the lint's own API read works. The sole cause is the stale/closed shared tracker. Fix (the renewal mechanism the lint is designed to enforce, done NON-silently): filed a fresh, itemised umbrella tracker mc#3436 (open, today) that enumerates every remaining mask + the burn-down plan, and surgically repointed each mc#2654-resolved tracker comment to mc#3436 using the lint's exact +/-2-line first-match resolution (prose mentions of mc#2654 left intact as history; ci.yml untouched to avoid tripping lint-mask-pr-atomicity; per-job trackers already migrated —mc#3230 lifecycle-real, mc#3232 design-token-drift-gate, mc#3244 prune-stale-e2e-dns— left as-is). Verification (vs live Gitea): all 23 continue-on-error:true directives now resolve to an open, <=14d tracker -> 0 violations -> genuine green. Comment-only changes; all edited YAML still parses; lint script + its unit tests unchanged. Tracking / burn-down: mc#3436 (renews 2026-07-18). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
156 lines
7.0 KiB
YAML
156 lines
7.0 KiB
YAML
# gate-check-v3 — automated PR gate detector
|
|
#
|
|
# Runs on every open PR (push/synchronize) and hourly via cron.
|
|
# Posts a structured [gate-check-v3] STATUS: comment on the PR.
|
|
#
|
|
# Inputs:
|
|
# PR_NUMBER — set via ${{ github.event.pull_request.number }} from the trigger
|
|
# POST_COMMENT — "true" to post/update comment on PR
|
|
#
|
|
# Gating logic (MVP signals 1,2,3,4,6):
|
|
# 1. Author-aware agent-tag comment scan
|
|
# 2. REQUEST_CHANGES reviews state machine
|
|
# 3. Staleness detection (SOP-12: review.commit_id != PR.head_sha + >1 working day)
|
|
# 4. Branch divergence / scope-creep guard (base-sha vs target HEAD; mc#365)
|
|
# 6. CI required-checks awareness
|
|
#
|
|
# Exit code: 0=CLEAR, 1=BLOCKED, 2=ERROR
|
|
|
|
name: gate-check-v3
|
|
|
|
on:
|
|
pull_request_target:
|
|
types: [opened, edited, synchronize, reopened]
|
|
# NOTE: `workflow_dispatch.inputs` block intentionally omitted.
|
|
# Gitea 1.22.6 parser rejects `workflow_dispatch.inputs.X` with
|
|
# "unknown on type" — it mis-treats the inputs sub-keys as top-level
|
|
# `on:` event types. Dropping the inputs block restores parsing.
|
|
# Manual dispatch from the Gitea UI works without the inputs schema
|
|
# (github.event.inputs.X returns empty); the script falls back to
|
|
# iterating all open PRs when PR_NUMBER is empty.
|
|
workflow_dispatch:
|
|
|
|
# Serialize per PR (or per repo for schedule/manual ticks) to prevent
|
|
# the fan-out OOM class documented in
|
|
# `reference_operator_host_python3_oom_storm_2026_05_18`. `edited`
|
|
# events fan out on every PR-body edit; combined with the hourly cron
|
|
# and synchronize bursts this workflow can stack runs of the same
|
|
# workflow_id on the same PR (each ~4GB anon-RSS) and trip the
|
|
# `--memory=4g --memory-swap=8g` per-container cap.
|
|
#
|
|
# NO `cancel-in-progress` (defaults to false). Per
|
|
# `feedback_janitor_supersede_must_group_by_workflow_id`, cancelling
|
|
# in-flight runs of any required-check-shaped workflow risks the
|
|
# dismiss_stale_approvals + empty-commit-rerun dance (Gitea 1.22.6 has
|
|
# no REST rerun). The gate-check is `continue-on-error: true` +
|
|
# idempotent (POST/PATCH gate-check comment by context) so sequential
|
|
# ticks are strictly safe.
|
|
concurrency:
|
|
group: gate-check-v3-${{ github.event.pull_request.number || github.event.issue.number || github.ref }}
|
|
|
|
permissions:
|
|
# read: contents — for checkout (base ref, not PR head for security)
|
|
# read: pull-requests — for reading PR info via API
|
|
# write: pull-requests — for posting/updating gate-check comments
|
|
# Without this the token cannot POST/PATCH /issues/comments → 403.
|
|
contents: read
|
|
pull-requests: write
|
|
|
|
env:
|
|
GITHUB_SERVER_URL: https://git.moleculesai.app
|
|
|
|
jobs:
|
|
# bp-exempt: PR advisory bot; merge blocking is enforced by CI status and branch protection.
|
|
gate-check:
|
|
runs-on: ubuntu-latest
|
|
# Hard bound on a hung-while-running run so the advisory context
|
|
# CONCLUDES (continue-on-error: true -> ignored) instead of
|
|
# hanging unconcluded and 405-blocking the Gitea merge-check.
|
|
# Normal conclude-time is ~12-16s, so 10m is a 30x safety margin
|
|
# over a stuck Python tick. Does NOT fix a QUEUED-never-picked-up
|
|
# orphan (operator-side run-clearing needed for that).
|
|
timeout-minutes: 10
|
|
# mc#3436: pre-existing continue-on-error mask; root-fix and remove, do not renew silently.
|
|
continue-on-error: true # Never block on our own detector failing
|
|
steps:
|
|
- name: Check out BASE ref (never PR-head under pull_request_target)
|
|
# pull_request_target runs with repo secrets-context, so checking out
|
|
# the PR HEAD would execute PR-branch gate_check.py with secrets.
|
|
# Fix: always load gate_check.py from the trusted base/default ref.
|
|
# Bug-1 (self-loop exclusion) + Bug-3 (403→exit0) from #547 are
|
|
# kept; only this checkout-ref regresses to pre-#547 behavior.
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
ref: ${{ github.event.pull_request.base.sha || github.ref_name }}
|
|
|
|
- name: Run gate-check-v3 (single PR mode)
|
|
if: github.event_name == 'pull_request_target' || github.event.inputs.pr_number != ''
|
|
env:
|
|
GITEA_TOKEN: ${{ secrets.SOP_CHECKLIST_GATE_TOKEN }}
|
|
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
|
PR_NUMBER: ${{ github.event.pull_request.number || github.event.inputs.pr_number }}
|
|
POST_COMMENT: ${{ github.event.inputs.post_comment || 'true' }}
|
|
run: |
|
|
set -euo pipefail
|
|
python3 tools/gate-check-v3/gate_check.py \
|
|
--repo "${{ github.repository }}" \
|
|
--pr "$PR_NUMBER" \
|
|
$([ "$POST_COMMENT" = "true" ] && echo "--post-comment")
|
|
echo "verdict=$?" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Run gate-check-v3 (all open PRs — cron mode)
|
|
if: github.event_name == 'schedule'
|
|
env:
|
|
GITEA_TOKEN: ${{ secrets.SOP_CHECKLIST_GATE_TOKEN }}
|
|
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
|
REPO: ${{ github.repository }}
|
|
run: |
|
|
set -euo pipefail
|
|
# Fetch all open PRs and run gate-check on each. This scheduled
|
|
# refresher is advisory; a transient Gitea list timeout must not turn
|
|
# main red. PR-specific gate-check runs still use normal failure
|
|
# semantics.
|
|
pr_numbers=$(python3 <<'PY'
|
|
import json
|
|
import os
|
|
import socket
|
|
import sys
|
|
import time
|
|
import urllib.error
|
|
import urllib.request
|
|
|
|
socket.setdefaulttimeout(30)
|
|
token = os.environ["GITEA_TOKEN"]
|
|
repo = os.environ["REPO"]
|
|
url = f"https://git.moleculesai.app/api/v1/repos/{repo}/pulls?state=open&limit=100"
|
|
last_error = None
|
|
for attempt in range(1, 4):
|
|
req = urllib.request.Request(
|
|
url,
|
|
headers={"Authorization": f"token {token}", "Accept": "application/json", "User-Agent": "molecule-ci-gate/1.0 (+gitea-api)"}, # non-urllib UA: CF WAF 1010-bans Python-urllib
|
|
)
|
|
try:
|
|
with urllib.request.urlopen(req, timeout=30) as r:
|
|
prs = json.loads(r.read())
|
|
break
|
|
except (TimeoutError, OSError, urllib.error.URLError, urllib.error.HTTPError) as exc:
|
|
last_error = exc
|
|
print(f"warning: PR list fetch attempt {attempt}/3 failed: {exc}", file=sys.stderr)
|
|
if attempt < 3:
|
|
time.sleep(2 * attempt)
|
|
else:
|
|
print(f"warning: skipped scheduled gate-check refresh; failed to list open PRs after 3 attempts: {last_error}", file=sys.stderr)
|
|
raise SystemExit(0)
|
|
for pr in prs:
|
|
print(pr["number"])
|
|
PY
|
|
)
|
|
for pr in $pr_numbers; do
|
|
echo "Checking PR #$pr..."
|
|
python3 tools/gate-check-v3/gate_check.py \
|
|
--repo "${{ github.repository }}" \
|
|
--pr "$pr" \
|
|
--post-comment \
|
|
|| true
|
|
done
|