From e7e0082c83bcc2a8a37214697acb2bb963092250 Mon Sep 17 00:00:00 2001 From: devops-engineer Date: Thu, 11 Jun 2026 20:55:27 +0000 Subject: [PATCH 1/2] feat(ci): canonical CI / validate consumer ci.yml templates (inline validate job, sidestep Gitea-1.22 cross-repo uses:) Add templates/ci-{org-template,plugin,workspace-template}.yml as the canonical ci.yml that new generated repos inherit. Each sets workflow name: CI + a job whose status display-name is validate, so the emitted commit-status context is exactly CI / validate fleet-wide. This lets a single canonical branch-protection required context (CI / validate (pull_request)) work across every template/plugin repo and lets the bp-context-drift-gate (advisory, merged #32) later go hard. The validate job INLINES the canonical validator: it anon-clones the public molecule-ci SSOT at CI time and runs the validator script directly, instead of cross-repo uses: molecule-ai/molecule-ci/... which is Gitea-1.22.6-fragile (DEFAULT_ACTIONS_URL=github routes the reusable-workflow fetch to the suspended github.com org). Same SSOT (validator lives in molecule-ci, fetched fresh per run), no cross-repo uses: dependency. When the operator-host actions/* mirror lands the inline job can collapse back to uses: without changing the emitted CI / validate context. SSOT cloned into .molecule-ci (not .molecule-ci-canonical) so the canonical check-secrets.py SKIP_DIRS prunes it and does not self-flag. No consumer repo touched and no existing molecule-ci workflow changed, so molecule-ci CI (yaml-lint/python-lint/pytest/secrets-scan) is unaffected. Design: bp-drift-durable-fix.md step 1. Co-Authored-By: Claude Opus 4.8 (1M context) --- templates/ci-org-template.yml | 61 +++++++++++++++++++++ templates/ci-plugin.yml | 61 +++++++++++++++++++++ templates/ci-workspace-template.yml | 84 +++++++++++++++++++++++++++++ 3 files changed, 206 insertions(+) create mode 100644 templates/ci-org-template.yml create mode 100644 templates/ci-plugin.yml create mode 100644 templates/ci-workspace-template.yml diff --git a/templates/ci-org-template.yml b/templates/ci-org-template.yml new file mode 100644 index 0000000..a7186bd --- /dev/null +++ b/templates/ci-org-template.yml @@ -0,0 +1,61 @@ +# CANONICAL consumer ci.yml for org-template repos — molecule-ai/molecule-ci SSOT. +# +# This is the file NEW org-template repos inherit. It standardizes the +# emitted commit-status context to `CI / validate` (workflow `name: CI` + +# a job whose status display-name is `validate`), so a single canonical +# branch-protection required context — `CI / validate (pull_request)` — +# works fleet-wide. See molecule-ci .gitea/workflows/bp-context-drift-gate.yml +# and the design doc bp-drift-durable-fix.md for the drift root cause this +# resolves (a BP-required context with no emitter = perma-pending = HTTP 405 +# merge-block forever). +# +# WHY INLINE (not `uses: molecule-ai/molecule-ci/.gitea/workflows/...`): +# cross-repo `uses:` (workflow_call) does NOT resolve on Gitea 1.22.6 — +# [actions].DEFAULT_ACTIONS_URL=github routes the fetch to github.com where +# the `molecule-ai` org is suspended → 404/no-op. So the reusable +# validate-org-template.yml in molecule-ci is currently un-callable from a +# consumer. This template instead INLINES the validate job: it anon-clones +# the public molecule-ci SSOT at CI time and runs the canonical validator +# script directly. Same single-source-of-truth (the validator lives in +# molecule-ci, fetched fresh on every run), no cross-repo `uses:` dependency. +# +# When the operator-host actions/* mirror lands (internal task #109) and +# cross-repo `uses:` resolves, this inline job can collapse back to a +# `uses:` call WITHOUT changing the emitted `CI / validate` context. +# +# The SSOT is cloned into `.molecule-ci` (NOT `.molecule-ci-canonical`) so +# that the canonical check-secrets.py — whose SKIP_DIRS prunes `.molecule-ci` +# — does not scan molecule-ci's own fixtures and self-flag. + +name: CI + +on: + pull_request: + push: + branches: [main, staging] + workflow_dispatch: {} + +permissions: + contents: read + +jobs: + validate: + name: validate + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 + # Fetch the canonical validator from the molecule-ci SSOT. Anonymous + # clone of the public repo — see molecule-ci/.gitea/workflows/ + # validate-plugin.yml for why a direct git-clone is used instead of + # actions/checkout for the cross-repo fetch on Gitea 1.22.6. + - name: Fetch molecule-ci canonical scripts + run: git clone --depth 1 https://git.moleculesai.app/molecule-ai/molecule-ci.git .molecule-ci + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + with: + python-version: "3.11" + - run: pip install pyyaml -q + - name: Validate org-template contract + run: python3 .molecule-ci/.molecule-ci/scripts/validate-org-template.py + - name: Check for secrets + run: python3 .molecule-ci/.molecule-ci/scripts/check-secrets.py diff --git a/templates/ci-plugin.yml b/templates/ci-plugin.yml new file mode 100644 index 0000000..edd3e83 --- /dev/null +++ b/templates/ci-plugin.yml @@ -0,0 +1,61 @@ +# CANONICAL consumer ci.yml for plugin repos — molecule-ai/molecule-ci SSOT. +# +# This is the file NEW plugin repos inherit. It standardizes the emitted +# commit-status context to `CI / validate` (workflow `name: CI` + a job +# whose status display-name is `validate`), so a single canonical +# branch-protection required context — `CI / validate (pull_request)` — +# works fleet-wide. See molecule-ci .gitea/workflows/bp-context-drift-gate.yml +# and the design doc bp-drift-durable-fix.md for the drift root cause this +# resolves (a BP-required context with no emitter = perma-pending = HTTP 405 +# merge-block forever). +# +# WHY INLINE (not `uses: molecule-ai/molecule-ci/.gitea/workflows/...`): +# cross-repo `uses:` (workflow_call) does NOT resolve on Gitea 1.22.6 — +# [actions].DEFAULT_ACTIONS_URL=github routes the fetch to github.com where +# the `molecule-ai` org is suspended → 404/no-op. So the reusable +# validate-plugin.yml in molecule-ci is currently un-callable from a +# consumer. This template instead INLINES the validate job: it anon-clones +# the public molecule-ci SSOT at CI time and runs the canonical validator +# script directly. Same single-source-of-truth (the validator lives in +# molecule-ci, fetched fresh on every run), no cross-repo `uses:` dependency. +# +# When the operator-host actions/* mirror lands (internal task #109) and +# cross-repo `uses:` resolves, this inline job can collapse back to a +# `uses:` call WITHOUT changing the emitted `CI / validate` context. +# +# The SSOT is cloned into `.molecule-ci` (NOT `.molecule-ci-canonical`) so +# that the canonical check-secrets.py — whose SKIP_DIRS prunes `.molecule-ci` +# — does not scan molecule-ci's own fixtures and self-flag. + +name: CI + +on: + pull_request: + push: + branches: [main, staging] + workflow_dispatch: {} + +permissions: + contents: read + +jobs: + validate: + name: validate + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 + # Fetch the canonical validator from the molecule-ci SSOT. Anonymous + # clone of the public repo — see molecule-ci/.gitea/workflows/ + # validate-plugin.yml for why a direct git-clone is used instead of + # actions/checkout for the cross-repo fetch on Gitea 1.22.6. + - name: Fetch molecule-ci canonical scripts + run: git clone --depth 1 https://git.moleculesai.app/molecule-ai/molecule-ci.git .molecule-ci + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + with: + python-version: "3.11" + - run: pip install pyyaml -q + - name: Validate plugin contract + run: python3 .molecule-ci/.molecule-ci/scripts/validate-plugin.py + - name: Check for secrets + run: python3 .molecule-ci/.molecule-ci/scripts/check-secrets.py diff --git a/templates/ci-workspace-template.yml b/templates/ci-workspace-template.yml new file mode 100644 index 0000000..cc4372a --- /dev/null +++ b/templates/ci-workspace-template.yml @@ -0,0 +1,84 @@ +# CANONICAL consumer ci.yml for workspace-template repos — molecule-ai/molecule-ci SSOT. +# +# This is the file NEW workspace-template repos inherit. It standardizes the +# emitted commit-status context to `CI / validate` (workflow `name: CI` + a +# job whose status display-name is `validate`), so a single canonical +# branch-protection required context — `CI / validate (pull_request)` — +# works fleet-wide. See molecule-ci .gitea/workflows/bp-context-drift-gate.yml +# and the design doc bp-drift-durable-fix.md for the drift root cause this +# resolves (a BP-required context with no emitter = perma-pending = HTTP 405 +# merge-block forever). +# +# WHY INLINE (not `uses: molecule-ai/molecule-ci/.gitea/workflows/...`): +# cross-repo `uses:` (workflow_call) does NOT resolve on Gitea 1.22.6 — +# [actions].DEFAULT_ACTIONS_URL=github routes the fetch to github.com where +# the `molecule-ai` org is suspended → 404/no-op. So the reusable +# validate-workspace-template.yml in molecule-ci is currently un-callable +# from a consumer. This template instead INLINES the validate job: it +# anon-clones the public molecule-ci SSOT at CI time and runs the canonical +# validator script directly. Same single-source-of-truth (the validator +# lives in molecule-ci, fetched fresh on every run), no cross-repo `uses:`. +# +# When the operator-host actions/* mirror lands (internal task #109) and +# cross-repo `uses:` resolves, this inline job can collapse back to a +# `uses:` call WITHOUT changing the emitted `CI / validate` context. +# +# Fork-PR security: on EXTERNAL fork PRs the validator runs in --static-only +# mode (no pip install of the template's requirements.txt, no adapter.py +# import, no docker build) — those are arbitrary-code-execution primitives +# from an untrusted PR's perspective. Internal PRs and pushes get the full +# runtime validation. This mirrors the static/runtime split in the reusable +# validate-workspace-template.yml while still emitting the single +# `CI / validate` context. +# +# The SSOT is cloned into `.molecule-ci` (NOT `.molecule-ci-canonical`) so +# that the canonical check-secrets.py — whose SKIP_DIRS prunes `.molecule-ci` +# — does not scan molecule-ci's own fixtures and self-flag. + +name: CI + +on: + pull_request: + push: + branches: [main, staging] + workflow_dispatch: {} + +permissions: + contents: read + +jobs: + validate: + name: validate + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 + # Fetch the canonical validator from the molecule-ci SSOT. Anonymous + # clone of the public repo — see molecule-ci/.gitea/workflows/ + # validate-plugin.yml for why a direct git-clone is used instead of + # actions/checkout for the cross-repo fetch on Gitea 1.22.6. + - name: Fetch molecule-ci canonical scripts + run: git clone --depth 1 https://git.moleculesai.app/molecule-ai/molecule-ci.git .molecule-ci + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + with: + python-version: "3.11" + - run: pip install pyyaml -q + # Secret scan — always runs, including on external fork PRs (static, + # no code execution; the most important security check). + - name: Check for secrets + run: python3 .molecule-ci/.molecule-ci/scripts/check-secrets.py + # On external fork PRs: STATIC-ONLY validation (no pip install of the + # template's requirements.txt, no adapter.py import). Untrusted code. + - name: Validate workspace-template contract (static-only, fork PR) + if: github.event.pull_request.head.repo.fork == true + run: python3 .molecule-ci/scripts/validate-workspace-template.py --static-only + # Internal PRs and pushes: FULL runtime validation. Install the + # template's own runtime deps so the validator can import adapter.py + # the same way the workspace container does at boot. + - if: github.event.pull_request.head.repo.fork != true && hashFiles('requirements.txt') != '' + run: pip install -q -r requirements.txt + - if: github.event.pull_request.head.repo.fork != true && hashFiles('requirements.txt') == '' + run: pip install -q molecule-ai-workspace-runtime + - name: Validate workspace-template contract (full) + if: github.event.pull_request.head.repo.fork != true + run: python3 .molecule-ci/scripts/validate-workspace-template.py -- 2.52.0 From 36daaf28d17c2f463f7de7a036d35627b4942b54 Mon Sep 17 00:00:00 2001 From: devops-engineer Date: Thu, 11 Jun 2026 21:11:43 +0000 Subject: [PATCH 2/2] =?UTF-8?q?fix(ci):=20correct=20workspace-template=20v?= =?UTF-8?q?alidator=20path=20to=20cloned=20SSOT=20layout=20(.molecule-ci/.?= =?UTF-8?q?molecule-ci/scripts)=20=E2=80=94=20CR2=20review=2010990?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- templates/ci-workspace-template.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/ci-workspace-template.yml b/templates/ci-workspace-template.yml index cc4372a..90b2f4c 100644 --- a/templates/ci-workspace-template.yml +++ b/templates/ci-workspace-template.yml @@ -71,7 +71,7 @@ jobs: # template's requirements.txt, no adapter.py import). Untrusted code. - name: Validate workspace-template contract (static-only, fork PR) if: github.event.pull_request.head.repo.fork == true - run: python3 .molecule-ci/scripts/validate-workspace-template.py --static-only + run: python3 .molecule-ci/.molecule-ci/scripts/validate-workspace-template.py --static-only # Internal PRs and pushes: FULL runtime validation. Install the # template's own runtime deps so the validator can import adapter.py # the same way the workspace container does at boot. @@ -81,4 +81,4 @@ jobs: run: pip install -q molecule-ai-workspace-runtime - name: Validate workspace-template contract (full) if: github.event.pull_request.head.repo.fork != true - run: python3 .molecule-ci/scripts/validate-workspace-template.py + run: python3 .molecule-ci/.molecule-ci/scripts/validate-workspace-template.py -- 2.52.0