Files
molecule-ai-workspace-templ…/.gitea/workflows/ci.yml
T
hongmingandClaude Opus 5 f020f5c9c7
meta-ci-advisory / meta (advisory) (pull_request) Successful in 12s
CI / Template validation (static) (pull_request) Successful in 18s
verify-providers-projection / Regenerate projection, fail on drift, assert registry ⊆ template (pull_request) Successful in 17s
CI / Template validation (static) (push) Successful in 20s
CI / Adapter unit tests (push) Successful in 1m24s
CI / Adapter unit tests (pull_request) Successful in 1m29s
CI / Template validation (runtime) (push) Successful in 1m16s
CI / T4 tier-4 conformance (live) (push) Successful in 1m20s
CI / validate (push) Successful in 3s
CI / Template validation (runtime) (pull_request) Successful in 1m26s
CI / T4 tier-4 conformance (live) (pull_request) Successful in 1m30s
CI / validate (pull_request) Successful in 3s
ci: keep the docker build log when the build fails
Class fix, ported from template-hermes. Both jobs that build this template's
image ran

    docker build ... --no-cache 2>&1 | tail -5

A failing buildkit run ends with the offending Dockerfile fragment and a
"failed to solve ... exit code: 1" summary, so those five lines are the same
every time and never contain the cause. On the hermes sibling that turned a
transient into a manual sweep against the live forge just to distinguish a bad
pin from a network blip; the rerun was green, so the only lasting artifact of
the incident was that CI could not explain its own failure. All four workspace
templates carried the identical pipe.

Nothing here changes what CI accepts -- only what it can tell you when it
refuses.

The build is now redirected to a file instead of piped, and the whole log is
printed inside a collapsed ::group:: when it fails. The FULL log, not a bigger
tail: buildkit interleaves parallel stages, so the failing layer's output can
sit arbitrarily far from the end, and a larger constant is the same defect with
a different number. Redirect rather than pipe because `tail` should not be in a
position to own the build's exit status.

Kept INLINE rather than extracted to a helper: tests/test_ci_runtime_image_pin.py
already pins "exactly one `docker build` step per job" as part of the
runtime-image contract, and a first attempt to source a shell helper broke that
guard in all four templates. The guard was right; the refactor was wrong. The
failure-path echoes say "image build" for the same reason -- "docker build" in
an echo would inflate that count.

test_failing_image_build_surfaces_its_own_output covers both jobs and kills
three mutants: restoring the pipe, printing a tail instead of the full log on
failure, and swallowing the build's exit status.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 04:19:32 -07:00

655 lines
34 KiB
YAML

name: CI
# Codex workspace-template CI. The jobs keep protected context names stable,
# fetch canonical validation code from molecule-ci at a reviewed immutable
# commit, and make runner requirements explicit.
#
# Workflow compatibility choices:
# - No `workflow_dispatch.inputs`
# - No `merge_group:` trigger
# - Canonical validator is fetched by exact SHA instead of delegated
# - workflow-level GITHUB_SERVER_URL pinned
# (feedback_act_runner_github_server_url)
# - No `on.push.paths:` (would permanently block path-excluded pushes;
# feedback_path_filtered_workflow_cant_be_required)
# - timeout-minutes on every job
# - A single `validate` aggregator emits the required-check name with
# a job that ALWAYS runs (feedback_branch_protection_check_name_parity)
#
# Cross-links:
# - RFC internal#456 §9-11 — T4 escalation leg + tier-4 conformance
# - molecule-ai/molecule-ci/scripts/validate-workspace-template.py — pattern source
# - claude-code template ci.yml (bbc2dae) — Gitea port style reference
on: [push, pull_request]
# Defense-in-depth de-dup ONLY (the t4-conformance per-run-unique
# tag/name fix below is the actual fail-closed primitive against the
# shared-host-daemon race; see that job). Scope per workflow + ref +
# EVENT so the push run and the pull_request run of the same internal-PR
# commit get DISTINCT groups — they must BOTH complete (each emits its
# own required-status context;
# feedback_gitea_gate_check_required_list_not_combined_status). Never
# per-SHA-global: that silently cross-cancels legit required checks
# (feedback_concurrency_group_per_sha). cancel-in-progress:false so an
# in-flight live T4 probe is never aborted mid-assertion (a cancelled
# privileged probe would look like a gate failure / flake); a newer push
# to the same ref+event simply queues behind it.
concurrency:
group: ci-${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: false
env:
GITHUB_SERVER_URL: https://git.moleculesai.app
# Canonical validation tooling is reviewed and content-addressed.
MOLECULE_CI_REF: 11b8598e5c0b3f0b1031733a8d5f6bc238f146a4
# HERMETIC dep policy (CI-robustness): do NOT set PIP_INDEX_URL=private —
# it force-routes EVERY pip install (pytest, pyyaml, jsonschema, …) at the
# private index, which serves only molecules-workspace-runtime → 404s all
# public tooling (the Adapter-unit-tests / Template-validation reds). Public
# tooling resolves from the default (PyPI) index; the private runtime — which
# is ABSENT from public PyPI, so no dependency-confusion — is pulled via an
# EXPLICIT --extra-index-url on the runtime install only. The legacy name
# molecule-ai-workspace-runtime IS squatted on public PyPI (0.1.999999); we
# install the CURRENT dist name molecules-workspace-runtime (private-only).
MOLECULE_PRIVATE_INDEX: https://git.moleculesai.app/api/packages/molecule-ai/pypi/simple/
# Defense-in-depth on GITHUB_TOKEN scope. validate-runtime +
# t4-conformance run untrusted-by-design code (pip-install, adapter
# import, docker build/run of the PR's Dockerfile). `contents: read`
# means the worst a malicious template PR can do with the token is
# read public repo state.
permissions:
contents: read
jobs:
validate-static:
name: Template validation (static)
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Fetch immutable molecule-ci canonical scripts
run: |
set -euo pipefail
CI_ROOT=".molecule-ci-canonical"
mkdir "$CI_ROOT"
git -C "$CI_ROOT" init -q
git -C "$CI_ROOT" remote add origin https://git.moleculesai.app/molecule-ai/molecule-ci.git
fetched=false
for attempt in 1 2 3; do
if GIT_ASKPASS=/bin/false GIT_TERMINAL_PROMPT=0 git -c credential.helper= -c http.userAgent=curl/8.4.0 -C "$CI_ROOT" fetch --no-tags --depth 1 origin "$MOLECULE_CI_REF"; then
fetched=true
break
fi
sleep "$attempt"
done
if [ "$fetched" != true ]; then
echo "::error::immutable molecule-ci validator fetch failed"
exit 1
fi
git -C "$CI_ROOT" checkout -q --detach FETCH_HEAD
ACTUAL_CI_REF="$(git -C "$CI_ROOT" rev-parse HEAD)"
if [ "$ACTUAL_CI_REF" != "$MOLECULE_CI_REF" ]; then
echo "::error::immutable molecule-ci validator ref mismatch"
exit 1
fi
# Secret scan — most important check. Always runs, including on
# fork PRs (no third-party code executes here).
- name: Check for secrets
run: |
python3 - << 'PYEOF'
import os, re, sys
from pathlib import Path
PATTERNS = [
re.compile(r'''["']sk-ant-[a-zA-Z0-9]{50,}["']'''),
re.compile(r'''["']ghp_[a-zA-Z0-9]{36,}["']'''),
re.compile(r'''["']AKIA[A-Z0-9]{16}["']'''),
re.compile(r'''["'][a-zA-Z0-9/+=]{40}["']'''),
re.compile(r'''["']sk_test_[a-zA-Z0-9]{24,}["']'''),
re.compile(r'''["']sk-proj-[a-zA-Z0-9_-]{20,}["']'''),
re.compile(r'''["']Bearer\s+[a-zA-Z0-9_.-]{20,}["']'''),
re.compile(r'''ghp_[a-zA-Z0-9]{36,}'''),
re.compile(r'''sk-ant-[a-zA-Z0-9]{50,}'''),
]
SKIP_DIRS = {'.molecule-ci', '.molecule-ci-canonical', '.git', 'node_modules', '__pycache__'}
EXTENSIONS = {'.yaml', '.yml', '.md', '.py', '.sh', '.toml'}
def is_false_positive(line):
ctx = line.lower()
return '...' in ctx or '<example' in ctx or '</example' in ctx
root = Path(os.environ.get('GITHUB_WORKSPACE', '.'))
warnings = []
for dirpath, dirnames, filenames in os.walk(root):
dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS]
for filename in filenames:
if Path(filename).suffix not in EXTENSIONS:
continue
filepath = Path(dirpath) / filename
try:
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
for lineno, line in enumerate(f.readlines(), 1):
for pattern in PATTERNS:
for match in pattern.finditer(line):
if not is_false_positive(line):
warnings.append(f" {filepath}:{lineno}: {match.group(0)[:40]}...")
except Exception:
pass
if warnings:
print("::error::Potential secret found in committed files:")
for w in warnings:
print(w)
sys.exit(1)
else:
print("::notice::No secrets detected")
PYEOF
- run: pip install pyyaml packaging -q
- name: Reject legacy declared-plugin installer
run: |
python3 - << 'PYEOF'
from pathlib import Path
from packaging.requirements import Requirement
from packaging.version import Version
boot_scripts = [
path for name in ("entrypoint.sh", "start.sh")
if (path := Path(name)).exists()
]
executable_lines = "\n".join(
line
for path in boot_scripts
for line in path.read_text(encoding="utf-8").splitlines()
if not line.lstrip().startswith("#")
)
forbidden = (
"MOLECULE_DECLARED_PLUGINS",
"molecule_runtime.plugin_sources",
"install_declared_plugins",
"for _plg_src in $MOLECULE_DECLARED_PLUGINS",
"/archive/${_plg_ref}.tar.gz",
"Authorization: token ${MOLECULE_TEMPLATE_REPO_TOKEN}",
'mkdir -p "/configs/plugins/$_plg_name"',
'cp -a "$_plg_dir/." "/configs/plugins/$_plg_name/"',
)
found = [fragment for fragment in forbidden if fragment in executable_lines]
if found:
raise SystemExit(
"legacy privileged declared-plugin installer detected: "
+ ", ".join(found)
)
runtime_version = Version(Path(".runtime-version").read_text().strip())
hardened_version = Version("0.4.0")
runtime_requirement = next(
Requirement(line)
for line in Path("requirements.txt").read_text().splitlines()
if line.strip().startswith("molecules-workspace-runtime")
)
if runtime_version < hardened_version:
raise SystemExit("runtime pin predates hardened plugin installer")
if runtime_version not in runtime_requirement.specifier:
raise SystemExit("runtime pin is outside the template requirement")
hardened_floor = any(
specifier.operator in {">=", ">", "==", "===", "~="}
and Version(specifier.version) >= hardened_version
for specifier in runtime_requirement.specifier
)
if not hardened_floor:
raise SystemExit("template requirement lacks a hardened runtime floor")
if Version("0.3.125") in runtime_requirement.specifier:
raise SystemExit("template requirement permits vulnerable runtime")
PYEOF
- run: python3 .molecule-ci-canonical/scripts/validate-workspace-template.py --static-only
# Audit E1: scripts/molecule-askpass is byte-copied across every workspace
# template (no shared base image yet). Pin its sha256 so an unintentional
# edit — or a hand-copy that drifts from its peers — reds this PR. All
# templates pin the SAME value; a legitimate change must update the file
# AND this pin in every template together (which a reviewer sees). Always
# fires (validate-static has no paths filter / fork guard) and feeds the
# required `validate` aggregate, so it is a hard gate with no secrets.
- name: askpass helper drift guard (audit E1)
run: |
expected=7def3e86a3edf8ff28018081f14a6e390790cb102fd06c27c3f7040b07464303
test -f scripts/molecule-askpass || { echo "::error::scripts/molecule-askpass is missing"; exit 1; }
actual=$(sha256sum scripts/molecule-askpass | cut -d' ' -f1)
if [ "$actual" != "$expected" ]; then
echo "::error::scripts/molecule-askpass drifted from the pinned cross-template sha256."
echo " expected $expected"
echo " actual $actual"
echo " If this change is intentional, apply it identically to EVERY workspace template and bump this pin in each template's ci.yml."
exit 1
fi
echo "askpass pin OK ($actual)"
# Shell lint the boot + provider/MCP helper scripts. These are
# the load-bearing scripts for the codex list_peers-401 fix and
# the agent-owned-token contract — a syntax slip here would ship
# an image that boots blind to peers. Non-fatal style nits are
# tolerated (-S error) but real errors fail static.
- name: ShellCheck boot + provider helpers
run: |
sudo apt-get update -qq && sudo apt-get install -y -qq shellcheck
shellcheck -S error start.sh codex_minimax_config.sh codex_mcp_config.sh codex_auth_sync.sh
# pc2-safe: false — needs docker for Docker build smoke; routes to docker-host only
# (task #390 B-lite — PC2 native runners advertise `ubuntu-latest` but cannot
# docker due to npipe→Linux-container bridging gap; AND-match on both labels
# excludes them while keeping the Linux docker-host runners eligible.)
validate-runtime:
name: Template validation (runtime)
runs-on: [ubuntu-latest, docker-host]
timeout-minutes: 15
needs: validate-static
if: github.event.pull_request.head.repo.fork != true
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Fetch immutable molecule-ci canonical scripts
run: |
set -euo pipefail
CI_ROOT=".molecule-ci-canonical"
mkdir "$CI_ROOT"
git -C "$CI_ROOT" init -q
git -C "$CI_ROOT" remote add origin https://git.moleculesai.app/molecule-ai/molecule-ci.git
fetched=false
for attempt in 1 2 3; do
if GIT_ASKPASS=/bin/false GIT_TERMINAL_PROMPT=0 git -c credential.helper= -c http.userAgent=curl/8.4.0 -C "$CI_ROOT" fetch --no-tags --depth 1 origin "$MOLECULE_CI_REF"; then
fetched=true
break
fi
sleep "$attempt"
done
if [ "$fetched" != true ]; then
echo "::error::immutable molecule-ci validator fetch failed"
exit 1
fi
git -C "$CI_ROOT" checkout -q --detach FETCH_HEAD
ACTUAL_CI_REF="$(git -C "$CI_ROOT" rev-parse HEAD)"
if [ "$ACTUAL_CI_REF" != "$MOLECULE_CI_REF" ]; then
echo "::error::immutable molecule-ci validator ref mismatch"
exit 1
fi
- run: pip install pyyaml -q
- if: hashFiles('requirements.txt') != ''
run: pip install -q --extra-index-url "$MOLECULE_PRIVATE_INDEX" -r requirements.txt
- if: hashFiles('requirements.txt') == ''
run: pip install -q --extra-index-url "$MOLECULE_PRIVATE_INDEX" molecules-workspace-runtime
- run: python3 .molecule-ci-canonical/scripts/validate-workspace-template.py
- name: Docker build smoke test
if: hashFiles('Dockerfile') != ''
run: |
set -euo pipefail
: "${GITHUB_RUN_ID:?GITHUB_RUN_ID is required}"
: "${GITHUB_RUN_ATTEMPT:?GITHUB_RUN_ATTEMPT is required}"
SMOKE_TAG="molecule-ai-workspace-codex-smoke-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
trap 'docker image rm -f "$SMOKE_TAG" >/dev/null 2>&1 || true' EXIT
if ! docker info >/dev/null 2>&1; then
echo "::warning::docker daemon unreachable from runner job container — skipping Docker build smoke (runner-config gap, not a template issue)."
exit 0
fi
if [ ! -s .runtime-version ]; then
echo "::error::.runtime-version is missing or empty"
exit 1
fi
EXPECTED_RUNTIME_VERSION="$(tr -d '[:space:]' < .runtime-version)"
# Redirect, do not pipe: `tail` must not be able to truncate the cause
# or own the exit status. On failure the WHOLE log is printed -- a fixed
# window is what made template-hermes#342 undiagnosable, and buildkit
# interleaves parallel stages so the failing layer can sit far from the end.
BUILD_LOG="$(mktemp)"
rc=0
docker build --build-arg RUNTIME_VERSION="$EXPECTED_RUNTIME_VERSION" -t "$SMOKE_TAG" . --no-cache >"$BUILD_LOG" 2>&1 || rc=$?
if [ "$rc" -ne 0 ]; then
echo "::group::image build output ($(wc -l <"$BUILD_LOG") lines, build failed)"
cat "$BUILD_LOG"
echo "::endgroup::"
echo "::error::image build failed (exit $rc)"
exit "$rc"
fi
tail -5 "$BUILD_LOG"
ACTUAL_RUNTIME_VERSION="$(
docker run --rm --entrypoint python3 "$SMOKE_TAG" \
-c 'from importlib.metadata import version; print(version("molecules-workspace-runtime"))'
)"
if [ "$ACTUAL_RUNTIME_VERSION" != "$EXPECTED_RUNTIME_VERSION" ]; then
echo "::error::image runtime $ACTUAL_RUNTIME_VERSION does not match .runtime-version $EXPECTED_RUNTIME_VERSION"
exit 1
fi
echo "Docker build succeeded with exact runtime $ACTUAL_RUNTIME_VERSION"
# --- Layer-3: real T4 tier-4 conformance gate (RFC internal#456 §11) ---
# NOT a string-match. Builds the actual image, runs it under the
# EXACT flags the controlplane provisioner emits for tier-4
# (userdata_containerized.go @ec2384c: --privileged --pid=host
# -v /:/host -v /var/run/docker.sock:/var/run/docker.sock), then
# asserts BOTH properties on the RUNNING container, atomically
# (RFC §10 — either failing fails the build):
# (a) the uid-1000 agent can attain host root
# (sudo nsenter --target 1 --mount --pid -- id -u == 0)
# (b) the codex MCP child resolves an agent-owned .auth_token
# (the list_peers-401 class — RFC internal#456 §10). We assert
# this on the REAL boot path: run start.sh's /configs chown,
# then have configs_dir.resolve() (under the MCP child's env,
# i.e. with the CONFIGS_DIR codex_mcp_config.sh would write)
# point at an agent-owned token file — exactly what the codex
# MCP subprocess does before calling list_peers.
t4-conformance:
name: T4 tier-4 conformance (live)
runs-on: docker-host
timeout-minutes: 15
needs: validate-static
if: github.event.pull_request.head.repo.fork != true
env:
MOLECULE_CI_REF: 11b8598e5c0b3f0b1031733a8d5f6bc238f146a4
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Build the runtime image
id: build
run: |
set -euo pipefail
if ! docker info >/dev/null 2>&1; then
echo "::error::docker daemon unreachable — T4 conformance gate CANNOT verify host-root reach. This is a hard gate; failing closed (do NOT treat as skip). Fix runner-config (internal#222) to unblock."
exit 1
fi
# Per-run-unique image tag. This repo triggers
# `on: [push, pull_request]`, so a single push to an internal PR
# branch fires BOTH a `push` run AND a `pull_request` run for the
# SAME commit. The runners share one host Docker daemon
# (--network host + /var/run/docker.sock bind), so a STATIC
# `-t t4-conformance-test` / `--name t4probe` collides across the
# two concurrent same-commit runs: whichever event creates the
# container first wins, the other dies with
# "docker: Error response from daemon: Conflict. The container
# name "/t4probe" is already in use ..." (exit 125)
# — a FALSE NEGATIVE (the probe never runs; the build that wins
# the race passes all assertions). Scope every host-daemon object
# to GITHUB_RUN_ID + GITHUB_RUN_ATTEMPT so push and pull_request
# runs of the same SHA are isolated. This does NOT weaken the
# gate — the same privileged container, the same real start.sh
# /configs chown, and the same host-root + MCP-token assertions
# still run, just under collision-proof names. (RFC internal#456
# §11; feedback_concurrency_group_per_sha — unique-name is the
# fail-closed primitive, concurrency is only de-dup.)
# Deterministic function of run-id/attempt — recomputed
# identically in the next step (no cross-step $GITHUB_ENV
# round-trip, which can be fragile under act_runner).
if [ ! -s .runtime-version ]; then
echo "::error::.runtime-version is missing or empty"
exit 1
fi
T4_TAG="t4-conformance-test:${GITHUB_RUN_ID:-local}-${GITHUB_RUN_ATTEMPT:-1}"
MCP_VERIFY_CONTAINER="mcp-built-image-e2e-${GITHUB_RUN_ID:-local}-${GITHUB_RUN_ATTEMPT:-1}"
: "${RUNNER_TEMP:?RUNNER_TEMP is required}"
CI_ROOT="$RUNNER_TEMP/molecule-ci-final-image-${GITHUB_RUN_ID:-local}-${GITHUB_RUN_ATTEMPT:-1}"
MCP_ATTESTATION="$RUNNER_TEMP/mcp-built-image-attestation-${GITHUB_RUN_ID:-local}-${GITHUB_RUN_ATTEMPT:-1}.json"
MCP_ATTESTATION_TMP="${MCP_ATTESTATION}.tmp"
MCP_ATTESTATION_SHA256="${MCP_ATTESTATION}.sha256"
MCP_E2E_LOG="$RUNNER_TEMP/mcp-built-image-e2e-${GITHUB_RUN_ID:-local}-${GITHUB_RUN_ATTEMPT:-1}.log"
KEEP_T4_IMAGE=0
cleanup_t4_build() {
docker rm -f "$MCP_VERIFY_CONTAINER" >/dev/null 2>&1 || true
rm -rf -- "$CI_ROOT"
rm -f -- "$MCP_ATTESTATION" "$MCP_ATTESTATION_TMP" "$MCP_ATTESTATION_SHA256" "$MCP_E2E_LOG"
if [ "$KEEP_T4_IMAGE" -ne 1 ]; then
docker image rm -f "$T4_TAG" >/dev/null 2>&1 || true
fi
}
trap cleanup_t4_build EXIT
mkdir "$CI_ROOT"
git init -q "$CI_ROOT"
git -C "$CI_ROOT" remote add origin https://git.moleculesai.app/molecule-ai/molecule-ci.git
fetched=false
for attempt in 1 2 3; do
if GIT_ASKPASS=/bin/false GIT_TERMINAL_PROMPT=0 \
git -c credential.helper= -c http.userAgent=curl/8.4.0 \
-C "$CI_ROOT" \
fetch --no-tags --depth 1 origin "$MOLECULE_CI_REF"; then
fetched=true
break
fi
sleep "$attempt"
done
if [ "$fetched" != true ]; then
echo "::error::immutable molecule-ci verifier fetch failed"
exit 1
fi
git -C "$CI_ROOT" checkout -q --detach FETCH_HEAD
test "$(git -C "$CI_ROOT" rev-parse HEAD)" = "$MOLECULE_CI_REF"
git -C "$CI_ROOT" diff --quiet --no-ext-diff --no-textconv "$MOLECULE_CI_REF" -- scripts/mcp_pin_lockstep.py scripts/mcp_built_image_e2e.py
python3 "$CI_ROOT/scripts/mcp_pin_lockstep.py" --repo-root . --json > "$MCP_ATTESTATION_TMP"
mv "$MCP_ATTESTATION_TMP" "$MCP_ATTESTATION"
sha256sum "$MCP_ATTESTATION" > "$MCP_ATTESTATION_SHA256"
git -C "$CI_ROOT" diff --quiet --no-ext-diff --no-textconv "$MOLECULE_CI_REF" -- scripts/mcp_pin_lockstep.py scripts/mcp_built_image_e2e.py
sha256sum --check "$MCP_ATTESTATION_SHA256"
EXPECTED_RUNTIME_VERSION="$(
python3 - "$CI_ROOT/scripts/mcp_built_image_e2e.py" "$MCP_ATTESTATION" <<'PY'
import importlib.util
from pathlib import Path
import sys
verifier_path = Path(sys.argv[1])
spec = importlib.util.spec_from_file_location(
"_molecule_ci_mcp_built_image_e2e", verifier_path
)
if spec is None or spec.loader is None:
raise SystemExit("could not load immutable built-image verifier")
verifier = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = verifier
spec.loader.exec_module(verifier)
with Path(sys.argv[2]).open("rb") as stream:
print(verifier.load_attestation(stream).runtime_version)
PY
)"
# Redirect, do not pipe: `tail` must not be able to truncate the cause
# or own the exit status. On failure the WHOLE log is printed -- a fixed
# window is what made template-hermes#342 undiagnosable, and buildkit
# interleaves parallel stages so the failing layer can sit far from the end.
BUILD_LOG="$(mktemp)"
rc=0
docker build --build-arg RUNTIME_VERSION="$EXPECTED_RUNTIME_VERSION" -t "$T4_TAG" . --no-cache >"$BUILD_LOG" 2>&1 || rc=$?
if [ "$rc" -ne 0 ]; then
echo "::group::image build output ($(wc -l <"$BUILD_LOG") lines, build failed)"
cat "$BUILD_LOG"
echo "::endgroup::"
echo "::error::image build failed (exit $rc)"
exit "$rc"
fi
tail -5 "$BUILD_LOG"
ACTUAL_RUNTIME_VERSION="$(
docker run --rm --entrypoint python3 "$T4_TAG" \
-c 'from importlib.metadata import version; print(version("molecules-workspace-runtime"))'
)"
if [ "$ACTUAL_RUNTIME_VERSION" != "$EXPECTED_RUNTIME_VERSION" ]; then
echo "::error::T4 image runtime $ACTUAL_RUNTIME_VERSION does not match .runtime-version $EXPECTED_RUNTIME_VERSION"
exit 1
fi
docker rm -f "$MCP_VERIFY_CONTAINER" >/dev/null 2>&1 || true
docker create --interactive --name "$MCP_VERIFY_CONTAINER" \
--network none \
--user 1000:1000 --workdir /tmp \
--cap-drop ALL --security-opt no-new-privileges \
--pids-limit 128 --memory 768m --cpus 1 \
--tmpfs /tmp:size=64m \
--entrypoint python3 "$T4_TAG" \
/mcp_built_image_e2e.py >/dev/null
git -C "$CI_ROOT" diff --quiet --no-ext-diff --no-textconv "$MOLECULE_CI_REF" -- scripts/mcp_pin_lockstep.py scripts/mcp_built_image_e2e.py
docker cp "$CI_ROOT/scripts/mcp_built_image_e2e.py" \
"$MCP_VERIFY_CONTAINER:/mcp_built_image_e2e.py"
sha256sum --check "$MCP_ATTESTATION_SHA256"
docker start --attach --interactive "$MCP_VERIFY_CONTAINER" \
< "$MCP_ATTESTATION" \
| tee "$MCP_E2E_LOG"
docker rm "$MCP_VERIFY_CONTAINER" >/dev/null
grep -qxF 'mcp-built-image-e2e:sentinel:executed' "$MCP_E2E_LOG"
KEEP_T4_IMAGE=1
- name: Run under EXACT tier-4 provisioner flags + assert host-root reach AND MCP token agent-ownership
run: |
set -euo pipefail
# EXACT flags from controlplane userdata_containerized.go
# (tier-4 emission @ec2384c). Override the entrypoint with a
# sleep so we can probe the agent context directly.
# Per-run-unique container name + image tag (see Build step
# rationale): the shared host Docker daemon is concurrently used
# by the SAME commit's push + pull_request runs. A static name
# collides and false-negatives. Recompute the SAME deterministic
# tag the build step used (pure function of run-id/attempt — no
# cross-step env reliance, fragile under act_runner).
T4_TAG="t4-conformance-test:${GITHUB_RUN_ID:-local}-${GITHUB_RUN_ATTEMPT:-1}"
T4_PROBE="t4probe-${GITHUB_RUN_ID:-local}-${GITHUB_RUN_ATTEMPT:-1}"
# Defensive pre-removal of a LEAKED container from a crashed
# prior attempt of THIS SAME run-id only (the name is run-id
# scoped, so this can never reap a concurrent foreign run's
# probe). This is cleanup of our own debris, NOT a gate bypass —
# no `|| true` on the real assertions below; failure still fails.
trap 'docker rm -f "$T4_PROBE" >/dev/null 2>&1 || true; docker rmi -f "$T4_TAG" >/dev/null 2>&1 || true' EXIT
docker rm -f "$T4_PROBE" >/dev/null 2>&1 || true
docker run -d \
--name "$T4_PROBE" \
--network host \
--privileged \
--pid=host \
-v /:/host \
-v /var/run/docker.sock:/var/run/docker.sock \
--entrypoint /bin/sh \
"$T4_TAG" -c 'sleep 600' >/dev/null
echo "=== Reproduce the agent-owned-token half of the REAL boot path ==="
# start.sh (as root, pre-gosu) chowns /configs to agent so
# configs_dir.resolve() picks /configs for BOTH the runtime
# and the codex MCP child. Reproduce that exact step, then
# assert the codex MCP child (which resolves CONFIGS_DIR via
# codex_mcp_config.sh's resolve()-based logic) lands on an
# AGENT-OWNED token file — not a root-owned/absent one (the
# pre-fix list_peers-401 trap).
docker exec "$T4_PROBE" sh -c 'mkdir -p /configs && touch /configs/.auth_token && chown -R agent:agent /configs'
echo "=== (b) codex MCP child token-resolution: configs_dir.resolve() under agent + owner_uid ==="
# Resolve the configs dir the SAME way codex_mcp_config.sh
# does (configs_dir.resolve() with HOME=/home/agent), as the
# agent user, then stat the .auth_token it would read.
RESOLVED=$(docker exec -u agent -e HOME=/home/agent "$T4_PROBE" \
python3 -c 'import molecule_runtime.configs_dir as c; print(c.resolve())')
echo "MCP child resolves configs dir = $RESOLVED"
OWNER_UID=$(docker exec -u agent "$T4_PROBE" stat -c '%u' "$RESOLVED/.auth_token")
echo "owner_uid of $RESOLVED/.auth_token = $OWNER_UID"
if [ "$OWNER_UID" != "1000" ]; then
echo "::error::T4 contract violated: codex MCP child would resolve token at $RESOLVED/.auth_token owned by uid=$OWNER_UID (expected 1000). This is the codex list_peers-401 class (RFC internal#456 §10) — the MCP subprocess cannot read the agent-owned token and every list_peers/delegate_task 401s."
exit 1
fi
echo "=== (a) host-root reach AS THE uid-1000 AGENT (not root) ==="
AGENT_HOSTROOT_UID=$(docker exec -u agent "$T4_PROBE" sudo -n nsenter --target 1 --mount --pid -- id -u)
echo "agent->host-root id -u = $AGENT_HOSTROOT_UID"
if [ "$AGENT_HOSTROOT_UID" != "0" ]; then
echo "::error::T4 contract violated: uid-1000 agent could NOT attain host root via 'sudo nsenter --target 1' (got uid=$AGENT_HOSTROOT_UID). T4 escalation leg ABSENT/broken."
exit 1
fi
# Defense-in-depth: host-fs write+readback through /host from
# the agent, proving real host reach (not a namespace trick).
# The probe file path is ALSO per-run-unique: /host maps to the
# SHARED host /, so a static /host/tmp/.t4-conformance-probe
# would let the concurrent same-commit push+pull_request runs
# clobber/rm each other's marker mid-readback (a second
# false-negative of the same class). Run-id scoping keeps each
# run asserting exactly the marker it wrote — still fully
# fail-closed.
PROBE_FILE="/host/tmp/.t4-conformance-probe-${GITHUB_RUN_ID:-local}-${GITHUB_RUN_ATTEMPT:-1}"
MARKER="t4-conformance-$(date +%s)-$RANDOM"
docker exec -u agent "$T4_PROBE" sudo -n sh -c "echo $MARKER > $PROBE_FILE"
READBACK=$(docker exec -u agent "$T4_PROBE" sudo -n cat "$PROBE_FILE")
docker exec -u agent "$T4_PROBE" sudo -n rm -f "$PROBE_FILE"
if [ "$READBACK" != "$MARKER" ]; then
echo "::error::T4 host-fs write+readback through /host failed (got '$READBACK' expected '$MARKER')."
exit 1
fi
echo "::notice::T4 tier-4 conformance PASS — uid-1000 agent reaches host root AND the codex MCP child resolves an agent-owned .auth_token (both, atomically)."
# Aggregator that emits a single `validate` check name — this is the
# required-check context set on branch protection (the codex repo's
# BP had enable_status_check:true + empty contexts list, which blocks
# ALL merges per feedback_gitea_empty_status_check_contexts_blocks_merge;
# branch protection is updated to require `validate` alongside this).
validate:
name: validate
runs-on: ubuntu-latest
needs: [validate-static, validate-runtime, t4-conformance, tests]
if: always()
timeout-minutes: 1
env:
# Fork-PR detection for the T4 hard-gate below. Only external fork PRs
# legitimately skip the live T4 probe; internal PRs and main pushes must
# run it to green.
IS_FORK_PR: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true }}
steps:
- name: Aggregate
run: |
static="${{ needs.validate-static.result }}"
runtime="${{ needs.validate-runtime.result }}"
t4="${{ needs.t4-conformance.result }}"
tests="${{ needs.tests.result }}"
echo "validate-static: $static"
echo "validate-runtime: $runtime"
echo "t4-conformance: $t4"
echo "tests: $tests"
is_fork_pr="${IS_FORK_PR:-false}"
echo "is-fork-pr: $is_fork_pr"
if [ "$static" != "success" ]; then
echo "::error::validate-static did not succeed: $static"
exit 1
fi
if [ "$runtime" != "success" ] && [ "$runtime" != "skipped" ]; then
echo "::error::validate-runtime did not succeed: $runtime"
exit 1
fi
# T4 conformance is a HARD gate on internal (non-fork) PRs
# and main pushes. `skipped` is only acceptable on fork PRs
# (the `if:` fork guard short-circuits it). Any other
# non-success fails the build: "verified" T4 requires this
# live gate green, never inference.
if [ "$t4" != "success" ]; then
if [ "$t4" = "skipped" ] && [ "$is_fork_pr" = "true" ]; then
echo "::notice::t4-conformance skipped on fork PR — allowing aggregate to pass."
else
echo "::error::t4-conformance did not succeed: $t4 — T4 host-root reach / codex MCP token-ownership not verified on a live container. Failing closed (RFC internal#456 §11)."
exit 1
fi
fi
if [ "$tests" != "success" ]; then
echo "::error::Adapter unit tests did not succeed: $tests"
exit 1
fi
echo "::notice::Template validation aggregate passed (static=$static, runtime=$runtime, t4=$t4, tests=$tests)"
tests:
name: Adapter unit tests
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Skip executable tests for external forks
if: github.event.pull_request.head.repo.fork == true
run: echo "::notice::dependency installation and repository tests are disabled for external forks"
- run: pip install -q pytest pytest-asyncio pyyaml packaging
if: github.event.pull_request.head.repo.fork != true
# The adapter credential-preflight tests (test_modernization_pr1.py)
# import molecule_runtime.adapters.base via the _adapter fixture.
- run: pip install -q --extra-index-url "$MOLECULE_PRIVATE_INDEX" molecules-workspace-runtime
if: github.event.pull_request.head.repo.fork != true
# ADR-004 adapter-socket conformance (tests/test_conformance.py) inherits
# the SDK-owned battery molecule_plugin.adapter_conformance.AdapterConformance.
# Install the reviewed SDK source commit directly so conformance cannot
# silently drift when either the package registry or SDK main advances.
- run: pip install -q "molecule-ai-sdk @ git+https://git.moleculesai.app/molecule-ai/molecule-ai-sdk.git@da42c7f2dae122aaa6f34a74c13e598a87870586"
if: github.event.pull_request.head.repo.fork != true
- run: python3 -m pytest tests/ -v
if: github.event.pull_request.head.repo.fork != true