feat(2403): complete SOP tier removal — salvage non-tier fixes + zero tier refs #2419
@@ -165,7 +165,7 @@ def api(
|
||||
# Format: "<workflow_name> / <job_name_or_key> (<event>)"
|
||||
# Examples observed on molecule-core/main:
|
||||
# "Secret scan / Scan diff for credential-shaped strings (pull_request)"
|
||||
# " / tier-check (pull_request)"
|
||||
# "sop-checklist / all-items-acked (pull_request)"
|
||||
#
|
||||
# Split strategy: peel off the trailing ` (<event>)` first, then split
|
||||
# the leading `<workflow> / <rest>` on the FIRST ` / ` (workflow names
|
||||
|
||||
@@ -50,15 +50,15 @@ class TestQaReviewDirectTrigger:
|
||||
"pull_request_review must include 'submitted' type"
|
||||
)
|
||||
|
||||
def test_job_guard_requires_approved_state(self):
|
||||
def test_job_guard_has_no_review_state_check(self):
|
||||
wf = load_workflow("qa-review.yml")
|
||||
guard = _job_guard_string(wf)
|
||||
assert "github.event.review.state == 'APPROVED'" in guard, (
|
||||
"job guard must check review.state for 'APPROVED'"
|
||||
)
|
||||
assert "github.event.review.state == 'approved'" in guard, (
|
||||
"job guard must check review.state for 'approved' (case fallback per #2135)"
|
||||
assert "github.event.review.state" not in guard, (
|
||||
"job guard must NOT check review.state (#2159: Gitea 1.22.6 payload unreliable); "
|
||||
"evaluator (review-check.sh) verifies actual APPROVE via API"
|
||||
)
|
||||
assert "github.event_name == 'pull_request_target'" in guard
|
||||
assert "github.event_name == 'pull_request_review'" in guard
|
||||
|
||||
def test_post_step_uses_status_post_token(self):
|
||||
wf = load_workflow("qa-review.yml")
|
||||
@@ -91,15 +91,15 @@ class TestSecurityReviewDirectTrigger:
|
||||
"pull_request_review must include 'submitted' type"
|
||||
)
|
||||
|
||||
def test_job_guard_requires_approved_state(self):
|
||||
def test_job_guard_has_no_review_state_check(self):
|
||||
wf = load_workflow("security-review.yml")
|
||||
guard = _job_guard_string(wf)
|
||||
assert "github.event.review.state == 'APPROVED'" in guard, (
|
||||
"job guard must check review.state for 'APPROVED'"
|
||||
)
|
||||
assert "github.event.review.state == 'approved'" in guard, (
|
||||
"job guard must check review.state for 'approved' (case fallback per #2135)"
|
||||
assert "github.event.review.state" not in guard, (
|
||||
"job guard must NOT check review.state (#2159: Gitea 1.22.6 payload unreliable); "
|
||||
"evaluator (review-check.sh) verifies actual APPROVE via API"
|
||||
)
|
||||
assert "github.event_name == 'pull_request_target'" in guard
|
||||
assert "github.event_name == 'pull_request_review'" in guard
|
||||
|
||||
def test_post_step_uses_status_post_token(self):
|
||||
wf = load_workflow("security-review.yml")
|
||||
@@ -153,7 +153,7 @@ class TestRefireTokenSeparation:
|
||||
"qa refire must receive STATUS_POST_TOKEN env var"
|
||||
)
|
||||
# Evaluator stays on read token
|
||||
assert "SOP_TIER_CHECK_TOKEN" in env.get("GITEA_TOKEN", "") or "GITHUB_TOKEN" in env.get("GITEA_TOKEN", ""), (
|
||||
assert "SOP_CHECKLIST_GATE_TOKEN" in env.get("GITEA_TOKEN", "") or "GITHUB_TOKEN" in env.get("GITEA_TOKEN", ""), (
|
||||
"qa refire evaluator must stay on read-scoped token"
|
||||
)
|
||||
|
||||
@@ -163,6 +163,6 @@ class TestRefireTokenSeparation:
|
||||
assert env.get("STATUS_POST_TOKEN") == "${{ secrets.STATUS_POST_TOKEN }}", (
|
||||
"security refire must receive STATUS_POST_TOKEN env var"
|
||||
)
|
||||
assert "SOP_TIER_CHECK_TOKEN" in env.get("GITEA_TOKEN", "") or "GITHUB_TOKEN" in env.get("GITEA_TOKEN", ""), (
|
||||
assert "SOP_CHECKLIST_GATE_TOKEN" in env.get("GITEA_TOKEN", "") or "GITHUB_TOKEN" in env.get("GITEA_TOKEN", ""), (
|
||||
"security refire evaluator must stay on read-scoped token"
|
||||
)
|
||||
|
||||
@@ -35,11 +35,33 @@ if grep -q '_is_tier_low_pending_ok' .gitea/scripts/gitea-merge-queue.py; then
|
||||
fi
|
||||
|
||||
# 5. No sop-tier-check context references in workflow YAML
|
||||
if grep -r 'sop-tier-check' .gitea/workflows/; then
|
||||
if grep -rI --exclude-dir='__pycache__' 'sop-tier-check' .gitea/workflows/; then
|
||||
echo "FAIL: sop-tier-check context reappeared in workflows" >&2
|
||||
fail=1
|
||||
fi
|
||||
|
||||
# 6. No SOP_TIER_CHECK_TOKEN references in workflow YAML or scripts
|
||||
if grep -rI --exclude-dir='__pycache__' --exclude='test_no_tier_regression.sh' 'SOP_TIER_CHECK_TOKEN' .gitea/workflows/ .gitea/scripts/; then
|
||||
echo "FAIL: SOP_TIER_CHECK_TOKEN reference reappeared (use SOP_CHECKLIST_GATE_TOKEN)" >&2
|
||||
fail=1
|
||||
fi
|
||||
|
||||
# 7. qa-review and security-review must have labeled/unlabeled triggers (#2139)
|
||||
for f in .gitea/workflows/qa-review.yml .gitea/workflows/security-review.yml; do
|
||||
if ! grep -q 'labeled, unlabeled' "$f"; then
|
||||
echo "FAIL: $f missing labeled/unlabeled triggers (#2139)" >&2
|
||||
fail=1
|
||||
fi
|
||||
done
|
||||
|
||||
# 8. qa-review and security-review must NOT have review.state guard (#2159)
|
||||
for f in .gitea/workflows/qa-review.yml .gitea/workflows/security-review.yml; do
|
||||
if grep -q 'github.event.review.state' "$f"; then
|
||||
echo "FAIL: $f has review.state guard reappeared (#2159)" >&2
|
||||
fail=1
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$fail" -eq 1 ]; then
|
||||
echo "TIER_REGRESSION_DETECTED" >&2
|
||||
exit 1
|
||||
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
- name: Detect force-merge + emit audit event
|
||||
env:
|
||||
# Same org-level secret the sop-checklist workflow uses.
|
||||
GITEA_TOKEN: ${{ secrets.SOP_TIER_CHECK_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
GITEA_TOKEN: ${{ secrets.SOP_CHECKLIST_GATE_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
GITEA_HOST: git.moleculesai.app
|
||||
REPO: ${{ github.repository }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
# Gitea persona whose ONLY job is reading branch_protections
|
||||
# and posting the [ci-drift] tracking issue. The endpoint
|
||||
# `GET /repos/.../branch_protections/{branch}` requires
|
||||
# repo-ADMIN role (Gitea 1.22.6) — SOP_TIER_CHECK_TOKEN and the
|
||||
# repo-ADMIN role (Gitea 1.22.6) — the default GITHUB_TOKEN and the
|
||||
# auto-injected GITHUB_TOKEN do NOT have it (read-only / write
|
||||
# without admin), so the previous fallback chain 403'd.
|
||||
# Mirrors the controlplane fix landed in CP PR#134.
|
||||
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
- 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_TIER_CHECK_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
GITEA_TOKEN: ${{ secrets.SOP_CHECKLIST_GATE_TOKEN || secrets.GITHUB_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' }}
|
||||
@@ -97,7 +97,7 @@ jobs:
|
||||
- name: Run gate-check-v3 (all open PRs — cron mode)
|
||||
if: github.event_name == 'schedule'
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.SOP_TIER_CHECK_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
GITEA_TOKEN: ${{ secrets.SOP_CHECKLIST_GATE_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
||||
REPO: ${{ github.repository }}
|
||||
run: |
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
# Forward-compat scope:
|
||||
# Today (2026-05-11) molecule-core/main protects 3 contexts:
|
||||
# - "Secret scan / Scan diff for credential-shaped strings (pull_request)"
|
||||
# - "sop-checklist / tier-check (pull_request)"
|
||||
# - "sop-checklist / all-items-acked (pull_request)"
|
||||
# - "CI / all-required (pull_request)"
|
||||
# Per RFC#324 Step 2 the required-list expands to ~5 contexts
|
||||
# (qa-review, security-review added). Each new required context's
|
||||
|
||||
@@ -7,18 +7,25 @@
|
||||
#
|
||||
# A1-α (refire mechanism):
|
||||
# Triggers on:
|
||||
# - `pull_request_target`: opened, synchronize, reopened
|
||||
# → initial status posts when PR opens / re-pushes
|
||||
# - `pull_request_target`: opened, synchronize, reopened, labeled, unlabeled
|
||||
# → initial status posts when PR opens / re-pushes, and re-evaluates
|
||||
# when labels change (e.g. risk-indicator labels).
|
||||
# - `pull_request_review` types: [submitted]
|
||||
# → re-evaluate when a team member submits an APPROVE review so
|
||||
# the gate flips immediately (no wait for the next push or
|
||||
# slash-command). Verified live: sop-checklist.yml uses this
|
||||
# same event and provably fires (produces
|
||||
# `sop-checklist / all-items-acked (pull_request_review)` contexts).
|
||||
# The job-level `if:` guard checks
|
||||
# `github.event.review.state == 'APPROVED' || 'approved'` so
|
||||
# only APPROVE reviews run the evaluator; COMMENT and
|
||||
# REQUEST_CHANGES are skipped at the job level.
|
||||
# The job-level `if:` does NOT guard on review.state (issue
|
||||
# #2159): Gitea 1.22.6's payload shape for this event does not
|
||||
# reliably expose the state field that the GitHub-style guard
|
||||
# expects. The evaluator (review-check.sh) reads actual reviews
|
||||
# from the API and checks for a real APPROVE, so running on
|
||||
# COMMENT or REQUEST_CHANGES is harmless (read-only,
|
||||
# idempotent). Branch-protection requires the
|
||||
# `(pull_request_target)` context variant, so the review-event
|
||||
# path EXPLICITLY POSTS the required context via the API. Trust
|
||||
# boundary preserved (BASE ref, no PR-head).
|
||||
# Branch-protection requires the `(pull_request_target)`
|
||||
# context variant, so the review-event path EXPLICITLY POSTS
|
||||
# the required context via the API. Trust boundary preserved
|
||||
@@ -96,7 +103,7 @@ name: qa-review
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, reopened]
|
||||
types: [opened, synchronize, reopened, labeled, unlabeled]
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
|
||||
@@ -110,13 +117,19 @@ jobs:
|
||||
approved:
|
||||
# Gate the job:
|
||||
# - On pull_request_target events: always run.
|
||||
# - On pull_request_review_approved events: run so the gate flips
|
||||
# immediately when a team member submits an APPROVE review.
|
||||
# - On pull_request_review events: always run. We do NOT guard on
|
||||
# review.state here because Gitea 1.22.6's payload shape for this
|
||||
# event does not reliably expose the state field (issue #2159).
|
||||
# The evaluator (review-check.sh) reads actual reviews from the
|
||||
# API and checks for a real APPROVE, so running on COMMENT or
|
||||
# REQUEST_CHANGES is harmless (read-only, idempotent).
|
||||
# - On labeled/unlabeled events: re-evaluate when labels change.
|
||||
# This ensures qa-review flips when risk-indicator labels are
|
||||
# added or removed.
|
||||
# Comment-triggered refires live in sop-checklist.yml review-refire job.
|
||||
if: |
|
||||
github.event_name == 'pull_request_target' ||
|
||||
(github.event_name == 'pull_request_review' &&
|
||||
(github.event.review.state == 'APPROVED' || github.event.review.state == 'approved'))
|
||||
github.event_name == 'pull_request_review'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Privilege check (A1.1 — INFORMATIONAL log only, NOT a gate)
|
||||
@@ -130,7 +143,7 @@ jobs:
|
||||
# no comment.user.login so the step is a no-op skip there.
|
||||
if: github.event_name == 'issue_comment'
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.SOP_TIER_CHECK_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
GITEA_TOKEN: ${{ secrets.SOP_CHECKLIST_GATE_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
login="${{ github.event.comment.user.login }}"
|
||||
@@ -162,7 +175,7 @@ jobs:
|
||||
- name: Evaluate qa-review
|
||||
id: eval
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.SOP_TIER_CHECK_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
GITEA_TOKEN: ${{ secrets.SOP_CHECKLIST_GATE_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
GITEA_HOST: git.moleculesai.app
|
||||
REPO: ${{ github.repository }}
|
||||
# PR number lives in different places per event:
|
||||
@@ -185,7 +198,7 @@ jobs:
|
||||
# TOKEN FIX (RC 8326): uses STATUS_POST_TOKEN (CTO-granted,
|
||||
# msg d52cc72a). Dedicated narrow-scoped write:repository token
|
||||
# for the explicit status POST. Evaluator step stays on
|
||||
# SOP_TIER_CHECK_TOKEN (read-only) per deliberate security
|
||||
# SOP_CHECKLIST_GATE_TOKEN (read-only) per deliberate security
|
||||
# separation: eval computes, POST writes, never the same cred.
|
||||
if: github.event_name == 'pull_request_review' && always()
|
||||
env:
|
||||
|
||||
@@ -12,18 +12,21 @@
|
||||
# Uses `pull_request_review` types: [submitted] — verified live via
|
||||
# sop-checklist.yml which provably fires this event (produces
|
||||
# `sop-checklist / all-items-acked (pull_request_review)` contexts).
|
||||
# The job-level `if:` guard checks
|
||||
# `github.event.review.state == 'APPROVED' || 'approved'` so only APPROVE
|
||||
# reviews run the evaluator; COMMENT and REQUEST_CHANGES are skipped at
|
||||
# the job level. Branch-protection requires the `(pull_request_target)`
|
||||
# context variant, so the review-event path EXPLICITLY POSTS the required
|
||||
# context via the API. Trust boundary preserved (BASE ref, no PR-head).
|
||||
# The job-level `if:` does NOT guard on review.state (issue #2159):
|
||||
# Gitea 1.22.6's payload shape for this event does not reliably expose
|
||||
# the state field that the GitHub-style guard expects. The evaluator
|
||||
# (review-check.sh) reads actual reviews from the API and checks for a
|
||||
# real APPROVE, so running on COMMENT or REQUEST_CHANGES is harmless
|
||||
# (read-only, idempotent). Branch-protection requires the
|
||||
# `(pull_request_target)` context variant, so the review-event path
|
||||
# EXPLICITLY POSTS the required context via the API. Trust boundary
|
||||
# preserved (BASE ref, no PR-head).
|
||||
|
||||
name: security-review
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, reopened]
|
||||
types: [opened, synchronize, reopened, labeled, unlabeled]
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
|
||||
@@ -37,13 +40,19 @@ jobs:
|
||||
approved:
|
||||
# Gate the job:
|
||||
# - On pull_request_target events: always run.
|
||||
# - On pull_request_review_approved events: run so the gate flips
|
||||
# immediately when a team member submits an APPROVE review.
|
||||
# - On pull_request_review events: always run. We do NOT guard on
|
||||
# review.state here because Gitea 1.22.6's payload shape for this
|
||||
# event does not reliably expose the state field (issue #2159).
|
||||
# The evaluator (review-check.sh) reads actual reviews from the
|
||||
# API and checks for a real APPROVE, so running on COMMENT or
|
||||
# REQUEST_CHANGES is harmless (read-only, idempotent).
|
||||
# - On labeled/unlabeled events: re-evaluate when labels change.
|
||||
# This ensures security-review flips when risk-indicator labels
|
||||
# are added or removed.
|
||||
# Comment-triggered refires live in sop-checklist.yml review-refire job.
|
||||
if: |
|
||||
github.event_name == 'pull_request_target' ||
|
||||
(github.event_name == 'pull_request_review' &&
|
||||
(github.event.review.state == 'APPROVED' || github.event.review.state == 'approved'))
|
||||
github.event_name == 'pull_request_review'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Privilege check (A1.1 — INFORMATIONAL log only, NOT a gate)
|
||||
@@ -52,7 +61,7 @@ jobs:
|
||||
# so re-running on a non-collaborator comment is harmless.
|
||||
if: github.event_name == 'issue_comment'
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.SOP_TIER_CHECK_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
GITEA_TOKEN: ${{ secrets.SOP_CHECKLIST_GATE_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
login="${{ github.event.comment.user.login }}"
|
||||
@@ -78,7 +87,7 @@ jobs:
|
||||
- name: Evaluate security-review
|
||||
id: eval
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.SOP_TIER_CHECK_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
GITEA_TOKEN: ${{ secrets.SOP_CHECKLIST_GATE_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
GITEA_HOST: git.moleculesai.app
|
||||
REPO: ${{ github.repository }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }}
|
||||
@@ -98,7 +107,7 @@ jobs:
|
||||
# TOKEN FIX (RC 8326): uses STATUS_POST_TOKEN (CTO-granted,
|
||||
# msg d52cc72a). Dedicated narrow-scoped write:repository token
|
||||
# for the explicit status POST. Evaluator step stays on
|
||||
# SOP_TIER_CHECK_TOKEN (read-only) per deliberate security
|
||||
# SOP_CHECKLIST_GATE_TOKEN (read-only) per deliberate security
|
||||
# separation: eval computes, POST writes, never the same cred.
|
||||
if: github.event_name == 'pull_request_review' && always()
|
||||
env:
|
||||
|
||||
@@ -167,7 +167,7 @@ jobs:
|
||||
if: steps.classify.outputs.run_qa == 'true'
|
||||
env:
|
||||
# Evaluator (review-check.sh + GET /pulls) stays on read-scoped token.
|
||||
GITEA_TOKEN: ${{ secrets.SOP_TIER_CHECK_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
GITEA_TOKEN: ${{ secrets.SOP_CHECKLIST_GATE_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
# Explicit POST /statuses uses narrow-scoped write:repository token.
|
||||
STATUS_POST_TOKEN: ${{ secrets.STATUS_POST_TOKEN }}
|
||||
GITEA_HOST: git.moleculesai.app
|
||||
@@ -186,7 +186,7 @@ jobs:
|
||||
if: steps.classify.outputs.run_security == 'true'
|
||||
env:
|
||||
# Evaluator (review-check.sh + GET /pulls) stays on read-scoped token.
|
||||
GITEA_TOKEN: ${{ secrets.SOP_TIER_CHECK_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
GITEA_TOKEN: ${{ secrets.SOP_CHECKLIST_GATE_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
# Explicit POST /statuses uses narrow-scoped write:repository token.
|
||||
STATUS_POST_TOKEN: ${{ secrets.STATUS_POST_TOKEN }}
|
||||
GITEA_HOST: git.moleculesai.app
|
||||
|
||||
@@ -278,7 +278,7 @@ receive **HTTP 401** on every API call. Affected workflows in molecule-core:
|
||||
|
||||
| Workflow | Symptom | Workaround |
|
||||
|---|---|---|
|
||||
| `gate-check-v3.yml` | Reports BLOCKED on every PR | Provision `SOP_TIER_CHECK_TOKEN`; update workflow to use it |
|
||||
| `gate-check-v3.yml` | Reports BLOCKED on every PR | Provision `SOP_CHECKLIST_GATE_TOKEN`; update workflow to use it |
|
||||
| `qa-review.yml` | Fails immediately on PR open | Same — needs named secret |
|
||||
| `security-review.yml` | Fails immediately on PR open | Same — needs named secret |
|
||||
|
||||
@@ -313,7 +313,7 @@ dispatcher may fire **only 1 of N eligible workflows** on the initial
|
||||
|
||||
This was observed on molecule-core PR #558 (created 2026-05-11T19:54:10Z):
|
||||
12+ workflows had no `paths:` filter and should have fired, but only
|
||||
`sop-tier-check.yml` dispatched.
|
||||
`sop-checklist.yml` dispatched.
|
||||
|
||||
Concurrent PRs created within the same minute received 12–30 dispatches each,
|
||||
confirming this is specific to the PR-create event dispatch, not a general
|
||||
|
||||
Reference in New Issue
Block a user