eab25a5d47
Test / skill-framework-cli (pull_request) Successful in 14s
Test / package-smoke (pull_request) Successful in 20s
contracts-codegen-drift / codegen-drift (pull_request) Successful in 9s
contracts-codegen-drift / contract-conformance (pull_request) Successful in 14s
contracts-codegen-drift / adapter-registry-conformance (pull_request) Successful in 15s
contracts-codegen-drift / validate (pull_request) Successful in 19s
contracts-codegen-drift / native-plugins-registry-conformance (pull_request) Successful in 13s
contracts-codegen-drift / plugin-state-conformance (pull_request) Successful in 14s
contracts-codegen-drift / workspace-routes-manifest-conformance (pull_request) Successful in 17s
contracts-codegen-drift / prompt-canonical-text-conformance (pull_request) Successful in 16s
Test / test (3.11) (pull_request) Successful in 48s
contracts-codegen-drift / env-contract-drift (pull_request) Successful in 11s
contracts-codegen-drift / platform-identity-gate-conformance (pull_request) Successful in 15s
contracts-codegen-drift / branding-conformance (pull_request) Successful in 14s
Test / test (3.12) (pull_request) Successful in 51s
contracts-codegen-drift / env-contract-conformance (pull_request) Successful in 16s
meta-ci-advisory / meta (advisory) (pull_request) Successful in 8s
llm-registry-ssot-drift / llm-registry-ssot-drift (pull_request) Successful in 12s
contracts-codegen-drift / org-template declares something (pull_request) Successful in 23s
contracts-codegen-drift / schedule accepts both cron spellings (pull_request) Successful in 20s
registry-ssot-drift / registry-ssot-drift (pull_request) Successful in 11s
contracts-codegen-drift / plugin-install-report-conformance (pull_request) Successful in 17s
Test / test (3.13) (pull_request) Successful in 1m4s
contracts-codegen-drift / go-parity (pull_request) Successful in 20s
llm-registry-ssot-drift / llm-registry-ssot-conformance (pull_request) Successful in 17s
Test / all-required (pull_request) Successful in 2s
contracts-codegen-drift / bindings-parity (pull_request) Successful in 26s
registry-ssot-drift / registry-ssot-conformance (pull_request) Successful in 15s
contracts-codegen-drift / all-required (pull_request) Successful in 3s
REPORTED DEFECT. The registry claimed molecule-controlplane never reads
MOLECULE_DECLARE_DEFAULT_NATIVE_PLUGINS ("zero occurrences") and filed it dark.
Both were false. CP reads it at internal/handlers/tenant_config.go:140 and
RELAYS it into the /tenants/config response every tenant consumes; production
has it enabled in Infisical /shared/controlplane. Corrected to two readers
(cp + tenant), cross_plane with defaults_agree RECOMPUTED, status live.
ROOT CAUSE, and it was systematic. The sweep grepped LOCAL WORKING TREES:
controlplane's was on a feature branch 222 commits behind main, the runtime's
was on a feature branch and being edited concurrently, and molecule-core's own
origin/main ref was stale. Re-deriving against authoritative origin/main found
the reported row was not alone:
40 citations pointing at the wrong line
4 readers missed entirely (MOLECULE_MODEL on cp + workspace,
MOLECULE_AGENT_NAME and MOLECULE_WORKSPACE_TOKEN on workspace)
1 declared flag that no longer exists on main at all
(FLEET_IMAGE_RECONCILE_DRY_RUN — fleet_reconcile_wire.go now hardcodes
Converger:nil + DryRun:true) — removed
1 false "this plane never reads it" claim
THREE FALSE-NEGATIVE CLASSES IN THE GATE ITSELF, which is why re-deriving was
not enough. The extractor only saw string literals at the call site, so it was
blind to:
* the name constant — `const declareDefaultNativePluginsEnv = "..."` then
`os.Getenv(declareDefaultNativePluginsEnv)`; also the runtime's
`KERNEL_FLAG_ENV`. Resolution is now repo-wide (binding and accessor
routinely live in different files); an identifier bound to two names is
reported as `ambiguous-const`, never guessed.
* the package-qualified helper — `envx.Bool("NAME", false)`, which is
molecule-core's STANDARD boolean gate helper. The env-ness is in the
qualifier; matching only the callee ("Bool") missed it.
* shell and Dockerfile — `if [ "$MEMORY_V2_CUTOVER" = "true" ]`. Restricted
to shell-ish files so `${...}` in TS/Go does not flood the gate.
The first two are the idioms the BEST-engineered flags use, so a literal-only
extractor reported exactly the wrong repos clean — a guard covering nothing
while looking like it covered everything.
One false POSITIVE fixed too: `env["X"] = v` is a WRITE. CP assembles each
workspace's desired env that way, and counting it as a read would declare CP a
consumer of vars it only emits — the mirror image of missing a reader.
STRUCTURAL FIX, so this cannot recur silently:
* tools/env-contract/extract.mjs — extraction factored out, shared by the
gate and the verifier so the two can never disagree about what a read is.
They did disagree: the verifier's literal-substring test called correct
const-mediated citations dead.
* tools/env-contract/verify-sites.mjs — re-verifies every consumer citation
against origin/main through the API, follows Python imports for cross-file
bindings, reports when a consumer's main has MOVED since verification, and
exits 2 (never 0) without credentials. Operator tool, not a CI job: CI has
no Gitea credential and a step that skipped without one would go green over
an unverified registry, since a skipped context passes a wildcard branch
protection.
* provenance{} pins the origin/main commit per consumer; every reader carries
site_verified_at; CI fails any consumer citation older than 180 days, which
is only refreshable by running the verifier.
* default_off_reason `enabled_by_config` + `enabled_in` — the state the
registry could not previously express and therefore recorded backwards: the
CODE default is off but production enables it, so the capability is LIVE.
The schema now forbids such an entry from also being dark.
83/83 consumer citations verify against origin/main. 34 node self-tests (7 new,
each red first), 41 conformance tests. Re-proved the gate fails on a deliberate
violation using the const idiom it was previously blind to: exit 0 -> exit 1.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
520 lines
27 KiB
YAML
520 lines
27 KiB
YAML
# Contracts codegen-drift + validate + parity gate (ONE merged gate over the
|
||
# whole contracts/ SSOT — supersedes cloudproviders-codegen-drift.yml).
|
||
#
|
||
# molecule-ai-sdk is the consolidated home of TWO contract IDL kinds that now
|
||
# share ONE contracts/ + gen/ + tools/:
|
||
# 1. the flat cloud-provider YAML SSOT (contracts/cloudproviders.yaml, #40)
|
||
# 2. the folded molecule-contracts JSON-Schema contracts (contracts/<domain>/
|
||
# *.contract.json + *.schema.json — mcp, plugin-manifest, workspace-template,
|
||
# org-template, catalog, provision-request, promote-request, workspace-comms)
|
||
#
|
||
# The invariant: the committed generated bindings under gen/{go,ts,python} are
|
||
# EXACTLY what tools/gen-*.mjs produce from contracts/ — never hand-edited, never
|
||
# stale — AND every JSON-Schema contract instance validates against its schema.
|
||
# Independent guards, all feeding a strict all-required aggregator:
|
||
#
|
||
# validate (python) check-jsonschema every contracts/<domain>/
|
||
# *.contract.json against its sibling *.schema.json (the
|
||
# JSON-Schema IDL kind; makes const/enum/required pins
|
||
# load-bearing — e.g. required_tool==provision_workspace).
|
||
# codegen-drift (node-only) re-run ALL generators (both IDL kinds) and
|
||
# fail on any diff in the GENERATED paths (scoped so the
|
||
# verbatim-moved llmwire/tools/shim files are never flagged).
|
||
# contract-conformance (python) jsonschema-validate the cloudproviders YAML +
|
||
# assert the semantic invariants (unique ids/aliases, one
|
||
# is_local, default resolves, {persist}∪{""}==migration-056).
|
||
# go-parity (go) build + vet + test gen/go incl. the folded
|
||
# molcontracts package, the moved-verbatim cloudprovider
|
||
# behavior oracle, llmwire pins, nodup-lint, dependency guard.
|
||
|
||
name: contracts-codegen-drift
|
||
|
||
on:
|
||
push:
|
||
branches: [main]
|
||
pull_request:
|
||
branches: [main]
|
||
|
||
concurrency:
|
||
group: contracts-codegen-drift-${{ github.ref }}
|
||
cancel-in-progress: true
|
||
|
||
jobs:
|
||
validate:
|
||
name: validate
|
||
runs-on: ubuntu-latest
|
||
timeout-minutes: 10
|
||
steps:
|
||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||
with:
|
||
python-version: "3.12"
|
||
- name: Install check-jsonschema
|
||
run: pip install --no-cache-dir check-jsonschema
|
||
- name: Validate every contracts/<domain>/*.contract.json against its sibling schema
|
||
shell: bash
|
||
run: |
|
||
set -euo pipefail
|
||
shopt -s nullglob
|
||
contracts=(contracts/*/*.contract.json)
|
||
if [ ${#contracts[@]} -eq 0 ]; then
|
||
echo "::error::no contracts/*/*.contract.json instances found — nothing to validate (fail-closed)"
|
||
exit 1
|
||
fi
|
||
rc=0
|
||
for instance in "${contracts[@]}"; do
|
||
schema="${instance%.contract.json}.schema.json"
|
||
if [ ! -f "$schema" ]; then
|
||
echo "::error file=$instance::missing sibling schema $schema (fail-closed)"
|
||
rc=1
|
||
continue
|
||
fi
|
||
echo "validating $instance against $schema"
|
||
if ! check-jsonschema --schemafile "$schema" "$instance"; then
|
||
echo "::error file=$instance::failed schema validation against $schema"
|
||
rc=1
|
||
fi
|
||
done
|
||
if [ "$rc" -ne 0 ]; then
|
||
echo "::error::one or more contract instances failed schema validation"
|
||
exit 1
|
||
fi
|
||
echo "all contract instances validate against their schemas"
|
||
|
||
codegen-drift:
|
||
name: codegen-drift
|
||
runs-on: ubuntu-latest
|
||
timeout-minutes: 10
|
||
steps:
|
||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||
with:
|
||
node-version: "20"
|
||
- name: Regenerate open RuntimeId schemas and bindings
|
||
run: node tools/gen-runtimes.mjs
|
||
- name: Regenerate NativePlugins bindings from the native-plugins registry
|
||
run: node tools/gen-native-plugins.mjs
|
||
- name: Regenerate Go bindings (cloudprovider + molcontracts) from the SSOT
|
||
run: node tools/gen-go.mjs
|
||
- name: Regenerate TypeScript bindings (cloudprovider + contract/comms/catalog)
|
||
run: node tools/gen-ts.mjs
|
||
- name: Regenerate Python bindings (cloudprovider + contract/comms/catalog)
|
||
run: node tools/gen-python.mjs
|
||
- name: Regenerate skill-framework bindings (EnterOS Skill Framework contracts)
|
||
run: node tools/gen-skill-framework.mjs
|
||
- name: Fail if committed gen/ differs from a fresh regeneration
|
||
shell: bash
|
||
run: |
|
||
set -euo pipefail
|
||
# Scope to the GENERATED paths only. The hand-authored, NON-generated
|
||
# files that also live under gen/ are intentionally out of scope here
|
||
# (the go-parity/build jobs compile/type-check them instead):
|
||
# gen/go/{llmwire,tools,doc.go,deps_test.go,go.mod,go.sum} + cloudprovider_test.go
|
||
# gen/ts/{index.ts,package.json,tsconfig.json}
|
||
# gen/python/{__init__.py,pyproject.toml}
|
||
if ! git diff --exit-code -- \
|
||
contracts/plugin-manifest/plugin-manifest.schema.json \
|
||
contracts/workspace-template/workspace-template.schema.json \
|
||
contracts/org-template/org-template.schema.json \
|
||
contracts/catalog/catalog-entry.schema.json \
|
||
contracts/catalog/catalog.schema.json \
|
||
contracts/catalog/publish-request.schema.json \
|
||
molecule_plugin/_runtime_ids.py \
|
||
gen/go/cloudprovider/cloudprovider.go \
|
||
gen/go/molcontracts \
|
||
gen/ts/cloudproviders.generated.ts \
|
||
gen/ts/contract_gen.ts gen/ts/workspace_comms_gen.ts gen/ts/catalog_gen.ts gen/ts/idle_prompt_gen.ts gen/ts/runtime_ids.generated.ts \
|
||
gen/ts/native_plugins.generated.ts \
|
||
gen/ts/skill_framework_gen.ts \
|
||
gen/ts/branding.generated.ts \
|
||
gen/python/cloudproviders.py \
|
||
gen/python/contract_gen.py gen/python/workspace_comms_gen.py gen/python/catalog_gen.py gen/python/idle_prompt_gen.py gen/python/runtime_ids_gen.py \
|
||
gen/python/native_plugins_gen.py \
|
||
gen/python/skill_framework_gen.py \
|
||
gen/python/branding_gen.py \
|
||
gen/python/plugin_install_report_gen.py; then
|
||
echo "::error::gen/ is out of date — committed generated bindings differ from a fresh"
|
||
echo "::error::run of the generators. gen/ is NEVER hand-edited. Regenerate locally with"
|
||
echo "::error::'node tools/gen-runtimes.mjs && node tools/gen-native-plugins.mjs && node tools/gen-go.mjs && node tools/gen-ts.mjs && node tools/gen-python.mjs && node tools/gen-skill-framework.mjs'"
|
||
echo "::error::and commit the result."
|
||
exit 1
|
||
fi
|
||
echo "gen/ is in sync with contracts/ (no drift, both IDL kinds)"
|
||
|
||
contract-conformance:
|
||
name: contract-conformance
|
||
runs-on: ubuntu-latest
|
||
timeout-minutes: 10
|
||
steps:
|
||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||
with:
|
||
python-version: "3.12"
|
||
- name: Install validators
|
||
run: pip install pytest "jsonschema>=4.18" "pyyaml>=6.0"
|
||
- name: Validate cloudproviders SSOT schema + semantic invariants
|
||
# --noconftest keeps this job hermetic: it needs only the contract test,
|
||
# not the package's runtime deps (the repo's main `test` job exercises
|
||
# the package + conftest fixtures separately).
|
||
run: python -m pytest tests/test_cloudproviders_contract.py -q --noconftest
|
||
|
||
adapter-registry-conformance:
|
||
# ADR-004 §2: official-runtimes.registry.json is the SSOT for WHICH runtimes
|
||
# are natively supported; mcp-plugin-delivery.contract.json is SSOT for the
|
||
# MCP descriptor + gate tool. Their per-runtime native surfaces MUST stay
|
||
# RECONCILED (same runtime set, same native path/format/server-map key,
|
||
# same server-name/required-tool literals). This gate turns that prose
|
||
# invariant into a cross-file assertion — a one-sided edit reds here.
|
||
name: adapter-registry-conformance
|
||
runs-on: ubuntu-latest
|
||
timeout-minutes: 10
|
||
steps:
|
||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||
with:
|
||
python-version: "3.12"
|
||
- name: Install validators
|
||
run: pip install --no-cache-dir pytest "jsonschema>=4.18"
|
||
- name: Reconcile the official runtime registry with the MCP delivery contract
|
||
run: python -m pytest tests/test_adapter_registry_contract.py -q --noconftest
|
||
|
||
native-plugins-registry-conformance:
|
||
# native-plugins.registry.json is the SSOT for the set of platform-delivered
|
||
# first-party plugins and their install policy. This gate validates the
|
||
# registry against its schema + the load-bearing invariants (key==name,
|
||
# pinned source, install in {default,concierge}, the scheduler/mgmt-MCP
|
||
# entries carry their expected policy) with negative controls.
|
||
name: native-plugins-registry-conformance
|
||
runs-on: ubuntu-latest
|
||
timeout-minutes: 10
|
||
steps:
|
||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||
with:
|
||
python-version: "3.12"
|
||
- name: Install validators
|
||
run: pip install --no-cache-dir pytest "jsonschema>=4.18"
|
||
- name: Validate the native plugins registry against its schema + invariants
|
||
run: python -m pytest tests/test_native_plugins_registry_contract.py -q --noconftest
|
||
|
||
plugin-state-conformance:
|
||
# contracts/plugin-state/plugin-state.contract.json is the delivery SSOT for
|
||
# what a plugin-manifest `contributes.state` declaration produces: the
|
||
# reserved daemon env vars (MOLECULE_PLUGIN_STATE_DIR /
|
||
# MOLECULE_PLUGIN_STATE_DURABLE), the per-plugin directory template, the
|
||
# provisioner-declared durability signal, and the degradation policy.
|
||
# THREE consumers must agree on those literals (controlplane provisioner Go,
|
||
# molecule_runtime injector Python, plugin authors) — molecule-ai-workspace-
|
||
# runtime#360 produced three "fixed in one place, not the other" regressions
|
||
# in a single day, so each literal is const-pinned and asserted here. The
|
||
# generic `validate` glob proves the instance validates; this job proves the
|
||
# load-bearing invariants (degradation never fail-closed, identity never
|
||
# plugin-supplied, migration never deleting, root outside the volumes the
|
||
# teardown path destroys) with negative controls.
|
||
name: plugin-state-conformance
|
||
runs-on: ubuntu-latest
|
||
timeout-minutes: 10
|
||
steps:
|
||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||
with:
|
||
python-version: "3.12"
|
||
- name: Install validators
|
||
run: pip install --no-cache-dir pytest "jsonschema>=4.18"
|
||
- name: Validate the plugin-state contract + its declaration surface
|
||
run: python -m pytest tests/test_plugin_state_contract.py -q --noconftest
|
||
|
||
workspace-routes-manifest-conformance:
|
||
# contracts/workspace-comms/routes.manifest.json is the SDK-owned SSOT for the
|
||
# method+path of the registry + A2A endpoint lane (RFC molecule-core#4428,
|
||
# Phase 0). It is NOT a *.contract.json instance, so the generic validate glob
|
||
# does not cover it — this dedicated gate validates it against its schema and
|
||
# asserts the load-bearing invariants (unique ids, the exact registry+a2a lane,
|
||
# both contract-lane headers) with negative controls. Mirrors the
|
||
# native-plugins-registry-conformance job.
|
||
name: workspace-routes-manifest-conformance
|
||
runs-on: ubuntu-latest
|
||
timeout-minutes: 10
|
||
steps:
|
||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||
with:
|
||
python-version: "3.12"
|
||
- name: Install validators
|
||
run: pip install --no-cache-dir pytest "jsonschema>=4.18"
|
||
- name: Validate the workspace routes manifest against its schema + invariants
|
||
run: python -m pytest tests/test_workspace_routes_manifest_contract.py -q --noconftest
|
||
|
||
prompt-canonical-text-conformance:
|
||
# TestSSOT: the prompt contract's embedded canonical_text (BASE_PLATFORM_PROMPT
|
||
# + ORCHESTRATOR_ONLY_GUARDRAIL) MUST stay byte-identical to the runtime
|
||
# producer (molecule_runtime.prompt). Offline leg validates the contract vs
|
||
# the vendored SSOT mirror; a runtime checkout (MOLECULE_RUNTIME_SRC) adds a
|
||
# live cross-repo leg. A silent drift un-gags a concierge / changes the
|
||
# platform identity frame.
|
||
name: prompt-canonical-text-conformance
|
||
runs-on: ubuntu-latest
|
||
timeout-minutes: 10
|
||
steps:
|
||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||
with:
|
||
python-version: "3.12"
|
||
- name: Install validators
|
||
run: pip install --no-cache-dir pytest
|
||
- name: Prompt contract canonical text == runtime SSOT (byte-identical)
|
||
run: python -m pytest tests/test_prompt_canonical_text_contract.py -q --noconftest
|
||
|
||
platform-identity-gate-conformance:
|
||
# The wire-contract fail-closed enforcement schema (platform-identity-gate)
|
||
# run against its golden decision-table cases: every `pass` case validates,
|
||
# every `fail` case is rejected (anti-vacuous proof of the divergence-#5
|
||
# bypass class — online-claimed-without-loaded-proof).
|
||
name: platform-identity-gate-conformance
|
||
runs-on: ubuntu-latest
|
||
timeout-minutes: 10
|
||
steps:
|
||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||
with:
|
||
python-version: "3.12"
|
||
- name: Install validators
|
||
run: pip install --no-cache-dir pytest "jsonschema>=4.18"
|
||
- name: Run the platform-identity fail-closed gate against golden cases
|
||
run: python -m pytest tests/test_platform_identity_gate_conformance.py -q --noconftest
|
||
|
||
branding-conformance:
|
||
# contracts/branding/branding.contract.json is the machine-readable mirror
|
||
# source for the branding identity SSOT (the hand-written Go authority is
|
||
# gen/go/branding/branding.go; its branding_contract_parity_test.go pins
|
||
# Go == the instance in the go-parity job). This gate schema-validates the
|
||
# instance, asserts the B1 exact-value pins (Tier-1 flipped to Enter OS,
|
||
# Tier-2 held at legacy), the cross-SSOT agreement with cloudproviders.yaml
|
||
# + registry.yaml, and parity between the generated Python mirror and the
|
||
# instance. The TS leg runs in bindings-parity (branding.test.cjs).
|
||
name: branding-conformance
|
||
runs-on: ubuntu-latest
|
||
timeout-minutes: 10
|
||
steps:
|
||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||
with:
|
||
python-version: "3.12"
|
||
- name: Install validators
|
||
run: pip install --no-cache-dir pytest "jsonschema>=4.18" "pyyaml>=6.0"
|
||
- name: Validate the branding SSOT schema + pins + cross-SSOT + mirror parity
|
||
run: python -m pytest tests/test_branding_contract.py -q --noconftest
|
||
|
||
env-contract-conformance:
|
||
# contracts/env/env.registry.json is the SSOT for every governed environment
|
||
# variable on the platform: owning plane, kind, what happens when nobody sets
|
||
# it, and — for a gate that legitimately defaults off — a MACHINE-CHECKABLE
|
||
# precondition instead of a prose comment. It generalises the per-module
|
||
# x-enteros.env seam this repo already ships (packages/core/src/ctx.ts
|
||
# createCtx + packages/mcp-server/src/env-check.ts checkModuleEnv + env_doctor)
|
||
# from one module's process to the whole platform.
|
||
#
|
||
# env.registry.json is NOT a *.contract.json instance, so the generic
|
||
# `validate` glob does not cover it — this dedicated gate validates it against
|
||
# its schema and asserts the invariants a schema cannot express, each with a
|
||
# NEGATIVE CONTROL:
|
||
# * cross_plane.defaults_agree is RECOMPUTED from readers[].default_on, so an
|
||
# entry cannot assert an agreement it does not have (MOLECULE_MAILBOX_KERNEL
|
||
# genuinely disagrees across cp and workspace).
|
||
# * a `gate_on` precondition must resolve to a DECLARED gate — the defect
|
||
# class that let a rollout note name DELEGATION_RESULT_INBOX_PUSH, a flag
|
||
# appearing nowhere in the repo that named it.
|
||
# * a `contract_pin` precondition is resolved for real through its JSON
|
||
# Pointer, so MOLECULE_MAILBOX_KERNEL's declaration reds the day
|
||
# contracts/idle-prompt stops pinning kernel_gated_writes const true.
|
||
# Mirrors the native-plugins-registry-conformance job.
|
||
name: env-contract-conformance
|
||
runs-on: ubuntu-latest
|
||
timeout-minutes: 10
|
||
steps:
|
||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||
with:
|
||
python-version: "3.12"
|
||
- name: Install validators
|
||
run: pip install --no-cache-dir pytest "jsonschema>=4.18"
|
||
- name: Validate the env registry against its schema + invariants
|
||
run: python -m pytest tests/test_env_registry_contract.py -q --noconftest
|
||
|
||
env-contract-drift:
|
||
# THE DRIFT GATE. Fails when this repo reads a governed environment variable
|
||
# that contracts/env/env.registry.json does not declare, or when the registry
|
||
# cites a read site that no longer exists. molecule-ai-sdk is at
|
||
# drift_gate=enforcing so the lint is proven to fail on a real violation —
|
||
# this codebase has several guards that report success while covering nothing,
|
||
# and a lint that has never failed is not a lint. The self-tests build a
|
||
# throwaway repo per violation class and assert the exit code AND the message;
|
||
# the lint's own integration leg then runs it against this repo for real.
|
||
#
|
||
# Node stdlib only, no npm install — the same posture as skill-framework-cli,
|
||
# and what makes a consumer able to adopt this with one vendored file.
|
||
#
|
||
# node-version 22, NOT 20: `node --test <glob>` only accepts a glob pattern
|
||
# from Node 21 onward. On 20 the quoted pattern is taken as a literal path
|
||
# and the step dies with "Could not find '.../tools/env-contract/*.test.mjs'".
|
||
# skill-framework-cli already pins 22 for the same invocation; the other
|
||
# node jobs here pin 20 because they only run generators, which take paths.
|
||
name: env-contract-drift
|
||
runs-on: ubuntu-latest
|
||
timeout-minutes: 10
|
||
steps:
|
||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||
with:
|
||
node-version: "22"
|
||
- name: Self-test the drift gate (stdlib node --test)
|
||
run: node --test "tools/env-contract/*.test.mjs"
|
||
- name: Fail on any undeclared governed env read in this repo
|
||
run: node tools/env-contract/lint.mjs --repo molecule-ai-sdk
|
||
- name: env_doctor (static) — declared surface, darkness and preconditions
|
||
run: node tools/env-contract/lint.mjs --report
|
||
# Cross-repo citations are NOT verified here: this job has no Gitea
|
||
# credential, and a step that skipped without one would go green over an
|
||
# unverified registry (a `skipped` context passes a wildcard branch
|
||
# protection). tools/env-contract/verify-sites.mjs is the operator tool;
|
||
# what CI enforces is the site_verified_at freshness ceiling asserted in
|
||
# tests/test_env_registry_contract.py.
|
||
|
||
org-template-declaration:
|
||
name: org-template declares something
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||
with:
|
||
python-version: "3.12"
|
||
- name: Install validators
|
||
run: pip install --no-cache-dir pytest "jsonschema>=4.18"
|
||
# The NEGATIVE half of the org-template contract. `anyOf` tests key
|
||
# PRESENCE, so `workspaces: []` satisfied it and an org template declaring
|
||
# nothing validated. A stale vendored validator used to catch that by
|
||
# testing truthiness; deleting it (org-template-molecule-dev#21) removed the
|
||
# only check that saw it. molecule-ci#106.
|
||
- name: An org template must declare something (workspaces:[] is not a declaration)
|
||
run: python -m pytest tests/test_org_template_empty_declaration.py -q --noconftest
|
||
|
||
schedule-cron-spellings:
|
||
name: schedule accepts both cron spellings
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||
with:
|
||
python-version: "3.12"
|
||
- name: Install validators
|
||
run: pip install --no-cache-dir pytest "jsonschema>=4.18"
|
||
# The schema modelled only core's `cron_expr` and so REJECTED the shipped
|
||
# platform-agent template, which authors the runtime-native `cron` for the
|
||
# verbatim concierge-graft path. sdk#178.
|
||
- name: A schedule may spell cron either way — but not both
|
||
run: python -m pytest tests/test_schedule_cron_spellings.py -q --noconftest
|
||
|
||
go-parity:
|
||
name: go-parity
|
||
runs-on: ubuntu-latest
|
||
timeout-minutes: 10
|
||
env:
|
||
GOTOOLCHAIN: local
|
||
steps:
|
||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
|
||
with:
|
||
go-version-file: gen/go/go.mod
|
||
cache: false
|
||
- name: go mod verify
|
||
working-directory: gen/go
|
||
run: go mod verify
|
||
- name: build (cloudprovider + llmwire + molcontracts + nodup-lint)
|
||
working-directory: gen/go
|
||
run: go build ./...
|
||
- name: vet
|
||
working-directory: gen/go
|
||
run: go vet ./...
|
||
- name: test (parity oracle + llmwire + nodup-lint + molcontracts)
|
||
working-directory: gen/go
|
||
run: go test ./...
|
||
- name: dependency guard (SDK depends on neither consumer)
|
||
working-directory: gen/go
|
||
run: go test -run TestNoConsumerDependency -v .
|
||
|
||
bindings-parity:
|
||
name: bindings-parity
|
||
runs-on: ubuntu-latest
|
||
timeout-minutes: 10
|
||
steps:
|
||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||
with:
|
||
node-version: "20"
|
||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||
with:
|
||
python-version: "3.12"
|
||
- name: Build and test TypeScript bindings
|
||
working-directory: gen/ts
|
||
run: npm ci && npm test
|
||
- name: Build Python bindings
|
||
run: pip install --no-cache-dir build && python -m build gen/python
|
||
|
||
plugin-install-report-conformance:
|
||
# contracts/plugin-install-report/ is the SSOT for how the runtime tells core
|
||
# what happened when it installed a workspace's declared plugins. Three of its
|
||
# values are the whole reason it exists, and each is ONE boolean flip away from
|
||
# re-creating the outage it addresses:
|
||
# concierge_gated:false — boot-step telemetry IS gated on kind=platform, which
|
||
# is why a fleet-wide boot-install failure was invisible for every
|
||
# kind=workspace workspace in prod on 2026-07-30: the only boxes core could
|
||
# see were the only boxes that were fine.
|
||
# durable:true — POST /boot-event is BroadcastOnly ("no structure_events row"),
|
||
# so it answers nothing for an operator asking later.
|
||
# outcome_rule — `installed` lists sources STAGED, not live; installed=[6] with
|
||
# swapped=false means nothing is live, and that state was indistinguishable
|
||
# from success in every signal core had.
|
||
# The generic `validate` glob proves the instance validates; this job proves
|
||
# those invariants, that the wire fields still mirror InstallReport 1:1, and that
|
||
# the Go + Python bindings were generated AT ALL — with negative controls.
|
||
name: plugin-install-report-conformance
|
||
runs-on: ubuntu-latest
|
||
timeout-minutes: 10
|
||
steps:
|
||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||
with:
|
||
python-version: "3.12"
|
||
- name: Install validators
|
||
run: pip install --no-cache-dir pytest "jsonschema>=4.18"
|
||
- name: Validate the plugin-install-report contract + its generated bindings
|
||
run: python -m pytest tests/test_plugin_install_report_contract.py -q --noconftest
|
||
|
||
all-required:
|
||
name: all-required
|
||
needs: [validate, codegen-drift, contract-conformance, adapter-registry-conformance, native-plugins-registry-conformance, plugin-state-conformance, plugin-install-report-conformance, workspace-routes-manifest-conformance, prompt-canonical-text-conformance, platform-identity-gate-conformance, branding-conformance, env-contract-conformance, env-contract-drift, org-template-declaration, schedule-cron-spellings, go-parity, bindings-parity]
|
||
if: ${{ always() }}
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- name: Assert every required dependency succeeded
|
||
run: |
|
||
set -euo pipefail
|
||
results='${{ toJSON(needs) }}'
|
||
echo "$results"
|
||
echo "$results" | python3 -c '
|
||
import json, sys
|
||
ns = json.load(sys.stdin)
|
||
# Strict: every required dependency MUST be "success". A skipped or
|
||
# cancelled required gate must RED this aggregator, not pass it (an
|
||
# all-required gate can never treat non-success as acceptable, or the
|
||
# SSOT invariant it guards is unenforced).
|
||
bad = [(k, v.get("result")) for k, v in ns.items()
|
||
if v.get("result") != "success"]
|
||
if bad:
|
||
print("FAIL: jobs not green:", file=sys.stderr)
|
||
for k, r in bad:
|
||
print(f" - {k}: {r}", file=sys.stderr)
|
||
sys.exit(1)
|
||
print(f"OK: all {len(ns)} required jobs succeeded")
|
||
'
|