Files
molecule-ai-workspace-templ…/.gitea/workflows/ci.yml
T
documentation-specialist 2e79a72f3c
sync-providers-yaml / Compare synced providers.yaml against controlplane canonical (pull_request) Failing after 10s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 10s
CI / Template validation (static) (pull_request) Successful in 20s
CI / Template validation (static) (push) Successful in 21s
verify-providers-projection / Regenerate projection, fail on drift, assert registry ⊆ template (pull_request) Successful in 22s
CI / Shell unit tests (pull_request) Successful in 20s
CI / Shell unit tests (push) Successful in 20s
CI / Adapter-socket conformance (pull_request) Successful in 51s
CI / Adapter-socket conformance (push) Successful in 51s
CI / Template validation (runtime) (push) Successful in 5m22s
CI / T4 tier-4 conformance (live) (push) Successful in 5m23s
CI / T4 tier-4 conformance (live) (pull_request) Successful in 5m24s
CI / all-required (push) Successful in 8s
CI / validate (push) Successful in 10s
CI / Template validation (runtime) (pull_request) Successful in 5m32s
CI / validate (pull_request) Successful in 7s
CI / all-required (pull_request) Successful in 8s
docs: correct runtime publish source comments
2026-07-14 07:32:34 -07:00

654 lines
35 KiB
YAML

name: CI
# Repository-local validation pipeline. It clones the canonical validator
# from molecule-ci on each run, while keeping this repository's protected
# context names and runner requirements explicit.
#
# Four-surface migration audit (feedback_gitea_actions_migration_audit_pattern):
# 1. YAML — no `workflow_dispatch.inputs`; no `merge_group`; preserved
# `on: [push, pull_request]` from the original. Added workflow-level
# env.GITHUB_SERVER_URL (feedback_act_runner_github_server_url).
# 2. Cache — `actions/setup-python` `cache: pip` preserved; works against
# Gitea's built-in cache server when runner.cache is configured.
# 3. Token — uses auto-injected GITHUB_TOKEN (Gitea-aliased). Validator
# job needs only `contents: read` (no write to issues/PRs).
# 4. Docs — anonymous git-clone of molecule-ci (no token in URL); the
# molecule-ci repo is public on the Gitea instance.
#
# Avoid job-level `if:` guards on protected contexts: skipped required jobs
# can remain pending in the branch-protection view. Runtime validation runs
# after static validation via `needs:` so protected contexts complete.
#
# Cross-links:
# - internal#326 — parent tracking issue
# - molecule-ai/molecule-ci/.gitea/workflows/validate-workspace-template.yml — pattern source
# - molecule-ai/molecule-core/.gitea/workflows/ci.yml — Gitea port style reference
on: [push, pull_request]
# Defense-in-depth de-dup ONLY (the t4-conformance unique-name fix 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:
# Belt-and-suspenders against the runner-default trap
# (feedback_act_runner_github_server_url). Runners are configured
# with this env via /opt/molecule/runners/config.yaml runner.envs,
# but pinning at the workflow level protects against a runner
# regenerated without the config file.
GITHUB_SERVER_URL: https://git.moleculesai.app
# 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 the GITHUB_TOKEN scope. The validate-runtime job
# runs untrusted-by-design code from the calling repo — pip-installs
# requirements.txt (post-install hooks), imports adapter.py, and
# docker-builds the Dockerfile. Each primitive can execute arbitrary
# code with the token in env. Pinning `contents: read` means the worst
# a malicious template PR can do with the token is read public repo
# state — no write to issues, no push to branches, no comment-spam.
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
# Canonical validator script lives in molecule-ci, fetched fresh on
# every run. Anonymous fetch of the public molecule-ci repo — no
# token needed; no actions/checkout cross-repo idiosyncrasies.
- name: Fetch molecule-ci canonical scripts
run: git clone --depth 1 https://git.moleculesai.app/molecule-ai/molecule-ci.git .molecule-ci-canonical
- uses: actions/setup-python@v5
with:
python-version: "3.11"
# Secret scan — the 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'''["']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'}
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
# Static-only validator — file existence checks, YAML parse,
# AST inspection of adapter.py (no import). Doesn't execute any
# third-party code; safe on fork PRs.
- run: pip install pyyaml jsonschema 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
validate-runtime:
name: Template validation (runtime)
# internal#512 follow-on (class defect): pin to `docker-host` so this
# job (which `docker build`s the template image) lands on Linux
# docker-host runners. The bare `ubuntu-latest` label is also
# advertised by hongming-pc-runner-* (Windows act_runner v1.0.3),
# where `docker build` fails non-deterministically. Mirror of the
# codex sibling fix (template-codex#9) and internal#512.
runs-on: docker-host
timeout-minutes: 15
needs: validate-static
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Fetch molecule-ci canonical scripts
run: git clone --depth 1 https://git.moleculesai.app/molecule-ai/molecule-ci.git .molecule-ci-canonical
- uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: "pip"
cache-dependency-path: requirements.txt
- run: pip install pyyaml jsonschema -q
# Install the template's runtime dependencies so the validator's
# check_adapter_runtime_load() can import adapter.py the same way
# the workspace container does at boot. Without this, a
# syntactically-valid adapter that ImportErrors on a missing
# transitive dep would build clean and crash on first user prompt.
- 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: |
# Graceful skip when the runner's job-container can't reach the
# Docker daemon (e.g. /var/run/docker.sock not mounted into the
# act job container, or the in-container uid not in the docker
# group). Without this guard, CI stays red even when the
# template's Dockerfile is fine — see internal#222 for the
# proper runner-config fix.
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
docker build -t template-test . --no-cache 2>&1 | tail -5 && echo "Docker build succeeded"
# --- 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 REAL start.sh boot path execs molecule-runtime as uid 1000
# — the property that determines /configs/.auth_token ownership
# in SaaS/tenant mode, where the runtime (not the platform)
# writes the token via save_token(). Asserted by running the
# real entrypoint (smoke-mode) with a PATH shim that records the
# terminal-exec uid. NOT a self-chown tautology — fails closed if
# the `exec gosu agent` (PR#24 b682444) ever regresses to root.
# The flags are not hard-coded blind: they are the documented
# provisioner contract; drift is caught because the controlplane
# string-match unit test (userdata_t4_privileged_test.go) guards the
# emission side and this gate guards the runtime side. Mirrors the
# already-live-verified claude-code template gate (12dd604) verbatim.
t4-conformance:
name: T4 tier-4 conformance (live)
# internal#512 follow-on (class defect): pin to `docker-host` so the
# T4 live probe (`docker build` + `docker run -d` + privileged
# `docker exec ... sudo -n nsenter --target 1 ...`) lands on the
# Linux docker-host runners. The bare `ubuntu-latest` label is
# also advertised by hongming-pc-runner-* (Windows act_runner v1.0.3)
# where the privileged docker exec chain cannot work. Mirror of
# template-codex#9 and internal#512.
runs-on: docker-host
timeout-minutes: 15
needs: validate-static
# Runs after static validation. Avoid job-level if: guards here so the
# protected context always reaches a terminal state.
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Build the runtime image
id: build
run: |
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 + container name. 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
# `--name t4probe` / `-t t4-conformance-test` 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
# uid probe, and the same host-root 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).
T4_TAG="t4-conformance-test:${GITHUB_RUN_ID:-local}-${GITHUB_RUN_ATTEMPT:-1}"
docker build -t "$T4_TAG" . --no-cache 2>&1 | tail -5
- name: Run under EXACT tier-4 provisioner flags + assert host-root reach AND token agent-ownership
run: |
set -euo pipefail
# EXACT flags from controlplane userdata_containerized.go
# (tier-4 emission @ec2384c). The Hermes start.sh entrypoint
# boots the hermes gateway + molecule-runtime and wants live
# creds; we only need the container up long enough to probe,
# so override the entrypoint with a sleep (same approach as
# the claude-code gate) and exercise the agent context directly.
# Per-run-unique container name (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. T4_PROBE is unique per run-id/attempt.
# Recompute the SAME deterministic tag the build step used
# (pure function of run-id/attempt — no cross-step env reliance).
T4_TAG="t4-conformance-test:${GITHUB_RUN_ID:-local}-${GITHUB_RUN_ATTEMPT:-1}"
T4_PROBE="t4probe-${GITHUB_RUN_ID:-local}-${GITHUB_RUN_ATTEMPT:-1}"
CAPLESS_PROBE="capless-liveness-${GITHUB_RUN_ID:-local}-${GITHUB_RUN_ATTEMPT:-1}"
echo "=== capability-independent child liveness under the local workspace profile ==="
docker rm -f "$CAPLESS_PROBE" >/dev/null 2>&1 || true
docker run --rm \
--name "$CAPLESS_PROBE" \
--cap-drop ALL \
--cap-add CHOWN --cap-add DAC_OVERRIDE --cap-add FOWNER \
--cap-add SETGID --cap-add SETUID \
--security-opt no-new-privileges \
--pids-limit 512 \
--entrypoint /bin/bash \
"$T4_TAG" -c '
set -euo pipefail
. /app/scripts/process-liveness.sh
gosu agent timeout 2s sleep 30 &
child_pid=$!
sleep 0.2
process_is_running "$child_pid" || {
echo "capability-independent liveness falsely reported the uid-1000 child dead" >&2
exit 1
}
set +e
wait "$child_pid"
child_rc=$?
set -e
[ "$child_rc" -eq 124 ] || {
echo "unexpected bounded child exit: $child_rc" >&2
exit 1
}
if process_is_running "$child_pid"; then
echo "capability-independent liveness reported the exited child alive" >&2
exit 1
fi
'
# 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.
docker rm -f "$T4_PROBE" >/dev/null 2>&1 || true
CID=$(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')
trap 'docker rm -f "$T4_PROBE" >/dev/null 2>&1 || true; docker rmi -f "$T4_TAG" >/dev/null 2>&1 || true' EXIT
echo "=== (b) token agent-ownership: assert the REAL start.sh boot path execs molecule-runtime as uid 1000 ==="
# ROOT-CAUSE-ACCURATE assertion (RFC internal#456 §10, Hermes
# list_peers-401 class). In SaaS/tenant mode (prod Hermes,
# ws-tenant-*) the platform does NOT pre-seed /configs/.auth_token
# — workspace_provision.go issueAndInjectToken() returns early in
# saasMode(), there is no #1877 volume pre-write and no #418 tar
# re-injection. The token is delivered in the /registry/register
# response body and persisted IN-CONTAINER by the runtime itself
# via platform_auth.save_token() (main.py:364), which does
# os.open(.auth_token, O_CREAT, 0o600) — the file lands owned by
# WHATEVER UID molecule-runtime runs as. The pinned PR#23 image
# (git 66b7565) ran `exec molecule-runtime` as ROOT → token
# root:root 600 → uid-1000 a2a_mcp_server EACCES → empty bearer →
# HTTP 401 → list_peers "No peers found". PR#24 (b682444) fixed it
# by changing the terminal exec to `exec gosu agent ...
# molecule-runtime` (uid 1000 → token agent-owned), exactly the
# claude-code proven pattern.
#
# The previous version of this step `touch`ed + `chown`ed the
# token ITSELF and then stat'd it — a tautology that proved
# nothing about the boot path and would stay GREEN if the gosu
# exec ever regressed back to root. Instead, run the REAL
# entrypoint (smoke-mode short-circuits the gateway/creds) with a
# shim on PATH in place of molecule-runtime that records the uid
# the terminal `exec` actually confers, then assert it is 1000.
# This fails CLOSED on any future revert of the `gosu agent` exec
# — the property that genuinely prevents the 401.
# Build the probe script on the host (no nested-quote fragility),
# copy it in, run it. The shim replaces `molecule-runtime` on
# PATH; start.sh's terminal `exec gosu agent env HOME=/tmp
# CONFIGS_DIR=/configs molecule-runtime` resolves to it (the
# `env` form preserves PATH — it is not `env -i`), so the shim
# records the uid the real terminal exec confers. Smoke mode
# makes start.sh take its line-23 short-circuit (same `exec gosu
# agent ... molecule-runtime` codepath, no gateway/creds needed).
cat > t4_token_uid_probe.sh <<'PROBE'
#!/bin/sh
set -e
mkdir -p /shim
printf '%s\n' '#!/bin/sh' 'id -u > /shim/runtime_uid' > /shim/molecule-runtime
chmod +x /shim/molecule-runtime
rm -f /shim/runtime_uid
chown -R agent:agent /shim
MOLECULE_SMOKE_MODE=1 PATH="/shim:$PATH" /usr/local/bin/start.sh > /shim/start.log 2>&1 || true
PROBE
docker cp t4_token_uid_probe.sh "$T4_PROBE":/shim_probe.sh
docker exec "$T4_PROBE" sh /shim_probe.sh
RUNTIME_UID=$(docker exec "$T4_PROBE" cat /shim/runtime_uid 2>/dev/null || echo MISSING)
echo "molecule-runtime terminal-exec uid = $RUNTIME_UID"
if [ "$RUNTIME_UID" != "1000" ]; then
echo "::error::T4 contract violated: start.sh execs molecule-runtime as uid=$RUNTIME_UID (expected 1000). In SaaS mode platform_auth.save_token() then writes /configs/.auth_token as that uid; uid!=1000 re-ships the Hermes list_peers-401 class (RFC internal#456 §10). The 'exec gosu agent ... molecule-runtime' (PR#24 b682444) must NOT regress."
docker exec "$T4_PROBE" sh -c 'tail -20 /shim/start.log' || true
exit 1
fi
echo "=== (a) host-root reach AS THE uid-1000 AGENT (not root) ==="
# Run as the agent user (uid 1000), exactly as gosu would.
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-filesystem write+readback through /host
# from the agent, proving real host reach (not just a namespace
# trick on an isolated PID 1). 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 start.sh execs molecule-runtime as uid 1000 (so SaaS-mode save_token() writes /configs/.auth_token agent-owned; list_peers-401 class closed), both atomically."
# Aggregator that emits a single `validate` check name — matches the
# historical required-check name on this repo's branch protection.
validate:
name: validate
runs-on: ubuntu-latest
needs: [validate-static, validate-runtime, t4-conformance, conformance, shell-tests]
timeout-minutes: 1
steps:
- name: Aggregate
run: |
static="${{ needs.validate-static.result }}"
runtime="${{ needs.validate-runtime.result }}"
t4="${{ needs.t4-conformance.result }}"
conformance="${{ needs.conformance.result }}"
shell="${{ needs.shell-tests.result }}"
echo "validate-static: $static"
echo "validate-runtime: $runtime"
echo "t4-conformance: $t4"
echo "conformance: $conformance"
echo "shell-tests: $shell"
if [ "$static" != "success" ]; then
echo "::error::validate-static did not succeed: $static"
exit 1
fi
# Preserve compatibility with older runs that skipped runtime validation.
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.
# Any other non-success fails the build: "verified" T4 requires
# this live gate green, never inference.
is_fork_pr="${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true }}"
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 / token-ownership not verified on a live container. Failing closed (RFC internal#456 §11)."
exit 1
fi
fi
# ADR-004 §4 adapter-socket conformance is a HARD gate: the SDK-owned
# AdapterConformance battery (tests/test_conformance.py) must pass on
# every push/PR so a hermes-only socket regression can't ship green
# (audit finding CI2). No fork carve-out — it runs no untrusted code.
if [ "$conformance" != "success" ]; then
echo "::error::conformance did not succeed: $conformance — ADR-004 adapter-socket conformance not green (audit CI2)."
exit 1
fi
if [ "$shell" != "success" ] && [ "$shell" != "skipped" ]; then
echo "::error::shell-tests did not succeed: $shell"
exit 1
fi
echo "::notice::Template validation aggregate passed (static=$static, runtime=$runtime, t4=$t4, conformance=$conformance, shell=$shell)"
# New single-sentinel context for branch protection (internal#349).
# Mirrors the existing `validate` aggregate but posts under the standardized
# `all-required` name so the org-wide hard-gate can require one context per
# repo without enumerating every individual CI job.
all-required:
name: all-required
runs-on: ubuntu-latest
needs: [validate-static, validate-runtime, t4-conformance, conformance, shell-tests]
if: always()
timeout-minutes: 1
steps:
- name: Verify all required jobs passed
run: |
static="${{ needs.validate-static.result }}"
runtime="${{ needs.validate-runtime.result }}"
t4="${{ needs.t4-conformance.result }}"
conformance="${{ needs.conformance.result }}"
shell="${{ needs.shell-tests.result }}"
echo "validate-static: $static"
echo "validate-runtime: $runtime"
echo "t4-conformance: $t4"
echo "conformance: $conformance"
echo "shell-tests: $shell"
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.
# Any other non-success fails the build: "verified" T4 requires
# this live gate green, never inference.
is_fork_pr="${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true }}"
if [ "$t4" != "success" ]; then
if [ "$t4" = "skipped" ] && [ "$is_fork_pr" = "true" ]; then
echo "::notice::t4-conformance skipped on fork PR — allowing all-required to pass."
else
echo "::error::t4-conformance did not succeed: $t4 — T4 host-root reach / token-ownership not verified on a live container. Failing closed (RFC internal#456 §11)."
exit 1
fi
fi
# ADR-004 §4 adapter-socket conformance — HARD gate (audit CI2). No
# fork carve-out: it executes no untrusted code, so it runs the same
# on fork PRs, internal PRs and main pushes.
if [ "$conformance" != "success" ]; then
echo "::error::conformance did not succeed: $conformance — ADR-004 adapter-socket conformance not green (audit CI2)."
exit 1
fi
if [ "$shell" != "success" ] && [ "$shell" != "skipped" ]; then
echo "::error::shell-tests did not succeed: $shell"
exit 1
fi
echo "::notice::All required CI jobs passed (static=$static, runtime=$runtime, t4=$t4, conformance=$conformance, shell=$shell)"
shell-tests:
# Shell-level unit tests for scripts/*.sh. Keep every helper test wired
# into CI; the suite completes in under five seconds on a fresh runner.
name: Shell unit tests
runs-on: ubuntu-latest
timeout-minutes: 3
needs: validate-static
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-python@v5
with:
python-version: "3.11"
# PyYAML is required by load-workspace-config.sh's python helper.
# In production it's transitive via molecule-ai-workspace-runtime;
# in this minimal CI env we install it explicitly so the YAML path
# is exercised instead of the script's silent ImportError fallback.
- run: pip install -q pyyaml
- run: bash scripts/test-derive-provider.sh
- run: bash scripts/test-derive-platform-llm.sh
- run: bash scripts/test-install-prefix-strip.sh
- run: bash scripts/test-load-workspace-config.sh
- run: bash scripts/test-default-model-selection.sh
- run: bash scripts/test-mcp-configs-dir.sh
- run: bash scripts/test-process-liveness.sh
- run: bash scripts/test-publish-local-runtime-cache.sh
# --- ADR-004 §4 adapter-socket conformance (audit finding CI2) ---
# This repo SHIPS tests/test_conformance.py — a ~5-line subclass of the
# SDK-owned battery molecule_plugin.adapter_conformance.AdapterConformance
# pointed at this repo's Adapter — but until now NO CI job installed the SDK
# or ran pytest, so a hermes-only adapter-socket regression (path/render/
# present/enumerate/persona seam) shipped GREEN. The other three templates
# (codex/openclaw/claude-code) already run their conformance suite; this job
# closes the hermes gap and is wired into BOTH required-check aggregates
# (`validate` + `all-required`) so branch protection actually enforces it.
#
# SCOPE: this job runs adapter conformance plus the narrow release/runtime
# supply-chain contracts, not the whole tests/ dir. The adapter-socket suite
# (16 cases) is the CI2 target and is green. tests/test_executor_plugin_path.py
# is intentionally NOT gated here — it is unrelated to this contract and
# currently fails independently (a2a extract_message_text / MagicMock-context
# version sensitivity). The remaining adapter-adjacent suites can be folded
# in after that separate debt is fixed.
conformance:
name: Adapter-socket conformance
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-python@v5
with:
python-version: "3.11"
# The conformance test imports the hermes Adapter (adapter.py), which
# imports molecule_runtime.adapters.base, so the runtime dist must be
# present. Install it exactly like validate-runtime does: explicit
# --extra-index-url for the private Gitea dist (hermetic dep policy —
# no global PIP_INDEX_URL; see the env: block), never routing public
# tooling at the private index.
- if: hashFiles('requirements.txt') != ''
run: pip install -q --extra-index-url "$MOLECULE_PRIVATE_INDEX" -r requirements.txt
# requirements-dev.txt pins pytest + pytest-asyncio and the molecule-ai-sdk
# (molecule_plugin) that ships the AdapterConformance battery that
# tests/test_conformance.py inherits. The SDK is pinned to a reviewed
# immutable commit there, so
# the private-index --extra-index-url is applied for its own private
# transitive deps.
- run: pip install -q --extra-index-url "$MOLECULE_PRIVATE_INDEX" -r requirements-dev.txt
- name: Run adapter and private-runtime contracts
run: >-
python3 -m pytest
tests/test_conformance.py
tests/test_release_contracts.py
tests/test_prepare_runtime_requirements.py
tests/test_plugin_installer_cutover.py
-v