Compare commits

...

1 Commits

Author SHA1 Message Date
Molecule AI Dev Engineer A (Kimi) 120ca0d2a8 style(scripts): fix ruff E501 line-too-long in 6 production scripts
ci-arm64-advisory / fast-checks (pull_request) Waiting to run
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (pull_request) Successful in 9s
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 7s
CI / Python Lint & Test (pull_request) Successful in 7s
CI / Detect changes (pull_request) Successful in 15s
CI / all-required (pull_request) Successful in 1m0s
E2E API Smoke Test / detect-changes (pull_request) Successful in 20s
E2E Chat / detect-changes (pull_request) Successful in 20s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 13s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 5s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 5s
Lint no tenant GITEA or GITHUB token write / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 6s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 6s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m3s
qa-review / approved (pull_request) Failing after 6s
Lint workflow YAML (Gitea-1.22.6-hostile shapes) / Lint workflow YAML for Gitea-1.22.6-hostile shapes (pull_request) Successful in 1m19s
Ops Scripts Tests / Ops scripts (unittest) (pull_request) Successful in 1m17s
security-review / approved (pull_request) Failing after 12s
CI / Platform (Go) (pull_request) Successful in 4s
CI / Canvas (Next.js) (pull_request) Successful in 7s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 11s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 13s
E2E Chat / E2E Chat (pull_request) Successful in 8s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 7s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 5s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
gate-check-v3 / gate-check (pull_request) Successful in 7s
sop-checklist / na-declarations (pull_request) N/A: (none)
sop-checklist / review-refire (pull_request) Has been skipped
sop-checklist / all-items-acked (pull_request) Successful in 11s
sop-tier-check / tier-check (pull_request) Successful in 8s
lint-mask-pr-atomicity / lint-mask-pr-atomicity (pull_request) Successful in 1m26s
audit-force-merge / audit (pull_request) Has been skipped
Manual line wrapping to ≤88 chars in:
- ci-required-drift.py (7 lines)
- detect-changes.py (1 line)
- lint-workflow-yaml.py (2 lines)
- main-red-watchdog.py (1 line)
- prod-auto-deploy.py (8 lines)
- status-reaper.py (2 lines)

Also auto-fixes F541 (f-strings without placeholders) and I001 (import sorting)
in ci-required-drift.py, detect-changes.py, prod-auto-deploy.py.

All 148 tests pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 19:12:11 +00:00
6 changed files with 67 additions and 26 deletions
+18 -10
View File
@@ -274,7 +274,8 @@ def required_checks_env(audit_doc: dict) -> set[str]:
found.append(v)
if not found:
sys.stderr.write(
f"::error::REQUIRED_CHECKS env not found in any step of {AUDIT_WORKFLOW_PATH}\n"
f"::error::REQUIRED_CHECKS env not found in any step of "
f"{AUDIT_WORKFLOW_PATH}\n"
)
sys.exit(3)
if len(found) > 1:
@@ -387,7 +388,8 @@ def detect_drift(branch: str) -> tuple[list[str], dict]:
missing_from_needs = sorted(jobs - needs)
if missing_from_needs:
findings.append(
"F1 — jobs in ci.yml NOT under sentinel `needs:` (sentinel doesn't gate them):\n"
"F1 — jobs in ci.yml NOT under sentinel `needs:` "
"(sentinel doesn't gate them):\n"
+ "\n".join(f" - {n}" for n in missing_from_needs)
)
@@ -397,7 +399,8 @@ def detect_drift(branch: str) -> tuple[list[str], dict]:
stale_needs = sorted(needs - jobs_all)
if stale_needs:
findings.append(
"F1b — sentinel `needs:` lists jobs NOT present in ci.yml (typo or removed job):\n"
"F1b — sentinel `needs:` lists jobs NOT present in ci.yml "
"(typo or removed job):\n"
+ "\n".join(f" - {n}" for n in stale_needs)
)
@@ -405,7 +408,9 @@ def detect_drift(branch: str) -> tuple[list[str], dict]:
# Compute the contexts the CI YAML actually produces. The sentinel
# is in (B) intentionally (`ci / all-required (pull_request)`); we
# whitelist it explicitly.
emitted_contexts = {expected_context(j) for j in jobs} | {expected_context(SENTINEL_JOB)}
emitted_contexts = {
expected_context(j) for j in jobs
} | {expected_context(SENTINEL_JOB)}
# Contexts NOT produced by ci.yml may still come from other
# workflows in the repo (Secret scan etc). We can't enumerate
# every workflow's emissions cheaply; instead, flag only contexts
@@ -418,7 +423,8 @@ def detect_drift(branch: str) -> tuple[list[str], dict]:
)
if stale_protection:
findings.append(
"F2 — protection `status_check_contexts` entries with `ci / ` prefix that NO "
"F2 — protection `status_check_contexts` entries with "
"`ci / ` prefix that NO "
"job in ci.yml emits (stale name → silent advisory gate):\n"
+ "\n".join(f" - {c}" for c in stale_protection)
)
@@ -494,7 +500,9 @@ def render_body(branch: str, findings: list[str], debug: dict) -> str:
f"# Drift detected on `{REPO}/{branch}`",
"",
"Auto-filed by `.gitea/workflows/ci-required-drift.yml` "
"(RFC [internal#219](https://git.moleculesai.app/molecule-ai/internal/issues/219) §4 + §6).",
"(RFC [internal#219]"
"(https://git.moleculesai.app/molecule-ai/internal/issues/219) "
"§4 + §6).",
"",
"## Findings",
"",
@@ -547,12 +555,12 @@ def file_or_update(
if dry_run:
print(f"::notice::[dry-run] would file/update drift issue for {branch}")
print(f"::group::[dry-run] title")
print("::group::[dry-run] title")
print(title)
print(f"::endgroup::")
print(f"::group::[dry-run] body")
print("::endgroup::")
print("::group::[dry-run] body")
print(body)
print(f"::endgroup::")
print("::endgroup::")
return
existing = find_open_issue(title)
+4 -2
View File
@@ -15,7 +15,6 @@ import subprocess
import sys
from pathlib import Path
PROFILES: dict[str, dict[str, str]] = {
"ci": {
"platform": r"^workspace-server/",
@@ -153,7 +152,10 @@ def parse_args(argv: list[str]) -> argparse.Namespace:
parser.add_argument("--event-name", default=os.environ.get("GITHUB_EVENT_NAME", ""))
parser.add_argument("--pr-base-sha", default="")
parser.add_argument("--base-ref", default="")
parser.add_argument("--push-before", default=os.environ.get("GITHUB_EVENT_BEFORE", ""))
parser.add_argument(
"--push-before",
default=os.environ.get("GITHUB_EVENT_BEFORE", ""),
)
return parser.parse_args(argv)
+5 -2
View File
@@ -71,7 +71,10 @@ from typing import Any, Iterable
try:
import yaml
except ImportError:
print("::error::PyYAML is required. Install with: pip install PyYAML", file=sys.stderr)
print(
"::error::PyYAML is required. Install with: pip install PyYAML",
file=sys.stderr,
)
sys.exit(2)
@@ -198,7 +201,7 @@ USES_CROSS_REPO_RE = re.compile(
(?P<owner>[A-Za-z0-9_.\-]+)
/
(?P<repo>[A-Za-z0-9_.\-]+)
/ # mandatory subpath separator => cross-repo composite/reusable
/ # mandatory subpath separator => cross-repo composite/reusable
(?P<path>[^@\s]+)
@
(?P<ref>\S+)
+4 -1
View File
@@ -607,7 +607,10 @@ def close_open_red_issues_for_other_shas(
"`feedback_no_such_thing_as_flakes`."
)
if dry_run:
print(f"::notice::[dry-run] would close issue #{num} ({issue.get('title')})")
title = issue.get("title", "")
print(
f"::notice::[dry-run] would close issue #{num} ({title})"
)
closed += 1
continue
# Comment first, then close. Order matters: a closed issue can
+30 -9
View File
@@ -17,7 +17,6 @@ import urllib.error
import urllib.request
from urllib.parse import quote
TRUE_VALUES = {"1", "true", "yes", "on", "disabled", "disable"}
PROD_CP_URL = "https://api.moleculesai.app"
DEFAULT_REQUIRED_CONTEXTS = [
@@ -60,7 +59,10 @@ def build_plan(env: dict[str, str]) -> dict:
}
short_sha = sha[:7]
target_tag = env.get("PROD_AUTO_DEPLOY_TARGET_TAG", "").strip() or f"staging-{short_sha}"
target_tag = (
env.get("PROD_AUTO_DEPLOY_TARGET_TAG", "").strip()
or f"staging-{short_sha}"
)
canary_slug = env.get("PROD_AUTO_DEPLOY_CANARY_SLUG", "hongming").strip()
body = {
"target_tag": target_tag,
@@ -78,7 +80,9 @@ def build_plan(env: dict[str, str]) -> dict:
body["canary_slug"] = canary_slug
cp_url = env.get("CP_URL", "").strip() or PROD_CP_URL
if cp_url != PROD_CP_URL and not truthy_flag(env.get("PROD_ALLOW_NON_PROD_CP_URL", "")):
if cp_url != PROD_CP_URL and not truthy_flag(
env.get("PROD_ALLOW_NON_PROD_CP_URL", "")
):
raise ValueError(
f"Refusing production deploy to CP_URL={cp_url!r}; "
f"set PROD_ALLOW_NON_PROD_CP_URL=true for an explicit non-prod drill"
@@ -127,7 +131,11 @@ def required_contexts(env: dict[str, str]) -> list[str]:
raw = env.get("PROD_AUTO_DEPLOY_REQUIRED_CONTEXTS", "")
if not raw.strip():
return DEFAULT_REQUIRED_CONTEXTS
return [line.strip() for line in raw.replace(",", "\n").splitlines() if line.strip()]
return [
line.strip()
for line in raw.replace(",", "\n").splitlines()
if line.strip()
]
def _api_json(url: str, token: str) -> dict:
@@ -176,10 +184,14 @@ def live_disable_flag(env: dict[str, str]) -> str:
def assert_not_disabled(env: dict[str, str]) -> None:
plan = build_plan(env)
if not plan.get("enabled"):
raise RuntimeError(plan.get("disabled_reason", "production auto-deploy disabled"))
raise RuntimeError(
plan.get("disabled_reason", "production auto-deploy disabled")
)
live_value = live_disable_flag(env)
if truthy_flag(live_value):
raise RuntimeError(f"PROD_AUTO_DEPLOY_DISABLED={live_value} (live Gitea variable)")
raise RuntimeError(
f"PROD_AUTO_DEPLOY_DISABLED={live_value} (live Gitea variable)"
)
def wait_for_ci_context(env: dict[str, str]) -> str:
@@ -221,15 +233,24 @@ def wait_for_ci_context(env: dict[str, str]) -> str:
if all(context_is_satisfied(state) for state in states.values()):
return "success"
time.sleep(interval)
last = ", ".join(f"{context}={state}" for context, state in last_states.items()) or "none"
raise TimeoutError(f"Timed out waiting {timeout}s for required CI contexts; last_states={last}")
last = ", ".join(
f"{context}={state}"
for context, state in last_states.items()
) or "none"
raise TimeoutError(
f"Timed out waiting {timeout}s for required CI contexts; "
f"last_states={last}"
)
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
sub = parser.add_subparsers(dest="command", required=True)
sub.add_parser("plan", help="print production deploy plan as JSON")
sub.add_parser("assert-enabled", help="fail if production deploy is currently disabled")
sub.add_parser(
"assert-enabled",
help="fail if production deploy is currently disabled",
)
sub.add_parser("wait-ci", help="block until required CI context is green")
args = parser.parse_args()
+6 -2
View File
@@ -125,7 +125,9 @@ OWNER, NAME = (REPO.split("/", 1) + [""])[:2] if REPO else ("", "")
API = f"https://{GITEA_HOST}/api/v1" if GITEA_HOST else ""
API_TIMEOUT_SEC = int(_env("STATUS_REAPER_API_TIMEOUT_SEC", default="30") or "30")
API_RETRIES = int(_env("STATUS_REAPER_API_RETRIES", default="3") or "3")
API_RETRY_SLEEP_SEC = float(_env("STATUS_REAPER_API_RETRY_SLEEP_SEC", default="2") or "2")
API_RETRY_SLEEP_SEC = float(
_env("STATUS_REAPER_API_RETRY_SLEEP_SEC", default="2") or "2"
)
# Compensating-status description prefix. Used as the marker so a human
# auditing commit statuses can tell at a glance that the green was
@@ -506,7 +508,9 @@ def reap(
successful_contexts = {
(s.get("context") or "")
for s in statuses
if isinstance(s, dict) and (s.get("status") or s.get("state") or "") == "success"
if isinstance(s, dict) and (
s.get("status") or s.get("state") or ""
) == "success"
}
for s in statuses:
if not isinstance(s, dict):