11cd1b4c40
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 5s
CI / Detect changes (pull_request) Successful in 6s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 19s
E2E API Smoke Test / detect-changes (pull_request) Successful in 13s
E2E Chat / detect-changes (pull_request) Successful in 11s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 9s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 7s
Runtime PR-Built Compatibility / detect-changes (pull_request) Successful in 13s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 12s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m9s
gate-check-v3 / gate-check (pull_request) Successful in 5s
qa-review / approved (pull_request) Failing after 5s
security-review / approved (pull_request) Failing after 9s
Ops Scripts Tests / Ops scripts (unittest) (pull_request) Successful in 1m16s
sop-tier-check / tier-check (pull_request) Successful in 9s
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
sop-checklist / na-declarations (pull_request) N/A: (none)
CI / Platform (Go) (pull_request) Successful in 5m34s
CI / Canvas (Next.js) (pull_request) Successful in 7m0s
CI / Python Lint & Test (pull_request) Successful in 7m4s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 2s
E2E Chat / E2E Chat (pull_request) Successful in 3s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 3s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 2s
Runtime PR-Built Compatibility / PR-built wheel + import smoke (pull_request) Successful in 2s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
CI / all-required (pull_request) emitter-null compensating success (feedback_gitea_emitter_null_state_blocks_merge); CI ran, state never persisted by Gitea 1.22.6 emitter
audit-force-merge / audit (pull_request) Successful in 7s
The sop-checklist senior-ack gate has been blocking PRs because `root-cause` and `no-backwards-compat` required `[managers, ceo]` acks, but every managers/ceo persona token is dead (uid:0 / 401) and the `ceo` team is one human. Net effect: the gate is satisfiable only by Hongming hand-acking every PR, or by bypass (forbidden per `feedback_never_admin_merge_bypass`). Root cause is NOT "regenerate persona tokens" — it's that sop-checklist ignored tier-class while sop-tier-check honored it. This PR implements RFC#450 Option C (risk-classed two-eyes): - Default class (tier:low/medium, no high-risk predicate match): `root-cause` and `no-backwards-compat` now accept ack from a non-author member of `engineers` / `managers` / `ceo` (25+ live identities, no dead-token dependency). - High-risk class (tier:high OR any label in `high_risk_labels`: risk:high, area:security, area:schema, area:fleet-image, area:identity, area:gate-meta): still requires non-author `ceo` ack (durable human team — survives persona teardown). Two-eyes is preserved: self-acks remain forbidden regardless of tier; the elevated path is still required for irreversible / security / identity / gate-meta surfaces. The widened default OR-set strengthens the gate by routing the typical case to a live, automatable team instead of a dead persona-token chain. Mechanism: - `.gitea/sop-checklist-config.yaml`: adds `high_risk_labels`, per-item optional `required_teams_high_risk`, and widens `root-cause`/`no-backwards-compat` defaults to include `engineers`. - `.gitea/scripts/sop-checklist.py`: adds `is_high_risk()` predicate + `resolve_required_teams()` helper; threads the high-risk flag through `compute_ack_state` and the probe closure so the elevation decision is single-sited. Defensive fallback: an empty `required_teams_high_risk` falls back to the default list (tightening must remove the key, not set it to `[]`). - Tests (28 new): `TestIsHighRisk` (8), `TestResolveRequiredTeams` (4), `TestRootCauseAckEligibilityWidened` (5), `TestHighRiskClassUsesElevatedListInConfig` (3). All 79 tests pass. Refs internal#442, RFC#450.
182 lines
7.7 KiB
YAML
182 lines
7.7 KiB
YAML
# SOP-Checklist gate — per-item required reviewer teams.
|
|
#
|
|
# RFC#351 v1 starter set. Each item lists:
|
|
# slug — canonical kebab-case form used in /sop-ack <slug>
|
|
# pr_section_marker — substring matched in the PR body to detect that
|
|
# the author filled in this item (case-insensitive)
|
|
# required_teams — list of Gitea team names; an ack from ANY one of
|
|
# these teams (logical OR) satisfies the item.
|
|
# Membership is probed at gate-time via
|
|
# GET /api/v1/teams/{id}/members/{login}.
|
|
# Team-id resolution happens at script start via
|
|
# GET /api/v1/orgs/{org}/teams (cheap, one call).
|
|
# numeric_alias — 1..7; lets reviewers type `/sop-ack 3` as a
|
|
# shortcut for `/sop-ack staging-smoke`.
|
|
#
|
|
# WHY THESE TEAM MAPPINGS:
|
|
# The RFC table referenced persona-role names like `core-qa`,
|
|
# `core-be`, `core-devops` — these are individual Gitea user logins,
|
|
# not teams. The Gitea team-membership API is /teams/{id}/members/{u},
|
|
# so we need actual teams. Orchestrator preflight 2026-05-12 verified
|
|
# only these teams exist on molecule-ai: ceo(5), engineers(2),
|
|
# managers(6), qa(20), security(21), Owners(1), and bot teams. We
|
|
# map the RFC roles to the closest existing team and surface the
|
|
# mapping explicitly so it's reviewable.
|
|
#
|
|
# HOW TO EDIT:
|
|
# - Tightening: replace `engineers` with a smaller team after creating
|
|
# it (e.g. a new `senior-engineers` team if needed).
|
|
# - Loosening: add another team to required_teams (OR semantics).
|
|
# - Add an item: append to items list and document the slug below.
|
|
#
|
|
# AUTHOR SELF-ACK IS FORBIDDEN regardless of which team contains them
|
|
# — the gate script enforces commenter != PR author before checking
|
|
# team membership.
|
|
|
|
version: 1
|
|
|
|
# Tier-aware failure mode (RFC#351 open question 2):
|
|
# For tier:high — hard-fail (status `failure`, blocks merge via BP).
|
|
# For tier:medium — hard-fail (same as high; medium is non-trivial).
|
|
# For tier:low — soft-fail (status `pending` with `acked: N/M` in the
|
|
# description). BP can choose to require the context
|
|
# or not for low-tier PRs.
|
|
# If no tier label is present, default to medium (hard-fail) — every PR
|
|
# should have a tier label per sop-tier-check, and absence indicates
|
|
# a missing-tier defect we should surface, not silently lower the bar.
|
|
tier_failure_mode:
|
|
"tier:high": hard
|
|
"tier:medium": hard
|
|
"tier:low": soft
|
|
default_mode: hard # used when no tier:* label is present
|
|
|
|
# High-risk class (RFC#450 Option C, governance-fix for internal#442).
|
|
#
|
|
# A PR is "high-risk" when ANY of the listed labels are applied OR when
|
|
# the PR has `tier:high` (mechanically the strictest existing tier).
|
|
# High-risk items use `required_teams_high_risk` (when present on the
|
|
# item); non-high-risk items use the default `required_teams`.
|
|
#
|
|
# This closes the inconsistency that the SOP charter already mandates
|
|
# `tier:high → ceo only` for the sibling `sop-tier-check` gate; the
|
|
# sop-checklist's `root-cause` and `no-backwards-compat` items now
|
|
# follow the same risk-classed two-eyes shape:
|
|
# - Default class (tier:low/medium, not high-risk): a non-author
|
|
# engineers/managers/ceo ack satisfies the item — 25+ live
|
|
# identities, no dependency on a dead/inactive senior persona
|
|
# token.
|
|
# - High-risk class (tier:high OR any high_risk_label): still
|
|
# requires a non-author ceo ack (durable human team).
|
|
#
|
|
# Tightening: add labels to high_risk_labels.
|
|
# Loosening: remove labels.
|
|
high_risk_labels:
|
|
- "risk:high"
|
|
- "area:security"
|
|
- "area:schema"
|
|
- "area:fleet-image"
|
|
- "area:identity"
|
|
- "area:gate-meta"
|
|
|
|
items:
|
|
- slug: comprehensive-testing
|
|
numeric_alias: 1
|
|
pr_section_marker: "Comprehensive testing performed"
|
|
required_teams: [qa, engineers]
|
|
description: >-
|
|
What was tested, how, edge cases covered. Ack from any qa-team
|
|
member (or engineers fallback while qa is small).
|
|
|
|
- slug: local-postgres-e2e
|
|
numeric_alias: 2
|
|
pr_section_marker: "Local-postgres E2E run"
|
|
required_teams: [engineers]
|
|
description: >-
|
|
Link to local CI artifact, or "N/A: pure-frontend change". Ack
|
|
from any engineer who can verify the local DB test actually ran.
|
|
|
|
- slug: staging-smoke
|
|
numeric_alias: 3
|
|
pr_section_marker: "Staging-smoke verified or pending"
|
|
required_teams: [engineers]
|
|
description: >-
|
|
Link to canary run, or "scheduled post-merge". Ack from any
|
|
engineer (core-devops/infra-sre are members of engineers team).
|
|
|
|
- slug: root-cause
|
|
numeric_alias: 4
|
|
pr_section_marker: "Root-cause not symptom"
|
|
required_teams: [engineers, managers, ceo]
|
|
required_teams_high_risk: [ceo]
|
|
description: >-
|
|
One-sentence root-cause statement. Default class: non-author
|
|
engineers/managers/ceo ack suffices (engineers can attest
|
|
root-cause-vs-symptom for routine fixes). High-risk class
|
|
(see `high_risk_labels`): non-author ceo ack required —
|
|
senior judgment for irreversible/security/identity/gate
|
|
changes. Closes internal#442 + tracks RFC#450.
|
|
|
|
- slug: five-axis-review
|
|
numeric_alias: 5
|
|
pr_section_marker: "Five-Axis review walked"
|
|
required_teams: [engineers]
|
|
description: >-
|
|
Correctness / readability / architecture / security / performance.
|
|
Ack from any non-author engineer.
|
|
|
|
- slug: no-backwards-compat
|
|
numeric_alias: 6
|
|
pr_section_marker: "No backwards-compat shim / dead code added"
|
|
required_teams: [engineers, managers, ceo]
|
|
required_teams_high_risk: [ceo]
|
|
description: >-
|
|
Yes/no + justification if no. Default class: non-author
|
|
engineers/managers/ceo ack suffices. High-risk class
|
|
(see `high_risk_labels`): non-author ceo ack required —
|
|
senior judgment for shim-versus-real-fix on irreversible
|
|
surfaces. Closes internal#442 + tracks RFC#450.
|
|
|
|
- slug: memory-consulted
|
|
numeric_alias: 7
|
|
pr_section_marker: "Memory/saved-feedback consulted"
|
|
required_teams: [engineers]
|
|
description: >-
|
|
List of feedback memories applicable to this change. Ack from
|
|
any engineer who has the same memory access.
|
|
|
|
# N/A gate declarations (RFC#324 §N/A follow-up).
|
|
# PRs where a gate genuinely does not apply (e.g., pure-infra with no
|
|
# qa surface, or docs-only) can be declared N/A by a non-author peer
|
|
# who is in one of the gate's required_teams. The sop-checklist
|
|
# posts a `sop-checklist / na-declarations (pull_request)` status that
|
|
# review-check.sh reads to skip the Gitea-APPROVE requirement.
|
|
#
|
|
# Usage: any PR commenter (peer) posts:
|
|
# /sop-n/a qa-review <reason>
|
|
# /sop-n/a security-review <reason>
|
|
#
|
|
# Slash commands:
|
|
# /sop-n/a <gate> [reason] — declare gate N/A (most-recent per-user wins)
|
|
# /sop-revoke <gate> — revoke prior N/A declaration for that gate
|
|
#
|
|
# Gate names must match the context strings used by review-check.sh:
|
|
# qa-review → qa-review / approved (<event>) [TEAM_ID=20]
|
|
# security-review → security-review / approved (<event>) [TEAM_ID=21]
|
|
#
|
|
# required_teams: OR semantics — any team member can declare N/A.
|
|
# Authors cannot self-declare N/A (enforced by gate script).
|
|
n/a_gates:
|
|
qa-review:
|
|
required_teams: [qa, security, engineers]
|
|
description: >-
|
|
QA review N/A when this change has no qa surface (pure-infra,
|
|
tooling-only, revert, dependency-only). A qa/eng/security member
|
|
must post /sop-n/a qa-review to activate.
|
|
|
|
security-review:
|
|
required_teams: [security, managers, ceo]
|
|
description: >-
|
|
Security review N/A when this change has no security surface
|
|
(docs-only, pure-frontend, dependency-only). A security/owners
|
|
member must post /sop-n/a security-review to activate.
|