Files
enteros-openclaw/.gitea/workflows/ci.yml
T
core-devops 4b9b9a3962
Build and Publish Patched OpenClaw / sync-upstream (push) Has been skipped
minimal-validate baseline hygiene OK
Build and Publish Patched OpenClaw / test (push) Successful in 36m0s
CI / Patch overlay integrity (push) Successful in 13s
minimal-ci / minimal-validate (push) Successful in 25s
CI / all-required (push) Successful in 12s
fix(ci): upstream-sync opens a PR instead of pushing to protected main (#2)
2026-07-07 16:46:28 +00:00

145 lines
6.5 KiB
YAML

name: CI
# Phantom-required-check fix (CI-robustness).
#
# main branch protection requires exactly the status context
# `CI / all-required (pull_request)`
# but the only workflow in this repo was `build-and-publish.yml`
# (workflow name "Build and Publish Patched OpenClaw"), so NOTHING ever
# posted `CI / all-required` → every PR sat permanently grey/pending and
# could never satisfy branch protection. This workflow emits that exact
# context.
#
# It is deliberately HERMETIC and deterministic: it validates the patch
# OVERLAY that this repo owns (it stores only the patched files on top of
# upstream openclaw) WITHOUT any network access — no upstream clone, no
# registry, no package index — so a transient network/registry blip can
# never false-red the required merge gate. The heavy, network-dependent
# real build/publish continues to run in build-and-publish.yml on
# push/schedule (its pull_request trigger is dropped — it is air-gapped and
# would false-red every PR); this hermetic gate guarantees the overlay is
# structurally sound and that no workflow pushes directly to protected main.
on: [push, pull_request]
env:
GITHUB_SERVER_URL: https://git.moleculesai.app
permissions:
contents: read
jobs:
overlay-integrity:
name: Patch overlay integrity
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Validate patch overlay (hermetic — no upstream fetch)
run: |
set -euo pipefail
fail() { echo "::error::$*"; exit 1; }
# 1. PATCHED_VERSION.md must exist and pin a concrete upstream ref.
test -f PATCHED_VERSION.md || fail "PATCHED_VERSION.md missing"
UPSTREAM_REF=$(awk '/^Upstream:/ {print $2; exit}' PATCHED_VERSION.md)
test -n "$UPSTREAM_REF" || fail "PATCHED_VERSION.md has no 'Upstream: <ref>' pin"
# A 40-hex commit SHA is what build-and-publish.yml checks out; a
# floating tag/branch would make the build non-reproducible.
echo "$UPSTREAM_REF" | grep -Eq '^[0-9a-f]{40}$' \
|| fail "Upstream ref '$UPSTREAM_REF' is not a 40-hex commit SHA (build must pin a reproducible ref)"
echo "::notice::upstream pinned at $UPSTREAM_REF"
# 2. The patched source tree this repo overlays must be present and
# non-empty (build-and-publish.yml does `cp -r src/. upstream/src/`).
test -d src || fail "src/ patch tree missing"
test -n "$(find src -type f -print -quit)" || fail "src/ patch tree is empty — nothing to overlay"
# 3. Dockerfile must exist (the publish job builds it).
test -f Dockerfile || fail "Dockerfile missing"
echo "::notice::patch overlay OK ($(find src -type f | wc -l) patched file(s))"
- name: Validate workflow YAML parses (hermetic)
run: |
set -euo pipefail
python3 - <<'PY'
import glob, sys, yaml
bad = 0
for f in sorted(glob.glob(".gitea/workflows/*.yml") + glob.glob(".gitea/workflows/*.yaml")):
try:
yaml.safe_load(open(f, encoding="utf-8"))
print("ok ", f)
except Exception as e:
print(f"::error::{f}: {e}"); bad += 1
sys.exit(1 if bad else 0)
PY
- name: Guard — no workflow pushes directly to protected main (hermetic)
# Regression guard for the sync-upstream fix (openclaw#2 / task #124).
# A scheduled job used to run `git push origin main`, which the
# pre-receive hook declines on every run because `main` is
# branch-protected — turning main's combined status red and violating
# the org "never push direct to main" rule. The upstream bump must go
# through a PR instead. This gate FAILS if any workflow reintroduces a
# direct push to the protected `main` branch (proven to fail on the
# pre-fix workflow); a push to a non-protected `sync/…` branch is fine.
run: |
set -euo pipefail
python3 - <<'PY'
import glob, re, sys
# Build the pattern from fragments so this guard's own source does
# not contain a contiguous literal that would match itself.
PUSH = r'\bgit\s+push\b'
BR = 'ma' + 'in'
to_protected = re.compile(
PUSH + r'[^\n]*(?:\borigin\s+\+?' + BR + r'\b|:' + BR + r'(?:["\'\s]|$))'
)
offenders = []
for f in sorted(glob.glob(".gitea/workflows/*.yml") + glob.glob(".gitea/workflows/*.yaml")):
for n, line in enumerate(open(f, encoding="utf-8"), 1):
if line.lstrip().startswith("#"): # skip YAML / shell comment prose
continue
code = line.split("#", 1)[0] # drop trailing inline comment
if to_protected.search(code):
offenders.append(f"{f}:{n}: {line.strip()}")
if offenders:
print("::error::workflow pushes directly to protected `main` — open a PR instead:")
for o in offenders:
print(" " + o)
sys.exit(1)
print("OK: no workflow pushes directly to protected main")
PY
# Stable aggregate required-context. Branch protection pins exactly
# `CI / all-required (pull_request)`; this job emits it and is green IFF
# every real job above succeeded. Mirrors the fleet `all-required`
# convention (molecule-external-workspace-sdk / template-langgraph / -hermes).
all-required:
name: all-required
needs: [overlay-integrity]
if: ${{ always() }}
runs-on: ubuntu-latest
timeout-minutes: 2
steps:
- name: Assert every required job succeeded
run: |
set -euo pipefail
results='${{ toJSON(needs) }}'
echo "$results"
echo "$results" | python3 -c '
import json, sys
ns = json.load(sys.stdin)
# Green only if each needed job is success (or a legitimate skip).
# failure/cancelled/None => NOT green => hard-fail the required gate.
bad = [(k, v.get("result")) for k, v in ns.items()
if v.get("result") not in ("success", "skipped")]
if bad:
print("FAIL: required jobs not green:", file=sys.stderr)
for k, r in bad:
print(f" - {k}: {r}", file=sys.stderr)
sys.exit(1)
print(f"OK: all {len(ns)} required job(s) succeeded")
'