meta-ci-advisory / meta (advisory) (push) Skipped
publish-image / Resolve runtime version (push) Skipped
publish-image / Classify change (decouple publish from promote) (push) Skipped
publish-image / Build & push workspace-template-hermes image (push) Skipped
publish-image / Promote runtime_image_pins (CP admin) (staging-api.moleculesai.app, staging) (push) Skipped
publish-image / Verify runtime_image_pins moved (fail-loud) (staging-api.moleculesai.app, staging) (push) Skipped
Secret scan / Scan diff for credential-shaped strings (push) Skipped
sync-providers-yaml / Compare synced providers.yaml against controlplane canonical (push) Skipped
verify-providers-projection / Regenerate projection, fail on drift, assert registry ⊆ template (push) Skipped
CI / Template validation (static) (push) Successful in 15s
sync-providers-yaml / Compare synced providers.yaml against controlplane canonical (pull_request) Skipped
CI / Template validation (static) (pull_request) Successful in 14s
CI / Adapter-socket conformance (push) Successful in 43s
meta-ci-advisory / meta (advisory) (pull_request) Successful in 4s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 3s
CI / Shell unit tests (push) Successful in 33s
verify-providers-projection / Regenerate projection, fail on drift, assert registry ⊆ template (pull_request) Successful in 15s
CI / Adapter-socket conformance (pull_request) Successful in 32s
CI / Gateway live round-trip (pinned hermes) (push) Successful in 1m19s
CI / Shell unit tests (pull_request) Successful in 39s
CI / Gateway live round-trip (pinned hermes) (pull_request) Successful in 2m7s
CI / Template validation (runtime) (pull_request) Successful in 4m23s
CI / Template validation (runtime) (push) Successful in 5m4s
CI / T4 tier-4 conformance (live) (pull_request) Successful in 4m17s
CI / T4 tier-4 conformance (live) (push) Successful in 5m10s
CI / validate (push) Successful in 1s
CI / all-required (push) Successful in 1s
CI / validate (pull_request) Successful in 1s
CI / all-required (pull_request) Successful in 1s
The runtime decides whether a turn is still working from the mtime of MOLECULE_TOOL_ACTIVITY_FILE (molecule_runtime.turn_lease, feeder C). turn_liveness_scope exports that variable per turn, inside the runtime process. hermes runs the tools in its gateway, which start.sh launches once at boot, so the gateway never had the variable and nothing wrote the file. GET /turn-liveness reported idle since boot while hermes worked, and the scheduler watchdog cancelled and re-queued every turn longer than its 30 s poll (dinecall-ai 2026-10-09: one 4-minute tick, 9 re-queues, 5 duplicate deliveries). start.sh now fixes one path at boot and exports it to both processes. It also installs a template hermes plugin, molecule_turn_activity, whose post_tool_call hook touches the file after every tool call. - Path: /tmp/molecule-turn-lease/tool_activity, in a dedicated 0700 agent-owned dir because the runtime chmods the file's parent to 0700. An already-set MOLECULE_TOOL_ACTIVITY_FILE wins. The runtime honours an explicit path in both kernel modes (executor_helpers.tool_activity_file), so no runtime change is needed. - Hook: post_tool_call is a bounded, fail-open observer. pre_tool_call is not used; it is a fail-closed policy hook, so a slow or raising callback there would block the tool. - The plugin is listed in plugins.enabled on both transports, and is installed agent-owned into $HERMES_HOME/plugins on every boot from /app/hermes_plugins. Not chosen: having the executor touch the lease while it awaits the plugin reply. That signals "a turn is in flight", not tool activity, so a wedged turn would read alive until the absolute cap. It would also defeat arm_turn_if_fed's evidence check; turn_lease_executor deliberately does not count reply events as activity. The CI round trip (gateway-live-roundtrip) now drives one real tool call through the pinned gateway with the plugin installed. It asserts that the activity file advanced and that the runtime's own lease (wrap_executor + turn_liveness_scope) observed the activity. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1140 lines
61 KiB
YAML
1140 lines
61 KiB
YAML
name: CI
|
|
|
|
# Repository-local validation pipeline. It fetches the canonical validator
|
|
# from molecule-ci at a reviewed immutable commit 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 exact-SHA fetch of public molecule-ci with credential
|
|
# helpers disabled.
|
|
#
|
|
# 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
|
|
# 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 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
|
|
with:
|
|
persist-credentials: false
|
|
# Canonical validator script lives in molecule-ci. Fetch the reviewed
|
|
# commit anonymously without consulting runner credential helpers.
|
|
- 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
|
|
- 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
|
|
|
|
# A 40-char git object id is not a credential. The generic
|
|
# ["'][a-zA-Z0-9/+=]{40}["'] rule above cannot tell a base64-ish
|
|
# 40-char blob from a SHA-1, and this repo pins upstream commits by
|
|
# SHA on purpose (ARG HERMES_COMMIT, ARG HERMES_PLATFORM_MOLECULE_A2A_REF,
|
|
# and the anchors in tests/ that must match them). Exempting at the
|
|
# MATCH level rather than reaching for the line-level `...` escape is
|
|
# deliberate: `...` disables every pattern for the whole line, so a
|
|
# real key sharing a line with a SHA would sail through.
|
|
#
|
|
# Narrow on purpose — exactly 40 chars, lowercase hex only. None of
|
|
# the credential formats above live in that alphabet: a base64 token
|
|
# of length 40 that happens to be all-lowercase-hex has ~1e-9 odds,
|
|
# and every named format (sk-ant-, ghp_, AKIA, sk_test_, Bearer) is
|
|
# matched by its own prefix rule regardless.
|
|
GIT_SHA1 = re.compile(r'\A["\'][0-9a-f]{40}["\']\Z')
|
|
|
|
def is_git_sha(matched_text):
|
|
return bool(GIT_SHA1.match(matched_text))
|
|
|
|
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 is_false_positive(line):
|
|
continue
|
|
if is_git_sha(match.group(0)):
|
|
continue
|
|
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
|
|
# 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)"
|
|
|
|
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
|
|
with:
|
|
persist-credentials: false
|
|
- name: Skip executable validation for external forks
|
|
if: github.event.pull_request.head.repo.fork == true
|
|
run: echo "::notice::dependency installation, adapter import, and Docker execution are disabled for external forks"
|
|
- name: Fetch immutable molecule-ci canonical scripts
|
|
if: github.event.pull_request.head.repo.fork != true
|
|
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
|
|
- uses: actions/setup-python@v5
|
|
with:
|
|
python-version: "3.11"
|
|
cache: "pip"
|
|
cache-dependency-path: requirements.txt
|
|
- if: github.event.pull_request.head.repo.fork != true
|
|
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: github.event.pull_request.head.repo.fork != true && hashFiles('requirements.txt') != ''
|
|
run: pip install -q --extra-index-url "$MOLECULE_PRIVATE_INDEX" -r requirements.txt
|
|
- if: github.event.pull_request.head.repo.fork != true && hashFiles('requirements.txt') == ''
|
|
run: pip install -q --extra-index-url "$MOLECULE_PRIVATE_INDEX" molecules-workspace-runtime
|
|
- if: github.event.pull_request.head.repo.fork != true
|
|
run: python3 .molecule-ci-canonical/scripts/validate-workspace-template.py
|
|
- name: Docker build smoke test
|
|
if: github.event.pull_request.head.repo.fork != true && hashFiles('Dockerfile') != ''
|
|
run: |
|
|
set -euo pipefail
|
|
# 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
|
|
if [ ! -s .runtime-version ]; then
|
|
echo "::error::.runtime-version is missing or empty"
|
|
exit 1
|
|
fi
|
|
if [ -z "${GITHUB_RUN_ID:-}" ] || [ -z "${GITHUB_RUN_ATTEMPT:-}" ]; then
|
|
echo "::error::GITHUB_RUN_ID and GITHUB_RUN_ATTEMPT are required for an isolated Docker tag"
|
|
exit 1
|
|
fi
|
|
EXPECTED_RUNTIME_VERSION="$(tr -d '[:space:]' < .runtime-version)"
|
|
SMOKE_TAG="molecule-ai-workspace-hermes-smoke-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
|
|
trap 'docker image rm -f "$SMOKE_TAG" >/dev/null 2>&1 || true' EXIT
|
|
# 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 #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 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
|
|
env:
|
|
MOLECULE_CI_REF: 11b8598e5c0b3f0b1031733a8d5f6bc238f146a4
|
|
# 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
|
|
with:
|
|
persist-credentials: false
|
|
- name: Skip privileged conformance for external forks
|
|
if: github.event.pull_request.head.repo.fork == true
|
|
run: echo "::notice::privileged T4 validation is disabled for external forks"
|
|
- name: Fetch immutable molecule-ci MCP proof tools
|
|
if: github.event.pull_request.head.repo.fork != true
|
|
run: |
|
|
set -euo pipefail
|
|
: "${RUNNER_TEMP:?RUNNER_TEMP is required}"
|
|
T4_SUFFIX="${GITHUB_RUN_ID:-local}-${GITHUB_RUN_ATTEMPT:-1}"
|
|
CI_ROOT="$RUNNER_TEMP/molecule-ci-mcp-e2e-${T4_SUFFIX}"
|
|
ATTESTATION="$RUNNER_TEMP/mcp-pin-attestation-${T4_SUFFIX}.json"
|
|
ATTESTATION_TMP="${ATTESTATION}.tmp"
|
|
MCP_ATTESTATION_SHA256="${ATTESTATION}.sha256"
|
|
VERIFIER="$CI_ROOT/scripts/mcp_built_image_e2e.py"
|
|
mkdir "$CI_ROOT"
|
|
KEEP_MCP_PROOF=0
|
|
cleanup_mcp_proof_fetch() {
|
|
if [ "$KEEP_MCP_PROOF" -ne 1 ]; then
|
|
rm -rf -- "$CI_ROOT"
|
|
rm -f -- "$ATTESTATION" "$ATTESTATION_TMP" "$MCP_ATTESTATION_SHA256"
|
|
fi
|
|
}
|
|
trap cleanup_mcp_proof_fetch EXIT
|
|
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 -q --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"
|
|
test -f "$CI_ROOT/scripts/mcp_pin_lockstep.py"
|
|
test ! -L "$CI_ROOT/scripts/mcp_pin_lockstep.py"
|
|
test -f "$VERIFIER"
|
|
test ! -L "$VERIFIER"
|
|
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 \
|
|
> "$ATTESTATION_TMP"
|
|
test -s "$ATTESTATION_TMP"
|
|
mv "$ATTESTATION_TMP" "$ATTESTATION"
|
|
sha256sum "$ATTESTATION" > "$MCP_ATTESTATION_SHA256"
|
|
KEEP_MCP_PROOF=1
|
|
- name: Build the runtime image
|
|
id: build
|
|
if: github.event.pull_request.head.repo.fork != true
|
|
run: |
|
|
set -euo pipefail
|
|
: "${RUNNER_TEMP:?RUNNER_TEMP is required}"
|
|
T4_SUFFIX="${GITHUB_RUN_ID:-local}-${GITHUB_RUN_ATTEMPT:-1}"
|
|
CI_ROOT="$RUNNER_TEMP/molecule-ci-mcp-e2e-${T4_SUFFIX}"
|
|
ATTESTATION="$RUNNER_TEMP/mcp-pin-attestation-${T4_SUFFIX}.json"
|
|
MCP_ATTESTATION_SHA256="${ATTESTATION}.sha256"
|
|
VERIFIER="$CI_ROOT/scripts/mcp_built_image_e2e.py"
|
|
T4_TAG="t4-conformance-test:${GITHUB_RUN_ID:-local}-${GITHUB_RUN_ATTEMPT:-1}"
|
|
KEEP_T4_IMAGE=0
|
|
cleanup_t4_build() {
|
|
if [ "$KEEP_T4_IMAGE" -ne 1 ]; then
|
|
docker image rm -f "$T4_TAG" >/dev/null 2>&1 || true
|
|
rm -rf -- "$CI_ROOT"
|
|
rm -f -- "$ATTESTATION" "$MCP_ATTESTATION_SHA256"
|
|
fi
|
|
}
|
|
trap cleanup_t4_build EXIT
|
|
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).
|
|
if [ ! -s "$ATTESTATION" ]; then
|
|
echo "::error::immutable MCP attestation is missing or empty"
|
|
exit 1
|
|
fi
|
|
test -f "$VERIFIER"
|
|
test ! -L "$VERIFIER"
|
|
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" "$ATTESTATION" <<'PY'
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(sys.argv[1]) / "scripts"))
|
|
from mcp_built_image_e2e import load_attestation
|
|
|
|
with Path(sys.argv[2]).open("rb") as stream:
|
|
sys.stdout.write(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 #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 attested runtime $EXPECTED_RUNTIME_VERSION"
|
|
exit 1
|
|
fi
|
|
KEEP_T4_IMAGE=1
|
|
- name: Verify management MCP in the final image
|
|
if: github.event.pull_request.head.repo.fork != true
|
|
run: |
|
|
set -euo pipefail
|
|
: "${RUNNER_TEMP:?RUNNER_TEMP is required}"
|
|
T4_SUFFIX="${GITHUB_RUN_ID:-local}-${GITHUB_RUN_ATTEMPT:-1}"
|
|
CI_ROOT="$RUNNER_TEMP/molecule-ci-mcp-e2e-${T4_SUFFIX}"
|
|
ATTESTATION="$RUNNER_TEMP/mcp-pin-attestation-${T4_SUFFIX}.json"
|
|
MCP_ATTESTATION_SHA256="${ATTESTATION}.sha256"
|
|
VERIFIER="$CI_ROOT/scripts/mcp_built_image_e2e.py"
|
|
MCP_E2E_LOG="$RUNNER_TEMP/mcp-built-image-e2e-${T4_SUFFIX}.log"
|
|
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}"
|
|
KEEP_T4_IMAGE=0
|
|
cleanup_mcp_e2e() {
|
|
docker rm -f "$MCP_VERIFY_CONTAINER" >/dev/null 2>&1 || true
|
|
rm -rf -- "$CI_ROOT"
|
|
rm -f -- "$ATTESTATION" "$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_mcp_e2e EXIT
|
|
test -s "$ATTESTATION"
|
|
test -f "$VERIFIER"
|
|
test ! -L "$VERIFIER"
|
|
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 \
|
|
--env MOLECULE_PREBAKE_NODE_BIN=/home/agent/.hermes/node/bin \
|
|
--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 "$VERIFIER" \
|
|
"$MCP_VERIFY_CONTAINER:/mcp_built_image_e2e.py"
|
|
sha256sum --check "$MCP_ATTESTATION_SHA256"
|
|
docker start --attach --interactive "$MCP_VERIFY_CONTAINER" \
|
|
< "$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 token agent-ownership
|
|
if: github.event.pull_request.head.repo.fork != true
|
|
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}"
|
|
trap 'docker rm -f "$CAPLESS_PROBE" "$T4_PROBE" >/dev/null 2>&1 || true; docker rmi -f "$T4_TAG" >/dev/null 2>&1 || true' EXIT
|
|
|
|
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
|
|
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 "=== (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, gateway-live-roundtrip]
|
|
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 }}"
|
|
roundtrip="${{ needs.gateway-live-roundtrip.result }}"
|
|
echo "validate-static: $static"
|
|
echo "validate-runtime: $runtime"
|
|
echo "t4-conformance: $t4"
|
|
echo "conformance: $conformance"
|
|
echo "shell-tests: $shell"
|
|
echo "gateway-roundtrip:$roundtrip"
|
|
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
|
|
is_fork_pr="${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true }}"
|
|
# Gateway live round-trip — explicit hard gate here too (review
|
|
# wf_3a7b849d #13: this aggregate assigned $roundtrip but never
|
|
# checked it, so a future `if: always()` on validate would silently
|
|
# drop the gate from the historical required-check). `skipped` only
|
|
# on fork PRs (the job self-skips there, #12).
|
|
if [ "$roundtrip" != "success" ]; then
|
|
if [ "$roundtrip" = "skipped" ] && [ "$is_fork_pr" = "true" ]; then
|
|
echo "::notice::gateway-live-roundtrip skipped on fork PR — allowing validate to pass."
|
|
else
|
|
echo "::error::gateway-live-roundtrip did not succeed: $roundtrip"
|
|
exit 1
|
|
fi
|
|
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.
|
|
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, gateway-live-roundtrip]
|
|
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 }}"
|
|
roundtrip="${{ needs.gateway-live-roundtrip.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
|
|
# Gateway live round-trip — HARD gate (2026-07-23 hermes-0.19
|
|
# incident: four behavior changes shipped green because no job
|
|
# booted the real pinned gateway with the plugin and traffic).
|
|
# `skipped` is only acceptable on fork PRs (its steps no-op there).
|
|
if [ "$roundtrip" != "success" ]; then
|
|
if [ "$roundtrip" = "skipped" ] && [ "$is_fork_pr" = "true" ]; then
|
|
echo "::notice::gateway-live-roundtrip skipped on fork PR — allowing all-required to pass."
|
|
else
|
|
echo "::error::gateway-live-roundtrip did not succeed: $roundtrip — the pinned hermes gateway + a2a plugin round-trip is not verified."
|
|
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)"
|
|
|
|
gateway-live-roundtrip:
|
|
# The gate the 2026-07-23 hermes-0.19 incident demanded: boot the REAL
|
|
# pinned hermes gateway (installed at the Dockerfile's HERMES_COMMIT,
|
|
# so every upstream pin bump exercises the NEW ref pre-merge)
|
|
# with the REAL a2a plugin (at HERMES_PLATFORM_MOLECULE_A2A_REF) and
|
|
# run scripts/e2e_full_chain.py: executor -> /a2a/inbound -> gateway
|
|
# pipeline -> stub LLM -> plugin send() -> executor callback, TWICE
|
|
# (the second message guards the 0.19 pairing-policy active-session
|
|
# drop). Catches, per-PR, all four break classes that shipped green on
|
|
# 2026-07-22/23: plugins.enabled opt-in, adapter ABI drift
|
|
# (connect kwarg), authz drops, and — via the shell unit test in
|
|
# shell-tests — the eager-MCP reconcile mechanics. No docker needed:
|
|
# the gateway runs as a subprocess against a temp HERMES_HOME.
|
|
name: Gateway live round-trip (pinned hermes)
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 12
|
|
needs: validate-static
|
|
# review wf_3a7b849d #12: skip the WHOLE job on fork PRs so it reports
|
|
# result="skipped" (not "success" from no-op'd steps) to the aggregates —
|
|
# otherwise all-required's `roundtrip == skipped` fork carve-out is
|
|
# unreachable and the check log falsely claims a fork's pinned round-trip
|
|
# was verified. Runs on internal PRs (fork=false) and pushes (no PR object).
|
|
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork != true }}
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
persist-credentials: false
|
|
- name: Skip executable round-trip for external forks
|
|
if: github.event.pull_request.head.repo.fork == true
|
|
run: echo "::notice::gateway live round-trip is disabled for external forks"
|
|
- uses: actions/setup-python@v5
|
|
with:
|
|
python-version: "3.11"
|
|
- name: Install pinned hermes + a2a plugin + runtime deps
|
|
if: github.event.pull_request.head.repo.fork != true
|
|
run: |
|
|
set -euo pipefail
|
|
HERMES_COMMIT=$(sed -n 's/^ARG HERMES_COMMIT=//p' Dockerfile | head -1)
|
|
A2A_REF=$(sed -n 's/^ARG HERMES_PLATFORM_MOLECULE_A2A_REF=//p' Dockerfile | head -1)
|
|
# review wf_3a7b849d #6: pin the runtime to .runtime-version, the
|
|
# EXACT version the shipped image is built and asserted against
|
|
# (docker build --build-arg RUNTIME_VERSION + the importlib equality
|
|
# check). Installing it unpinned made this "pinned stack" gate resolve
|
|
# a DIFFERENT runtime than the deliverable — so a prepare-dependent
|
|
# break in the pinned 0.4.x could ship green, and an unrelated newer
|
|
# release could red every template PR.
|
|
RUNTIME_VERSION=$(tr -d '[:space:]' < .runtime-version)
|
|
test -n "$HERMES_COMMIT" && test -n "$A2A_REF" && test -n "$RUNTIME_VERSION"
|
|
echo "pinned hermes-agent@$HERMES_COMMIT a2a-plugin@$A2A_REF runtime==$RUNTIME_VERSION"
|
|
# Install from the SAME immutable git ref the image pins (2026-09-03).
|
|
# It used to be `hermes-agent==$HERMES_VERSION` from PyPI; that pin
|
|
# named the wheel in site-packages, which the shipped image does not
|
|
# import (~/.local/bin/hermes puts the checkout at sys.path[0]) — so
|
|
# this gate was conformance-testing code the fleet does not run.
|
|
#
|
|
# It must be an EDITABLE install from a checkout, not `pip install
|
|
# <git-ref>`: upstream's build backend refuses to build a wheel or
|
|
# sdist at all ("Building wheels or sdists for hermes-agent is not
|
|
# supported. Hermes is distributed via the shell installer, Docker
|
|
# image, or Nix"), so the package form fails outright. This mirrors
|
|
# what install.sh does in the image (`uv pip install -e .` from the
|
|
# pinned checkout), which is also why the image was never affected.
|
|
git clone -q --filter=blob:none --no-checkout \
|
|
https://github.com/NousResearch/hermes-agent.git /tmp/hermes-src
|
|
git -C /tmp/hermes-src checkout -q --detach "$HERMES_COMMIT"
|
|
# Same reasoning as the Dockerfile's assertion: verify the ref rather
|
|
# than trust the fetch, so this gate cannot silently test other code.
|
|
ACTUAL_COMMIT="$(git -C /tmp/hermes-src rev-parse HEAD)"
|
|
if [ "$ACTUAL_COMMIT" != "$HERMES_COMMIT" ]; then
|
|
echo "::error::hermes checkout is at $ACTUAL_COMMIT, expected $HERMES_COMMIT"
|
|
exit 1
|
|
fi
|
|
pip install -q -e /tmp/hermes-src
|
|
pip install -q aiohttp
|
|
pip install -q "git+https://git.moleculesai.app/molecule-ai/hermes-platform-molecule-a2a.git@${A2A_REF}#egg=hermes-platform-molecule-a2a"
|
|
pip install -q --extra-index-url "$MOLECULE_PRIVATE_INDEX" "molecules-workspace-runtime==$RUNTIME_VERSION"
|
|
# The image build runs vendor/hermes-agent/install.sh instead of
|
|
# downloading it (Dockerfile, above ARG HERMES_COMMIT). The upstream
|
|
# clone from the previous step is at HERMES_COMMIT, so compare git blob
|
|
# ids: equal ids mean byte-identical files. A HERMES_COMMIT bump that
|
|
# does not refresh the vendored copy fails here.
|
|
- name: Vendored installer is upstream scripts/install.sh at HERMES_COMMIT
|
|
if: github.event.pull_request.head.repo.fork != true
|
|
run: |
|
|
set -euo pipefail
|
|
HERMES_COMMIT=$(sed -n 's/^ARG HERMES_COMMIT=//p' Dockerfile | head -1)
|
|
test -n "$HERMES_COMMIT"
|
|
UPSTREAM_BLOB="$(git -C /tmp/hermes-src rev-parse "$HERMES_COMMIT:scripts/install.sh")"
|
|
VENDORED_BLOB="$(git hash-object vendor/hermes-agent/install.sh)"
|
|
if [ "$UPSTREAM_BLOB" != "$VENDORED_BLOB" ]; then
|
|
echo "::error::vendor/hermes-agent/install.sh is blob $VENDORED_BLOB, but scripts/install.sh at $HERMES_COMMIT is blob $UPSTREAM_BLOB. Refresh the copy (vendor/hermes-agent/README.md)."
|
|
exit 1
|
|
fi
|
|
echo "vendored installer == upstream scripts/install.sh at $HERMES_COMMIT (blob $VENDORED_BLOB)"
|
|
- name: Run the full-chain round-trip against the pinned gateway
|
|
if: github.event.pull_request.head.repo.fork != true
|
|
run: |
|
|
set -euo pipefail
|
|
HERMES_BIN="$(command -v hermes)"
|
|
export HERMES_BIN
|
|
python scripts/e2e_full_chain.py
|
|
|
|
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
|
|
with:
|
|
persist-credentials: false
|
|
- name: Skip executable tests for external forks
|
|
if: github.event.pull_request.head.repo.fork == true
|
|
run: echo "::notice::repository shell tests are disabled for external forks"
|
|
- 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.
|
|
- if: github.event.pull_request.head.repo.fork != true
|
|
run: pip install -q pyyaml
|
|
- if: github.event.pull_request.head.repo.fork != true
|
|
run: bash scripts/test-derive-provider.sh
|
|
- if: github.event.pull_request.head.repo.fork != true
|
|
run: bash scripts/test-derive-platform-llm.sh
|
|
- if: github.event.pull_request.head.repo.fork != true
|
|
run: bash scripts/test-derive-minimax-bridge.sh
|
|
- if: github.event.pull_request.head.repo.fork != true
|
|
run: bash scripts/test-install-prefix-strip.sh
|
|
- if: github.event.pull_request.head.repo.fork != true
|
|
run: bash scripts/test-load-workspace-config.sh
|
|
- if: github.event.pull_request.head.repo.fork != true
|
|
run: bash scripts/test-default-model-selection.sh
|
|
- if: github.event.pull_request.head.repo.fork != true
|
|
run: bash scripts/test-mcp-configs-dir.sh
|
|
- if: github.event.pull_request.head.repo.fork != true
|
|
run: bash scripts/test-nonroot-boot.sh
|
|
- if: github.event.pull_request.head.repo.fork != true
|
|
run: bash scripts/test-process-liveness.sh
|
|
- if: github.event.pull_request.head.repo.fork != true
|
|
run: bash scripts/test-mcp-reconcile-watch.sh
|
|
- if: github.event.pull_request.head.repo.fork != true
|
|
run: bash scripts/test-log-mirror.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.
|
|
#
|
|
# 2026-09-03: tests/test_executor_plugin_path.py used to be excluded here
|
|
# because it "currently fails independently (a2a extract_message_text /
|
|
# MagicMock-context version sensitivity)". That debt is now PAID — the
|
|
# runtime's canonical extract_message_text probes `.message` first to detect
|
|
# a RequestContext, and a bare MagicMock auto-creates that attribute, so
|
|
# every wire-level test read as a context wrapping a phantom message, got
|
|
# empty text, and returned before dispatching. Pinning `.message = None` on
|
|
# the mocked messages restores all 19. The file is folded in, as that comment
|
|
# anticipated. tests/test_vendor_branding_neutralized.py is folded in too:
|
|
# it is the guard that turns an upstream pin bump red until someone re-reads
|
|
# prompt_builder.py, and it cannot do that from outside CI.
|
|
conformance:
|
|
name: Adapter-socket conformance
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 5
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
persist-credentials: false
|
|
- name: Skip executable conformance for external forks
|
|
if: github.event.pull_request.head.repo.fork == true
|
|
run: echo "::notice::adapter imports and repository conformance tests are disabled for external forks"
|
|
- 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: github.event.pull_request.head.repo.fork != true && 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.
|
|
- if: github.event.pull_request.head.repo.fork != true
|
|
run: pip install -q --extra-index-url "$MOLECULE_PRIVATE_INDEX" -r requirements-dev.txt
|
|
# test_vendor_branding_neutralized.py and test_executor_plugin_path.py
|
|
# were NOT in this list (2026-09-03). That is how both rotted unnoticed:
|
|
# * the vendor-branding guard exists specifically to turn an upstream
|
|
# pin bump RED until someone re-reads prompt_builder.py — it cannot
|
|
# do that from outside CI, and upstream had meanwhile added a THIRD
|
|
# vendor constant the neutralizer did not know about;
|
|
# * every wire-level test in the executor file had silently stopped
|
|
# exercising the transport (runtime's extract_message_text changed
|
|
# shape-detection; a MagicMock message now reads as a RequestContext
|
|
# and yields empty text, so execute() short-circuited before
|
|
# dispatching). 19 of them failed on a pristine tree.
|
|
# Both are green and in the list now. A guard nobody runs is not a guard.
|
|
- name: Run adapter and private-runtime contracts
|
|
if: github.event.pull_request.head.repo.fork != true
|
|
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
|
|
tests/test_ci_runtime_image_pin.py
|
|
tests/test_ci_molecule_ci_fetch.py
|
|
tests/test_toolsearch_concierge_scope.py
|
|
tests/test_github_cli_present.py
|
|
tests/test_context_overflow_autoheal.py
|
|
tests/test_cron_approval_mode.py
|
|
tests/test_executor_plugin_path.py
|
|
tests/test_vendor_branding_neutralized.py
|
|
tests/test_vendored_hermes_installer.py
|
|
tests/test_start_sh_tmpdir.py
|
|
tests/test_turn_activity_plugin.py
|
|
-v
|