Files
molecule-core/.gitea/workflows/sop-checklist.yml
T
hongming-ceo-delegated ac55f43d8e
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 7s
Block integration-tester contamination artifacts / Block staging-trigger / invalid manifest contamination (pull_request) Successful in 7s
CI / Python Lint & Test (pull_request) Successful in 6s
sop-checklist / all-items-acked (pull_request) ack advisory until agent-team — would be failure: acked: 0/10 — missing: comprehensive-testing, local-postgres-e2e, staging-smoke, +7 — body
sop-checklist / na-declarations (pull_request) N/A: (none)
sop-checklist / all-items-acked (pull_request_target) Successful in 5s
Concierge Creates Workspace Hermetic / Concierge Creates Workspace Hermetic (pull_request) Successful in 18s
ci-required-drift / drift (pull_request) Successful in 18s
E2E Staging SaaS (full lifecycle) / E2E Staging SaaS (pull_request) Has been skipped
E2E Staging SaaS (full lifecycle) / E2E Staging Platform Boot (pull_request) Has been skipped
E2E Staging SaaS (full lifecycle) / E2E Staging Concierge user_tasks (pull_request) Has been skipped
E2E Staging SaaS (full lifecycle) / E2E Staging Workspace Requests (core#2606) (pull_request) Has been skipped
E2E Peer Visibility (literal MCP list_peers) / detect-changes (pull_request) Successful in 20s
CI / Detect changes (pull_request) Successful in 23s
E2E Staging SaaS (full lifecycle) / E2E Staging Concierge Platform Agent (pull_request) Has been skipped
E2E Staging SaaS (full lifecycle) / E2E Staging Plugin Install Lifecycle (pull_request) Has been skipped
Harness Replays / detect-changes (pull_request) Successful in 6s
E2E API Smoke Test / detect-changes (pull_request) Successful in 30s
E2E Chat / detect-changes (pull_request) Successful in 31s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 28s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 10s
E2E External Connect-Snippet + Leak-Lint / E2E External Connect-Snippet + Leak-Lint (pull_request) Successful in 34s
Lint curl status-code capture / Scan workflows for curl status-capture pollution (pull_request) Successful in 10s
Lint forbidden tenant-env keys / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 8s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 8s
Lint forbidden hand-written mcp__ tool-id literals / Scan for hand-written mcp__ tool-id literals (pull_request) Successful in 9s
lint-bp-context-emit-match / lint-bp-context-emit-match (pull_request) Successful in 21s
lint-continue-on-error-tracking / lint-continue-on-error-tracking (pull_request) Successful in 30s
Lint publish-runner timeout-minutes / Lint publish-runner timeout-minutes (pull_request) Successful in 20s
lint-no-coe-on-required / lint-no-coe-on-required (pull_request) Successful in 22s
lint-required-workflows-docker-host-pinned / Lint docker-host pin on docker-touching workflows (pull_request) Successful in 14s
lint-phantom-gate / lint-phantom-gate (pull_request) Successful in 22s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 20s
lint-env-coupling-dismissal / lint-env-coupling-dismissal (pull_request) Successful in 33s
mcp-plugin-delivery-contract-drift / Compare MCP plugin delivery contract against template and runtime canonicals (pull_request) Successful in 12s
Lint schedule budget / Zero-cron ratchet (pull_request) Successful in 21s
lint-staging-tenant-cd-gate-chain / lint-staging-tenant-cd-gate-chain (pull_request) Successful in 21s
lint-setup-go-cache / lint-setup-go-cache (pull_request) Successful in 22s
Lint pre-flip continue-on-error / Verify continue-on-error flips have run-log proof (pull_request) Successful in 43s
Lint workflow YAML (Gitea-1.22.6-hostile shapes) / Lint workflow YAML for Gitea-1.22.6-hostile shapes (pull_request) Successful in 22s
lint-required-context-exists-in-bp / lint-required-context-exists-in-bp (pull_request) Successful in 44s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 9s
nodup-lint / no-duplication (pull_request) Successful in 13s
mcp-verb-published-manifest / Published mcp-server manifest ⊇ contract required verbs (pull_request) Successful in 17s
PR Diff Guard / PR diff guard (pull_request) Successful in 15s
sync-providers-yaml / Verify SDK registry dependency (pull_request) Successful in 14s
template-delivery-e2e / detect-changes (pull_request) Successful in 21s
verify-providers-gen / Regenerate providers artifact and fail on drift (pull_request) Successful in 16s
gate-check-v3 / gate-check (pull_request_target) Successful in 14s
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 7s
reserved-path-review / reserved-path-review (pull_request_review) Successful in 8s
security-review / approved (pull_request_review) Successful in 8s
Ops Scripts Tests / Ops scripts (unittest) (pull_request) Successful in 30s
E2E Chat / E2E Chat (pull_request) Successful in 4s
E2E Peer Visibility (literal MCP list_peers) / E2E Peer Visibility (pull_request) Successful in 8s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 5s
Local Provision Lifecycle E2E / Local Provision Lifecycle E2E (stub) (pull_request) Successful in 1m1s
gitea-merge-queue / queue (pull_request_review) Successful in 47s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 38s
Local Provision Lifecycle E2E / Local Provision Lifecycle E2E (real image + MiniMax LLM, advisory) (pull_request) Successful in 54s
Harness Replays / Harness Replays (pull_request) Successful in 1m14s
template-delivery-e2e / Template-asset delivery (fresh seo-agent — config+prompts via asset channel, seo-all via plugin reconcile) (pull_request) Successful in 1m31s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 2m27s
CI / Platform (Go) (pull_request) Successful in 3m31s
CI / Canvas (Next.js) (pull_request) Successful in 4m31s
CI / Canvas Deploy Status (pull_request) Successful in 2s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 6m3s
CI / all-required (pull_request) Successful in 5s
sop-checklist / review-refire (pull_request_target) Compensating status: base workflow skipped review-refire; ac55f43 fixes future PRs. Real gates green; no refire requested.
audit-force-merge / audit (pull_request_target) Successful in 6s
E2E Staging SaaS (full lifecycle) / E2E Staging Concierge (compile+skip) (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) / Prune stale e2e DNS records (pull_request) Waiting to run
E2E Staging SaaS (full lifecycle) / pr-validate (pull_request) Waiting to run
ci: keep review-refire lifecycle status green
2026-07-09 00:47:20 -07:00

306 lines
15 KiB
YAML

# sop-checklist — peer-ack merge gate for SOP-checklist items.
#
# RFC#351 Step 2 of 6 (implementation MVP).
#
# === CONSOLIDATION (issue #1280) ===
#
# This workflow is the SINGLE `issue_comment` subscriber — the logic from
# `review-refire-comments.yml` has been merged in. Before this change:
# - sop-checklist.yml (pre-2026-05-16) → issue_comment:[created,edited,deleted] → runner slot used, job no-oped
# - review-refire-comments.yml → issue_comment:[created] → runner slot used, job no-oped
# → every non-refire comment occupied 2 runner slots for ~800 s each
# (~650 no-op runs/day, ~1,300 runner-slot-occupancy-hours/day).
#
# Fix (PR #1345 / issue #1280):
# - ONE workflow, ONE issue_comment:[created] subscription (no edited/deleted)
# - all-items-acked job: pull_request_target OR sop slash-command comments
# - review-refire job: qa/security refire slash commands
# → ~50% reduction in comment-triggered runner occupancy vs pre-fix.
#
# Trust boundary (mirrors RFC#324 §A4 + sop-checklist security note):
# `pull_request_target` (not `pull_request`) — workflow def is loaded
# from BASE branch, so a PR cannot rewrite this workflow to exfiltrate
# the token. The `actions/checkout` step pins `ref: base.sha` so the
# script ALSO comes from BASE. PR-HEAD code is never executed in the
# runner.
#
# Token scope:
# - read:repository, read:organization for PR + comments + team probes
# - write:repository for POST /statuses/{sha}
# - The token owner MUST be a member of every team referenced by the
# config's required_teams (else /teams/{id}/members/{login} returns
# 403 — see review-check.sh same-gotcha doc). For the MVP we use
# the dev-lead token (a member of engineers, managers, qa, security)
# via a repo secret `SOP_CHECKLIST_GATE_TOKEN`. Provisioning of that
# secret is a follow-up authorization step (separate from this PR).
#
# Slash-command contract (RFC#351 v1 + §A1.1-style notes from RFC#324):
#
# /sop-ack <slug-or-numeric-alias> [optional note]
# — register a peer-ack for one checklist item.
# — slug accepts kebab-case, snake_case, or natural-spaces
# (all normalized to canonical kebab-case).
# — numeric 1..7 maps via config.items[*].numeric_alias.
# — most-recent (user, slug) directive wins.
#
# /sop-revoke <slug-or-numeric-alias> [reason]
# — invalidate the commenter's own prior /sop-ack for this slug.
# — does NOT affect other peers' acks on the same slug.
# — most-recent (user, slug) directive wins, so a later /sop-ack
# re-restores the ack.
#
# /sop-n/a <gate> [reason]
# — declare a gate (qa-review, security-review) N/A.
# — see sop-checklist-config.yaml n/a_gates section.
#
# /qa-recheck /security-recheck
# — refire the corresponding status check on the PR head.
#
# The eval is read-only + idempotent (read PR + comments + team
# membership, compute, post status). Re-running on any event is safe —
# the new status overwrites the previous one for the same context.
name: sop-checklist
# Concurrency: supersede only same-EVENT-TYPE in-progress runs for the same PR.
#
# `github.event_name` is part of the group key on purpose. This workflow runs
# on BOTH `pull_request_target` (posts the BP-required
# `sop-checklist / all-items-acked (pull_request_target)` context, from the
# trusted base ref) AND `issue_comment` (posts the non-required
# `(pull_request)` context on /sop-ack). Gitea auto-suffixes the status
# context from the trigger event, so the two event types write DIFFERENT
# contexts.
#
# Before this fix the group was keyed by PR number alone with
# cancel-in-progress: true, so a /sop-ack comment run would CANCEL an
# in-flight pull_request_target run — leaving the required
# `(pull_request_target)` context stuck at `failure: Has been cancelled`
# with no same-event trigger to re-post it (a permanent merge wedge;
# repro'd on #3392/#3393/docs#91 2026-07-02). Splitting the group by
# event_name keeps "newest run of the SAME event type wins" (older
# pull_request_target edits/labels still supersede correctly, older
# comment runs still supersede) while a comment can no longer strand the
# trusted required status. The gate eval is idempotent (reads live
# PR+comments, posts a per-SHA status), so the surviving run always posts
# the correct verdict.
concurrency:
group: ${{ github.repository }}-${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.event.issue.number || github.ref }}
cancel-in-progress: true
# bp-required: yes ← emits sop-checklist / all-items-acked (pull_request)
on:
pull_request_target:
types: [opened, edited, synchronize, reopened, labeled, unlabeled]
issue_comment:
types: [created] # NOT [created, edited, deleted] — Gitea 1.22.6 holds a runner slot
# at job-parsing time, before job-level if: guards run. edited/deleted events
# occupied ~1,300 runner-slot-hours/day on this workflow alone during the
# 2026-05-16 freeze. Per PR #1345 fix.
permissions:
contents: read
pull-requests: read
statuses: write
secrets: read
jobs:
# sop-checklist gate: runs on PR lifecycle events OR sop slash commands.
# All other comment types (no-op text comments) no longer assign a runner
# because this job's if: guard short-circuits before runner assignment.
all-items-acked:
if: |
github.event_name == 'pull_request_target' ||
(github.event_name == 'issue_comment' &&
github.event.issue.pull_request != null &&
(contains(github.event.comment.body, '/sop-ack') ||
contains(github.event.comment.body, '/sop-revoke') ||
contains(github.event.comment.body, '/sop-n/a')))
# Completes eb18297b: the gate calls Gitea over the INTERNAL docker-network
# address (GITEA_API_BASE_URL=http://molecule-gitea-local:3000, set below),
# which resolves ONLY from a runner on `molecule-net`. `ubuntu-latest` is
# advertised by the REMOTE robot-1 pool (host-network / off-net), where
# `molecule-gitea-local` does not resolve — the job died with
# `socket.gaierror: Temporary failure in name resolution` before it could
# evaluate (e.g. #3446 head, 2026-07-05). Pin to `ci-meta`, the local
# molecule-net runner that serves molecule-ai repos (GITHUB_SERVER_URL is
# http://molecule-gitea-local:3000 there), so the internal host is always
# reachable and the gate never touches the CF edge (WAF-1010). Verified live:
# a molecule-core job on `ci-meta` reaches molecule-gitea-local:3000 (HTTP
# 200). This gate is bp-exempt, so if the runner is briefly offline the
# status is simply not posted (no merge is blocked).
runs-on: ci-meta
steps:
- name: Check out BASE ref (trust boundary — never PR-head)
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# For pull_request_target, the default branch is the trust
# anchor. For issue_comment the PR base may differ from the
# default branch (PR targeting `staging`), so we use the
# default-branch ref explicitly — same approach as
# qa-review.yml so the script source is always trusted.
ref: ${{ github.event.repository.default_branch }}
- name: Run sop-checklist
env:
GITEA_TOKEN: ${{ secrets.SOP_CHECKLIST_GATE_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }}
OWNER: ${{ github.repository_owner }}
REPO_NAME: ${{ github.event.repository.name }}
# OPERATOR RELAX (standing directive, until the agent team exists):
# peer-ack ENFORCEMENT is gated on the org Actions variable
# MERGE_REQUIRE_SOP_ACK (currently 'false'). Anything but 'true' =>
# the gate still runs the FULL ack evaluation but POSTs success with
# the 'ack advisory until agent-team' description prefix (see
# apply_ack_enforcement_policy in sop-checklist.py — unit-tested).
# Set the org var to 'true' to restore enforcement, no code change.
MERGE_REQUIRE_SOP_ACK: ${{ vars.MERGE_REQUIRE_SOP_ACK }}
# CF-1010 fix: call Gitea over the INTERNAL docker-network address, not
# the public CF edge. The gate script uses urllib, whose `Python-urllib/*`
# User-Agent Cloudflare bans with WAF error 1010 (browser_signature_banned);
# that killed the gate before it could evaluate whenever the job landed on
# a molecule-net (local) runner — a non-deterministic org-wide RED that was
# being worked around by routing jobs to the remote robot-1 pool + repo-admin
# elevation. This is the internal service address the runners already use for
# checkout/control (GITHUB_SERVER_URL). The status link stays on the public
# host via --public-host. Auth is unchanged (same Gitea, same token).
GITEA_API_BASE_URL: http://molecule-gitea-local:3000
GITEA_PUBLIC_HOST: git.moleculesai.app
run: |
set -euo pipefail
python3 .gitea/scripts/sop-checklist.py \
--owner "$OWNER" \
--repo "$REPO_NAME" \
--pr "$PR_NUMBER" \
--config .gitea/sop-checklist-config.yaml \
--gitea-host "$GITEA_API_BASE_URL" \
--public-host "$GITEA_PUBLIC_HOST"
# bp-exempt: informational refire handler, not a merge gate. Emits
# qa-review/security-review status updates on /qa-recheck et al slash commands.
#
# Keep this job free of a job-level `if:`. `main` currently uses wildcard
# branch protection (`status_check_contexts: ["*"]`), so a skipped job still
# publishes a blocking commit status. Running the job and letting the steps
# no-op on pull_request_target gives that lifecycle context a real success
# while slash-command refires remain guarded below.
review-refire:
runs-on: ubuntu-latest
steps:
- name: Classify comment
id: classify
env:
COMMENT_BODY: ${{ github.event.comment.body }}
run: |
set -euo pipefail
{
echo "run_qa=false"
echo "run_security=false"
} >> "$GITHUB_OUTPUT"
first_line=$(printf '%s\n' "$COMMENT_BODY" | sed -n '1p')
case "$first_line" in
/qa-recheck*)
echo "run_qa=true" >> "$GITHUB_OUTPUT"
;;
/security-recheck*)
echo "run_security=true" >> "$GITHUB_OUTPUT"
;;
*)
echo "::notice::no supported review refire slash command; no-op"
;;
esac
- name: Check out BASE ref for trusted scripts
if: |
steps.classify.outputs.run_qa == 'true' ||
steps.classify.outputs.run_security == 'true'
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.event.repository.default_branch }}
# KMS consolidation (#971/#3274 pattern): the two refire steps below
# previously read the per-bot Gitea Actions secret `STATUS_POST_TOKEN`
# for their explicit POST /statuses. We now fetch the consolidated
# CI_STATUS_TOKEN from the Infisical SSOT (prod /shared/ci-status) via
# the CI machine identity and export it to $GITHUB_ENV. Each refire step
# keeps reading it under the STATUS_POST_TOKEN env var name the
# review-refire-status.sh script already expects, so the script is
# unchanged. The old STATUS_POST_TOKEN Gitea secret is left in place
# until a green run validates this path (validate-before-delete).
#
# Trust boundary: this job runs on issue_comment with a BASE-ref checkout
# (trusted base context), so the INFISICAL_CI_* machine-identity secrets
# are available (same boundary that already gated STATUS_POST_TOKEN).
- name: Fetch CI_STATUS_TOKEN from Infisical SSOT
if: |
steps.classify.outputs.run_qa == 'true' ||
steps.classify.outputs.run_security == 'true'
env:
INFISICAL_CI_CLIENT_ID: ${{ secrets.INFISICAL_CI_CLIENT_ID }}
INFISICAL_CI_CLIENT_SECRET: ${{ secrets.INFISICAL_CI_CLIENT_SECRET }}
INFISICAL_PROJECT_ID: ${{ secrets.INFISICAL_CI_PROJECT_ID }}
run: |
set -uo pipefail
BASE="https://key.moleculesai.app"
TOK=$(curl -fsS -X POST "$BASE/api/v1/auth/universal-auth/login" \
-H 'Content-Type: application/json' \
-d "{\"clientId\":\"$INFISICAL_CI_CLIENT_ID\",\"clientSecret\":\"$INFISICAL_CI_CLIENT_SECRET\"}" \
| python3 -c 'import sys,json; v=json.load(sys.stdin).get("accessToken"); sys.stdout.write(v if isinstance(v,str) else "")')
if [ -z "$TOK" ]; then echo "::error::Infisical accessToken empty - failing closed"; exit 1; fi
read_secret() {
curl -fsS "$BASE/api/v3/secrets/raw/$1?workspaceId=$INFISICAL_PROJECT_ID&environment=prod&secretPath=$2" \
-H "Authorization: Bearer $TOK" \
| python3 -c 'import sys,json; v=json.load(sys.stdin).get("secret",{}).get("secretValue"); sys.stdout.write(v if isinstance(v,str) else "")'
}
CI_STATUS_TOKEN=$(read_secret CI_STATUS_TOKEN %2Fshared%2Fci-status)
echo "::add-mask::$CI_STATUS_TOKEN"
if [ -z "$CI_STATUS_TOKEN" ]; then
echo "::error::Infisical returned empty CI_STATUS_TOKEN at prod /shared/ci-status — failing closed."
exit 1
fi
echo "CI_STATUS_TOKEN=$CI_STATUS_TOKEN" >> "$GITHUB_ENV"
echo "CI_STATUS_TOKEN loaded from Infisical (len=${#CI_STATUS_TOKEN})"
- name: Refire qa-review status
if: steps.classify.outputs.run_qa == 'true'
env:
# Evaluator (review-check.sh + GET /pulls) stays on read-scoped token.
GITEA_TOKEN: ${{ secrets.SOP_CHECKLIST_GATE_TOKEN }}
# Explicit POST /statuses uses narrow-scoped write:repository token,
# now sourced from Infisical SSOT via $GITHUB_ENV (was
# secrets.STATUS_POST_TOKEN) — same env var name for the script.
STATUS_POST_TOKEN: ${{ env.CI_STATUS_TOKEN }}
GITEA_HOST: git.moleculesai.app
REPO: ${{ github.repository }}
PR_NUMBER: ${{ github.event.issue.number }}
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
TEAM: qa
TEAM_ID: '20'
REVIEW_CHECK_DEBUG: '0'
REVIEW_CHECK_STRICT: '0'
run: |
set -euo pipefail
.gitea/scripts/review-refire-status.sh
- name: Refire security-review status
if: steps.classify.outputs.run_security == 'true'
env:
# Evaluator (review-check.sh + GET /pulls) stays on read-scoped token.
GITEA_TOKEN: ${{ secrets.SOP_CHECKLIST_GATE_TOKEN }}
# Explicit POST /statuses uses narrow-scoped write:repository token,
# now sourced from Infisical SSOT via $GITHUB_ENV (was
# secrets.STATUS_POST_TOKEN) — same env var name for the script.
STATUS_POST_TOKEN: ${{ env.CI_STATUS_TOKEN }}
GITEA_HOST: git.moleculesai.app
REPO: ${{ github.repository }}
PR_NUMBER: ${{ github.event.issue.number }}
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
TEAM: security
TEAM_ID: '21'
REVIEW_CHECK_DEBUG: '0'
REVIEW_CHECK_STRICT: '0'
run: |
set -euo pipefail
.gitea/scripts/review-refire-status.sh