chore(sync): main → develop tras los bumps de Dependabot #1544
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI/CD | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| on: | |
| push: | |
| # GT-574: `develop` is where every change actually lands, and until now this | |
| # pipeline did not run on a push to it — so the engine suites and the | |
| # boundary guard only ever saw a change once it was already on `main`, or in | |
| # a PR that nothing required. The deploy/docker/publish jobs stay ref-guarded | |
| # to main/tags below, so adding develop here runs tests and gates only. | |
| branches: [main, develop] | |
| tags: ['v*'] | |
| # GT-324/W-Enforce: run the engine test + boundary-guard jobs on every PR to | |
| # main/develop. The deploy/docker/publish jobs below stay ref-guarded | |
| # (if: github.ref == main || tags), so PRs exercise tests + the | |
| # STATELESS_CORE_REPOSITORY_BAN guard without triggering any deploy. | |
| pull_request: | |
| branches: [main, develop] | |
| workflow_dispatch: | |
| jobs: | |
| test: | |
| name: Test | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-tags: true | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: '20' | |
| - name: Install dependencies | |
| run: npm ci | |
| # sdk/cli imports @beyondnet/evolith-* subpaths that resolve to dist, and its | |
| # integration tests spawn the built dist/main.js — build the chain AND the | |
| # CLI before running the tests. | |
| - name: Build workspace dependencies + CLI | |
| run: | | |
| npm run build --workspace @beyondnet/evolith-contracts | |
| npm run build --workspace @beyondnet/evolith-core-domain | |
| npm run build --workspace @beyondnet/evolith-agent-runtime | |
| npm run build --workspace @beyondnet/evolith-sdk | |
| npm run build --workspace @beyondnet/evolith-core | |
| npm run build --workspace @beyondnet/evolith-infra-providers | |
| npm run build --workspace @beyondnet/evolith-mcp | |
| npm run build --workspace src/sdk/cli | |
| # CD gate = the evolith-cli unit suite (fast, deterministic). The full e2e | |
| # (env-sensitive: spawns servers, loads rulesets) is covered by the | |
| # dedicated sdk-cli-ci.yml e2e job + the per-flow E2E playbooks, not here. | |
| - name: Run unit tests | |
| run: npm run test:unit | |
| working-directory: src/sdk/cli | |
| # GT-592 — retrieval quality is a MEASUREMENT, gated here so a ranking | |
| # change cannot be settled by anecdote. | |
| # | |
| # The harness replays a dense baseline recorded once against the real | |
| # Qwen3-Embedding-0.6B sidecar (rag-eval-record.mjs), because a runner | |
| # cannot host a 0.6B-parameter model — but everything that lives in this | |
| # repository (tokenizer, BM25, RRF) is executed for real, from the built | |
| # agent-runtime dist. The recorded baseline is fingerprinted against the | |
| # frozen corpus, so a corpus edit without a re-record fails loudly rather | |
| # than reporting a stale comparison. | |
| # | |
| # The self-tests run FIRST and on purpose: they deliberately damage the | |
| # ranking three ways and assert the gate goes red, so a gate nobody can | |
| # breach is itself a build failure. | |
| - name: Retrieval eval self-tests (the gate must be able to fail) | |
| run: node --test .harness/scripts/ci/rag-eval.test.mjs | |
| - name: Retrieval eval gate (hybrid vs dense-only, fixed query set) | |
| run: node .harness/scripts/ci/rag-eval.mjs | |
| test-core-domain: | |
| name: Test core-domain | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| cache-dependency-path: package-lock.json | |
| - name: Install dependencies | |
| run: npm ci | |
| # Architecture boundary guard: enforces the core-domain inner-layer | |
| # hierarchy on VALUE imports AND the stateless-Core *Repository ban | |
| # (GT-377/ADR-0101 — product/initiative/evidence/decision are context, not | |
| # entities). Parses the import graph only (no type-aware services / built | |
| # deps needed). | |
| - name: Run core-domain architecture boundary guard | |
| run: npm run lint:boundaries --workspace @beyondnet/evolith-core-domain | |
| # GT-382 follow-up: the core-domain OPA integration test loads the real | |
| # compiled policy.wasm. Build it (pinned opa toolchain) so the test runs | |
| # end-to-end; it self-skips if the artifact is absent. | |
| - name: Compile OPA policy to WASM | |
| run: npm run build:policy | |
| - name: Run core-domain tests with coverage | |
| run: npm run test:cov --workspace @beyondnet/evolith-core-domain | |
| # GT-326: end-to-end governance flow (phase → gate → artifact → verdict) | |
| # against a real tmpdir satellite, exercising Core's Tracker/agent | |
| # integration contract (events, webhook dispatch, audit) in CI. | |
| - name: Run core-domain governance E2E (GT-326) | |
| run: npm run test:e2e --workspace @beyondnet/evolith-core-domain | |
| test-core: | |
| name: Test core | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| cache-dependency-path: package-lock.json | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Run core barrel contract tests | |
| run: npm test --workspace @beyondnet/evolith-core | |
| test-mcp-server: | |
| name: Test mcp-server | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| cache-dependency-path: package-lock.json | |
| - name: Install dependencies | |
| run: npm ci | |
| # Architecture boundary guard: enforces the mcp-server layer hierarchy | |
| # (domain → application → adapters) on VALUE imports. Parses the import | |
| # graph only (no type-aware services), so it needs no built workspace deps. | |
| - name: Run mcp-server architecture boundary guard | |
| run: npm run lint:boundaries --workspace @beyondnet/evolith-mcp | |
| - name: Build workspace dependencies | |
| run: | | |
| npm run build --workspace @beyondnet/evolith-contracts | |
| npm run build --workspace @beyondnet/evolith-core-domain | |
| npm run build --workspace @beyondnet/evolith-agent-runtime | |
| npm run build --workspace @beyondnet/evolith-sdk | |
| npm run build --workspace @beyondnet/evolith-core | |
| npm run build --workspace @beyondnet/evolith-infra-providers | |
| # GT-602 criterion 2: the three rego tool sets are DERIVED from | |
| # AbacEvaluator.toolProjection(), not hand-maintained beside it. Two copies | |
| # of one fact is what let fifteen tools stay FORBIDDEN in production. The | |
| # projection is the runtime's own classifyTool, so this cannot drift from | |
| # what actually decides. | |
| - name: ABAC rego tool sets match the runtime projection | |
| run: node .harness/scripts/generate-abac-tool-sets.mjs --check | |
| # GT-583 criteria 2 and 3: the capability manifest's per-operation | |
| # `inputSchema`/`outputSchema`, and the CLI's `TOOL_SCHEMAS`, are DERIVED | |
| # from ToolRegistryService.operationProjection() — the registry that | |
| # answers `tools/list` describing itself. Three hand-maintained copies of | |
| # one contract is what shipped a THREE-entry CLI catalog for a fifty-tool | |
| # MCP surface, under names no tool has ever had. | |
| - name: Capability operation schemas match the runtime projection | |
| run: node .harness/scripts/generate-capability-operations.mjs --check | |
| # GT-602: compile the rego to the `policy.wasm` the dispatcher actually loads. | |
| # `abac-rego-parity.spec.ts` now compiles it ITSELF when it is missing or stale, | |
| # so this step is no longer what makes the parity check run. It stays because it | |
| # fails fast and unambiguously on a toolchain problem (no opa binary, no network) | |
| # instead of surfacing it as one red test inside the whole jest suite. | |
| - name: Compile OPA policy bundle (policy.wasm) | |
| run: npm run build:policy | |
| # GT-602 criteria 1 and 3 are enforced HERE. `abac-rego-parity.spec.ts` loads the | |
| # COMPILED policy.wasm — the artifact that decides at dispatch — evaluates it over | |
| # every tool name the DI graph registers, and requires ALLOW for an `architect` in | |
| # `production`. A tool that exists in the TypeScript registry and not in the | |
| # compiled policy comes back ABAC-03 and turns this step red. That block never | |
| # skips: a missing bundle is compiled, and a bundle that cannot be compiled is a | |
| # failure. `Test mcp-server` is a required check on `main`. | |
| - name: Run mcp-server tests with coverage | |
| run: npm run test:cov --workspace @beyondnet/evolith-mcp | |
| # GT-330: MCP server dedicated E2E (spawns the live MCP HTTP server) | |
| - name: Build mcp-server + run E2E | |
| run: | | |
| npm run build --workspace @beyondnet/evolith-mcp | |
| npm run --workspace src/packages/mcp-server test:e2e | |
| test-core-api: | |
| name: Test core-api | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| cache-dependency-path: package-lock.json | |
| - name: Install dependencies | |
| run: npm ci | |
| # Architecture boundary guard: enforces the core-api layer hierarchy | |
| # (application → infrastructure → presentation; openapi cross-cutting) on | |
| # VALUE imports. Parses the import graph only (no type-aware services), so | |
| # it needs no built workspace deps. | |
| - name: Run core-api architecture boundary guard | |
| run: npm run lint:boundaries --workspace core-api | |
| - name: Build workspace dependencies | |
| run: | | |
| npm run build --workspace @beyondnet/evolith-contracts | |
| npm run build --workspace @beyondnet/evolith-core-domain | |
| npm run build --workspace @beyondnet/evolith-infra-providers | |
| - name: Run core-api tests | |
| run: npm test --workspace core-api | |
| # GT-330: core-api dedicated E2E (real HTTP surface — health/metrics/v1) | |
| - name: Run core-api E2E | |
| run: npm run --workspace core-api test:e2e | |
| # W-Coverage: the SDK REST/MCP clients ship to consumers but ran in NO CI job. | |
| test-sdk-client: | |
| name: Test sdk-client | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| cache-dependency-path: package-lock.json | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Build workspace dependencies | |
| run: | | |
| npm run build --workspace @beyondnet/evolith-contracts | |
| npm run build --workspace @beyondnet/evolith-core-domain | |
| npm run build --workspace @beyondnet/evolith-agent-runtime | |
| - name: Run sdk-client tests with coverage | |
| run: npm run test:cov --workspace @beyondnet/evolith-sdk | |
| # GT-565: this suite existed, passed, and ran in NO workflow. A contract test CI | |
| # never executes is the same false-signal class it exists to catch -- it is the | |
| # mechanism that would have caught the sdk-client type drift, and it was | |
| # disconnected the whole time the drift went unnoticed. | |
| test-contract: | |
| name: Test contract (SDK wire fidelity) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| cache-dependency-path: package-lock.json | |
| - name: Install dependencies | |
| run: npm ci | |
| # The runtime layer boots core-api, and the compile-time layer resolves the | |
| # SDK against the BUILT core-domain, so both must exist before the run. | |
| - name: Build workspace dependencies | |
| run: | | |
| # ORDER MATTERS. The contract suite compiles core-api's module chain and | |
| # the CLI's commands, so it needs every package those import, built in | |
| # dependency order: core must precede mcp (mcp imports it), and | |
| # core-domain must precede everything (its subpath exports, e.g. | |
| # `common/request-context`, only resolve once built). | |
| # | |
| # Derived empirically, not guessed: every dist was moved aside to | |
| # reproduce a clean CI checkout, then packages were added one at a time | |
| # until `npm run test:contract` went 43/43. The previous list stopped at | |
| # core-domain + agent-runtime, which is why this job had never once | |
| # passed since it was wired. | |
| npm run build --workspace @beyondnet/evolith-contracts | |
| npm run build --workspace @beyondnet/evolith-contracts | |
| npm run build --workspace @beyondnet/evolith-core-domain | |
| npm run build --workspace @beyondnet/evolith-infra-providers | |
| npm run build --workspace @beyondnet/evolith-core | |
| npm run build --workspace @beyondnet/evolith-agent-runtime | |
| # sdk BEFORE mcp: mcp's agent.tools.ts imports @beyondnet/evolith-sdk. | |
| npm run build --workspace @beyondnet/evolith-sdk | |
| npm run build --workspace @beyondnet/evolith-mcp | |
| - name: Run contract tests | |
| run: npm run test:contract | |
| # W-Coverage: the filesystem/logger/config/github adapters ran in NO CI job. | |
| test-infra-providers: | |
| name: Test infra-providers | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| cache-dependency-path: package-lock.json | |
| - name: Install dependencies | |
| run: npm ci | |
| # infra-providers depends on @beyondnet/evolith-core-domain (resolved from dist). | |
| - name: Build workspace dependencies | |
| run: | | |
| npm run build --workspace @beyondnet/evolith-contracts | |
| npm run build --workspace @beyondnet/evolith-core-domain | |
| - name: Run infra-providers tests with coverage | |
| run: npm run test:cov --workspace @beyondnet/evolith-infra-providers | |
| # GT-578 follow-up: three workspaces had specs and no job, so ~340 tests ran | |
| # nowhere. That is not hypothetical — `@beyondnet/evolith-contracts` carried a | |
| # spec asserting two deliberately different path conventions were identical, so | |
| # it could never pass, and it sat red and unseen because nothing executed it. | |
| # Two of the three ship to npm; agent-runtime's suite includes the guard that | |
| # freezes the published package's public surface. | |
| test-agent-runtime: | |
| name: Test agent-runtime | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| cache-dependency-path: package-lock.json | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Build workspace dependencies | |
| run: | | |
| npm run build --workspace @beyondnet/evolith-contracts | |
| npm run build --workspace @beyondnet/evolith-core-domain | |
| - name: Run agent-runtime tests | |
| run: npm run test --workspace @beyondnet/evolith-agent-runtime | |
| test-agent-runtime-api: | |
| name: Test agent-runtime-api | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| cache-dependency-path: package-lock.json | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Build workspace dependencies | |
| run: | | |
| npm run build --workspace @beyondnet/evolith-contracts | |
| npm run build --workspace @beyondnet/evolith-core-domain | |
| npm run build --workspace @beyondnet/evolith-agent-runtime | |
| - name: Run agent-runtime-api tests | |
| run: npm run test --workspace agent-runtime-api | |
| test-contracts: | |
| name: Test contracts | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| cache-dependency-path: package-lock.json | |
| - name: Install dependencies | |
| run: npm ci | |
| # `capability-contract.parity.spec.ts` imports | |
| # `@beyondnet/evolith-core-domain/capabilities/capabilities-manifest`, a | |
| # subpath that resolves through dist. Without this the suite passes locally | |
| # (where a dist already exists from an earlier build) and fails in CI on a | |
| # clean checkout — which is exactly what it did the first time this job ran. | |
| - name: Build workspace dependencies | |
| run: | | |
| npm run build --workspace @beyondnet/evolith-contracts | |
| npm run build --workspace @beyondnet/evolith-core-domain | |
| - name: Run contracts tests | |
| run: npm run test --workspace @beyondnet/evolith-contracts | |
| # GT-578 — the two anti-false-green mechanisms, run on every push and PR. | |
| # | |
| # They live here rather than in docs.yml's `Validate documentation` job on | |
| # purpose: that job is a required check on main, and wiring a brand-new | |
| # reporting-mode guard into a required check is how a reporting-mode guard | |
| # gets deleted. This job is not required yet; promoting it is a branch- | |
| # protection change, made once the evidence corpus is migrated (below). | |
| governance-guards: | |
| name: Governance guards (GT-578) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| cache-dependency-path: package-lock.json | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Install evidence-command shell dependencies | |
| run: sudo apt-get update && sudo apt-get install -y ripgrep | |
| # FIRST, and deliberately. Every step in this job runs under `bash -e`, so the | |
| # first red step skips every step after it. On run 30629887881 a network- | |
| # dependent guard went red and this block — the layer that checks the guards | |
| # THEMSELVES — never ran, silently, on the one run where a guard's behaviour | |
| # was in question. A self-test that only executes once every guard has already | |
| # passed can confirm a green run and nothing else. | |
| # | |
| # It runs first rather than merely earlier because it needs nothing: no build, | |
| # no registry, no `gh`. Its fixtures are in-repo and its subjects are the guard | |
| # modules. Anything it depends on would be another way for it not to run. | |
| - name: Self-tests for the governance guards | |
| run: | | |
| node --test .harness/scripts/ci/42-validate-guard-denominators.test.mjs | |
| node --test .harness/scripts/ci/48-validate-security-publish-lag.test.mjs | |
| node --test .harness/scripts/ci/49-validate-gap-id-allocation.test.mjs | |
| node --test .harness/scripts/ci/50-validate-gap-claim.test.mjs | |
| node --test .harness/scripts/ci/41-validate-evidence-commands.test.mjs | |
| node --test .harness/scripts/ci/43-validate-guard-negative-fixtures.test.mjs | |
| node --test .harness/scripts/ci/44-validate-adr-implementation-status.test.mjs | |
| node --test .harness/scripts/ci/34-check-skill-registry-parity.test.mjs | |
| node --test .harness/scripts/ci/45-validate-port-inventory-honesty.test.mjs | |
| # Everything below was referenced by NO workflow. Enumerating `.harness` for | |
| # `*.test.mjs` found 51 self-tests and 32 of them ran nowhere — the same defect | |
| # PR #322 closed for one file, at twenty-six times the size. Five were red and | |
| # had been red silently: two audit scripts whose path literals lost the `src/` | |
| # prefix in the refactor, a report writer that died on an unhandled ENOENT off | |
| # the real tree, a fixture whose diff header predated a move, and a guard test | |
| # that GT-556/557 had quietly detached from its own fixture. | |
| # | |
| # A note on what these prove. Most of the GUARDS below also run in no workflow; | |
| # wiring the self-test asserts the guard still behaves, not that anything calls | |
| # it. That second hole is real and deliberately left open here — switching on | |
| # twenty-odd unrun guards at once is a different change with a different blast | |
| # radius, and it should not ride in on a test-wiring commit. | |
| node --test .harness/scripts/ci/07-generate-inventories.test.mjs | |
| node --test .harness/scripts/ci/19-validate-rest-versioning.test.mjs | |
| node --test .harness/scripts/ci/40-validate-path-literals.test.mjs | |
| node --test .harness/scripts/ci/drift-audit.test.mjs | |
| node --test .harness/scripts/ci/parity-gate.test.mjs | |
| node --test .harness/scripts/lib/guard-classification.test.mjs | |
| node --test .harness/scripts/lib/scope.test.mjs | |
| node --test .harness/scripts/ci/agentic/review-input.test.mjs | |
| node --test .harness/scripts/ci/agentic/review-provider.test.mjs | |
| node --test .harness/scripts/ci/agentic/review-result.test.mjs | |
| node --test .harness/scripts/cleanup-temp-files.test.mjs | |
| node --test .harness/scripts/validate-contract-conformance.test.mjs | |
| node --test .harness/scripts/board/atdm-principal.test.mjs | |
| node --test .harness/scripts/board/debt-economics.test.mjs | |
| node --test .harness/scripts/board/new-row-economics-guard.test.mjs | |
| node --test .harness/scripts/run-evolith-intelligent-data-audit.test.mjs | |
| node --test .harness/scripts/run-evolith-sdlc-interface-audit.test.mjs | |
| # The four `rag-*` suites, which are NOT the special case they look like. Each | |
| # file states in its own header that the DB client and the sidecar `fetch` are | |
| # stubbed and "the network is never touched", and all four pass offline with no | |
| # skips — no model endpoint, no pgvector, nothing to provision. The live | |
| # integration against a real endpoint is a separate thing that does not exist | |
| # here; these are unit tests over the adapter and the chunker. | |
| node --test .harness/scripts/ci/rag-sync.test.mjs | |
| node --test .harness/scripts/ci/rag-pgvector.test.mjs | |
| node --test .harness/scripts/ci/rag-embed-qwen3.test.mjs | |
| node --test .harness/scripts/ci/rag-embed-integration.test.mjs | |
| # The dead-reference budget below was measured on a BUILT tree. A handful of | |
| # recorded commands legitimately name build outputs (`dist/main.js`), which | |
| # a clean checkout reports as dead — so without this the ratchet drifts by | |
| # the number of dist references and fails for a reason that has nothing to | |
| # do with the corpus. Building first makes the count reproducible. | |
| - name: Build workspace dependencies | |
| run: | | |
| npm run build --workspace @beyondnet/evolith-contracts | |
| npm run build --workspace @beyondnet/evolith-core-domain | |
| npm run build --workspace @beyondnet/evolith-infra-providers | |
| npm run build --workspace @beyondnet/evolith-agent-runtime | |
| # GT-642: the cycle guard RUNS GT-589's extractor rather than owning a second | |
| # one, so the extractor has to be built or the guard has nothing to run. It | |
| # fails loudly in that case instead of reporting "no cycles found". | |
| npm run build --workspace @beyondnet/evolith-repo-facts | |
| # STRICT. Every script under .harness/scripts/ci must either refuse a | |
| # zero-element scan or be registered, with a reason, as something that is | |
| # not a corpus scan. A new guard that can pass vacuously fails here, which | |
| # is the whole point: the sweep is worthless if the 34th guard starts | |
| # unprotected. | |
| - name: Every CI guard publishes a denominator | |
| run: node .harness/scripts/ci/42-validate-guard-denominators.mjs --verbose | |
| # OBSERVED, not declared. 42 checks that each guard CAN refuse an empty | |
| # scan; this one points every scanning guard at a deliberately empty | |
| # repo-shaped tree and watches it go red. A guard nobody has ever seen fail | |
| # is the defect this whole sweep is about, and the distinction between | |
| # "has a vacuity check" and "the vacuity check fires" is where the last | |
| # four false greens in this repository came from. | |
| - name: Every scanning guard has been SEEN failing | |
| run: node .harness/scripts/ci/43-validate-guard-negative-fixtures.mjs | |
| # GT-607: seven ADRs were Accepted with no implementing code. The guard | |
| # falsifies an author DECLARATION rather than guessing at implementing | |
| # code — a grep-based signal was tried against this corpus and rejected, | |
| # with the reasoning in the guard header. Report mode: 212 Accepted ADRs | |
| # still declare nothing and are baselined; `--strict` is the switch when | |
| # that baseline reaches zero. | |
| - name: ADR implementation-status declarations resolve | |
| run: node .harness/scripts/ci/44-validate-adr-implementation-status.mjs | |
| # GT-424: the three skill registries. R3 measures the EFFECTIVE routing | |
| # surface and audits GT-608's derivation rather than trusting it — a | |
| # synthesis narrowed by one extra conjunct silently unreaches capabilities | |
| # and would otherwise keep this green. | |
| - name: Skill registries agree in both directions | |
| run: node .harness/scripts/ci/34-check-skill-registry-parity.mjs | |
| # GT-621: a diagram published "12 hexagonal ports · 30 adapters". The | |
| # numbers were stale AND the sentence read as delivered capability when it | |
| # described a declared surface — 11 of 19 ports are reachable from the | |
| # runtime; the rest are seams with adapters and no consumer. The hot path | |
| # is DERIVED from `AgentRuntimeDeps`, so it cannot rot the way the | |
| # hand-typed count did. | |
| - name: A port inventory does not read as a capability claim | |
| run: node .harness/scripts/ci/45-validate-port-inventory-honesty.mjs --verbose | |
| # GT-644: a policy calling an OPA builtin the shipped wasm runtime cannot | |
| # execute passes `opa test` and `opa build`, then throws "not implemented: | |
| # built-in function N" at evaluation time — which OpaEvaluator turns into | |
| # "OPA engine error — enforcement blocked" for EVERY rule in the run. One | |
| # `time.parse_rfc3339_ns` therefore fails the whole OPA half of the gate with | |
| # a message that reads like an infrastructure hiccup. The authority is the | |
| # COMPILED bundle's `builtins()` table, so the bundle is built first; | |
| # `--require-wasm` makes a bundle that could not be built fatal rather than a | |
| # source scan wearing a green tick. | |
| # | |
| # Placed HERE, ahead of the three checks below, on purpose: those three ask | |
| # the network (npm, the base branch, the pull-request list) and this one is a | |
| # deterministic read of this checkout. `bash -e` stops the job at the first | |
| # failure, so a live-GitHub answer standing in front of a repository check | |
| # means the repository check never runs at all — which is what happened on | |
| # run 30629887881, where an unrelated contested claim skipped both steps. | |
| - name: Compile the OPA bundle the builtin check reads | |
| run: npm run build:policy | |
| - name: Policies only call builtins the wasm runtime implements | |
| run: | | |
| node .harness/scripts/ci/55-validate-wasm-builtin-support.mjs --require-wasm --verbose | |
| node --test .harness/scripts/ci/55-validate-wasm-builtin-support.test.mjs | |
| # GT-624: the 2026-07-23 security wave sat unpublished until 2026-07-27 | |
| # while SECURITY.md declared the 1.1.x line "actively patched". Nothing | |
| # detected it; an audit did. This asks the registry what is published, finds | |
| # the commit where each package's published version was SET, and fails on any | |
| # commit after it whose type or scope marks it as security. It needs the | |
| # network — a registry answer is the only honest source for "published", as | |
| # the newest v* tag here is v1.1.0 while npm serves 1.2.2. | |
| - name: No security fix is committed but unpublished | |
| run: node .harness/scripts/ci/48-validate-security-publish-lag.mjs --verbose | |
| # GT-642: two runtime import cycles reached a 346-module package unseen and were | |
| # found by hand (GT-641), because nothing looks — lint:boundaries enforces layer | |
| # DIRECTION and not acyclicity, and tsc compiles a require cycle silently. This | |
| # runs GT-589's extractor over EVERY workspace package and prints the per-package | |
| # table on every run, zeros included: a guard whose green run reports nothing is | |
| # indistinguishable from one that cannot run. Cycles that close only through an | |
| # `import type` edge are listed and are deliberately NOT fatal. | |
| - name: No runtime import cycle in any workspace package | |
| run: node .harness/scripts/ci/54-validate-import-cycles.mjs --verbose | |
| # GT-647: a runner stage lists the workspace packages it copies BY HAND, and | |
| # node_modules/@beyondnet/* are symlinks into src/packages/* — so a package | |
| # left off the list dangles and the container dies at its first require. On | |
| # 2026-07-31 GT-604 added one import to infra-providers and silently broke | |
| # three images (core-api, mcp-server, cli), which nothing detected: the build | |
| # is green, the push is green, and the only symptom is a chaos-drill timeout. | |
| # The closure is COMPUTED transitively here, because at direct-dependency | |
| # depth every one of those Dockerfiles was already complete. | |
| - name: Every image ships its full workspace closure | |
| run: | | |
| node .harness/scripts/ci/56-validate-docker-workspace-closure.mjs --verbose | |
| node --test .harness/scripts/ci/56-validate-docker-workspace-closure.test.mjs | |
| # GT-435 — nothing has ever run in production, so nothing has ever pulled these images and | |
| # a green CI proves nothing about them. On 2026-08-02 all three charts named a tag no | |
| # workflow produces; one of them (agent-runtime 0.1.0) had no matching git tag at all, so | |
| # it was unproducible by any path. The day the server exists is not the day to find out. | |
| - name: Charts request images this repository can produce | |
| run: node .harness/scripts/ci/58-validate-deployable-images.mjs | |
| # GT-650 / ADR-0125 — the artifact registry is the accepted single declaration, and the gate | |
| # corpus is still hand-maintained while the migration lands. For as long as both exist, the | |
| # only thing making that intermediate state safe is that they are checked to agree. This | |
| # guard dies with `57` when the gates derive from the registry. | |
| - name: The artifact registry agrees with the gate corpus | |
| run: node .harness/scripts/ci/59-validate-artifact-registry.mjs | |
| # GT-650 — the served gate corpus is now GENERATED from the registry and the gates, so the | |
| # two copies cannot disagree by construction. `57-validate-gate-corpora-parity` compared them | |
| # and is deleted with this change: a guard kept past its cause becomes noise, and noise | |
| # trains people to skip red. | |
| - name: The served gate corpus is current | |
| run: node .harness/scripts/generate-phase-gates-rules.mjs --check | |
| # GT-591: the OPA pin sat at 0.65.0 for two years and nothing said so, while a | |
| # SECOND spelling of it lived in compile-opa-wasm.mjs's download URL — so the | |
| # policies could be tested by one compiler and the enforcing wasm built by | |
| # another, both green. Now on the v1 line, where `if`/`contains` are mandatory, | |
| # so the pin and the Rego dialect are one fact: the guard also fails on any | |
| # `.rego` that stops declaring `import rego.v1`. | |
| - name: One OPA pin, on the v1 line, and no second spelling of it | |
| run: node .harness/scripts/ci/53-validate-opa-pin.mjs --verbose | |
| # GT-638: a gap id is chosen by reading the highest GT-* on whichever branch | |
| # you are on, so two parallel sessions allocate the same number and find out | |
| # at merge time. `8449af3d` had to renumber GT-634 -> GT-637 for exactly this. | |
| # The comparison needs the BASE branch, which a shallow checkout does not | |
| # have — fetch it explicitly rather than letting the guard fail for the wrong | |
| # reason. | |
| - name: One gap id, one gap | |
| run: | | |
| git fetch --no-tags --depth=1 origin "${{ github.base_ref || github.event.repository.default_branch }}" | |
| node .harness/scripts/ci/49-validate-gap-id-allocation.mjs --verbose | |
| # GT-639: a board row carries a STATUS but not who is working it or where, so | |
| # two sessions can both read `PENDING` and both start. On 2026-07-30 the same | |
| # work was done twice, three times over. The claim set is DERIVED from open | |
| # pull requests — never hand-written, which would go stale in the direction | |
| # that matters — and an id claimed by two open PRs fails here, naming both. | |
| # | |
| # A claim is DECLARED: an id in the title, in the branch name, or in the body | |
| # after a `Closes`/`Advances`/`Claims` marker. Reading it from any `GT-*` in | |
| # the prose made every cross-reference a claim, and the bodies here cite their | |
| # neighbours constantly — run 30630343658 failed three times over, none of them | |
| # real, and the fix each author reached for was to delete the sentence. | |
| # | |
| # A claim is ALSO what the DIFF did (GT-645): a board row whose status it | |
| # moves, or a closure-evidence record it writes. Prose alone missed the other | |
| # direction — run 30633517559 read ONE claim off #326 and exited green on a | |
| # pull request that had flipped eight rows. `GH_TOKEN` now covers a `gh pr | |
| # diff` per open pull request as well as the list; a diff it cannot read is a | |
| # hard failure, never a run that quietly checked the prose alone. | |
| - name: One gap, one claim | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: node .harness/scripts/ci/50-validate-gap-claim.mjs | |
| # GT-578 AC3: this is now a governed execution gate, not a report. It | |
| # resolves every validationCommand recorded in the board's closure | |
| # evidence and executes the read-only subset. Strict mode fails on dead | |
| # referents, non-zero executions, inconclusive search exits, vacuous scans, | |
| # or any command that dirties the checkout. | |
| # | |
| # The remaining budget of 17 is not live debt: it is the clean-checkout | |
| # basis for generated/gitignored referents (`dist/`, `node_modules/`, | |
| # `.harness/bin/`) that are materialized by build/download steps. Lower it | |
| # only from the number this job prints; at 0, delete `--max-dead`. | |
| - name: Execute the board's recorded validationCommands (strict) | |
| run: node .harness/scripts/ci/41-validate-evidence-commands.mjs --execute --strict --max-dead 17 --verbose | |
| # RETIRED 2026-07-27 (GT-570): the `publish-npm` job lived here and published | |
| # ONLY `src/sdk/cli`, with `--tag beta` and no provenance, writing the token | |
| # into a .npmrc via `npm config set`. It could not publish the other seven | |
| # public packages at all — including @beyondnet/evolith-mcp, which carries the | |
| # 2026-07-23 security wave — which is why every version on the registry has no | |
| # dist.attestations. Publishing now lives in `npm-release.yml`: all eight | |
| # workspaces, in dependency order, with --provenance, idempotent against the | |
| # registry, and rehearsable via a dry run that defaults to on. | |
| docker-services: | |
| name: Build & Push Services (GHCR) | |
| needs: [test, test-core-domain, test-core, test-mcp-server, test-core-api, test-sdk-client, test-infra-providers] | |
| runs-on: ubuntu-latest | |
| if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') | |
| permissions: | |
| contents: read | |
| packages: write | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| # context is the repo root — both Dockerfiles COPY repo-root-relative paths | |
| - service: core-api | |
| context: . | |
| dockerfile: ./src/apps/core-api/Dockerfile | |
| image: evolith-core-api | |
| - service: mcp-server | |
| context: . | |
| dockerfile: ./src/packages/mcp-server/Dockerfile | |
| image: evolith-mcp | |
| # GT-437: agent-runtime-api had a Dockerfile but was never built/pushed | |
| # on main by this workflow (only by docker-images.yml on tags/dispatch). | |
| - service: agent-runtime | |
| context: . | |
| dockerfile: ./src/apps/agent-runtime-api/Dockerfile | |
| image: evolith-agent-runtime | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Login to GHCR | |
| uses: docker/login-action@v4 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Build and push ${{ matrix.service }} | |
| uses: docker/build-push-action@v5 | |
| with: | |
| context: ${{ matrix.context }} | |
| file: ${{ matrix.dockerfile }} | |
| push: true | |
| tags: | | |
| ghcr.io/${{ github.repository_owner }}/${{ matrix.image }}:latest | |
| ghcr.io/${{ github.repository_owner }}/${{ matrix.image }}:${{ github.sha }} | |
| # GT-324: deploy the freshly-pushed images to the Coolify runtime. | |
| # | |
| # El fail-soft cubre UN solo modo de fallo: secrets ausentes. Sin | |
| # COOLIFY_API_TOKEN o sin el deploy-hook del servicio, el step avisa y sale 0, | |
| # asi que el workflow es seguro de mergear antes de configurar CD. | |
| # | |
| # Con los secrets PUESTOS, este job falla duro y debe hacerlo: `curl --fail` | |
| # sale distinto de cero si el hook no responde, y eso significa que el | |
| # despliegue no ocurrio. El comentario anterior decia "never fails CD", que era | |
| # falso y ademas indeseable -- un step de despliegue que no puede fallar no | |
| # informa de nada. Si esto esta rojo, mira el runtime antes que el workflow. | |
| # | |
| # GT-567: APAGADO A PROPOSITO via `vars.VPS_DEPLOY_ENABLED`. El entorno | |
| # objetivo es hoy Docker + kind local (cluster `evolith-cluster`); el VPS se | |
| # retoma mas adelante. Sin este flag el job fallaba en CADA push a `main` | |
| # desde 2026-07-19 contra un host fuera de alcance, y como no es check | |
| # requerido nadie leia el rojo. | |
| # | |
| # Se apaga con una VARIABLE y no borrando los secrets a proposito: borrarlos | |
| # deja el apagado invisible --dentro de un mes el job simplemente saltaria y | |
| # nadie sabria si fue deliberado-- y sus valores solo se pueden reponer desde | |
| # el panel de Coolify, o sea con el VPS levantado. Aqui queda escrito el | |
| # porque, y reactivar es UN paso: `gh variable set VPS_DEPLOY_ENABLED --body true`. | |
| deploy: | |
| name: Deploy services (Coolify) | |
| needs: [docker-services] | |
| runs-on: ubuntu-latest | |
| if: github.ref == 'refs/heads/main' && vars.VPS_DEPLOY_ENABLED == 'true' | |
| steps: | |
| # GT-324 — HARDENED 2026-08-02. Each service used to check its own hook and, when unset, | |
| # warn and `exit 0`. That is right for "not configured yet" and WRONG for "half configured": | |
| # set the token, typo one hook, and CD reports success while that service never deploys — | |
| # and the only symptom is an old image serving traffic nobody is looking at. | |
| # | |
| # The two states are now separated. Nothing configured is a skip; PARTIALLY configured is a | |
| # failure that names how many of the four values are present. Same contract as the Tracker's | |
| # `images.yml`, so both repositories fail the same way. | |
| - name: Classify the deploy configuration | |
| id: cfg | |
| env: | |
| TOKEN: ${{ secrets.COOLIFY_API_TOKEN }} | |
| HOOK_CORE: ${{ secrets.COOLIFY_COREAPI_DEPLOY_HOOK }} | |
| HOOK_MCP: ${{ secrets.COOLIFY_MCP_DEPLOY_HOOK }} | |
| HOOK_AGENT: ${{ secrets.COOLIFY_AGENTRUNTIME_DEPLOY_HOOK }} | |
| run: | | |
| set=0; unset=0 | |
| for v in "$TOKEN" "$HOOK_CORE" "$HOOK_MCP" "$HOOK_AGENT"; do | |
| if [ -n "$v" ]; then set=$((set+1)); else unset=$((unset+1)); fi | |
| done | |
| if [ "$set" -eq 0 ]; then | |
| echo "::warning::Coolify is not configured (no token, no hooks). Skipping the deploy. Set COOLIFY_API_TOKEN and the three hooks to enable CD (GT-324)." | |
| echo "state=absent" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| if [ "$unset" -gt 0 ]; then | |
| echo "::error::Coolify is PARTIALLY configured: $set of 4 values present. A half-wired deploy reports success while some services never ship, so this fails instead of warning. Set all of COOLIFY_API_TOKEN, COOLIFY_COREAPI_DEPLOY_HOOK, COOLIFY_MCP_DEPLOY_HOOK and COOLIFY_AGENTRUNTIME_DEPLOY_HOOK, or unset them all." | |
| exit 1 | |
| fi | |
| echo "state=complete" >> "$GITHUB_OUTPUT" | |
| - name: Trigger the deploys | |
| if: steps.cfg.outputs.state == 'complete' | |
| env: | |
| TOKEN: ${{ secrets.COOLIFY_API_TOKEN }} | |
| HOOK_CORE: ${{ secrets.COOLIFY_COREAPI_DEPLOY_HOOK }} | |
| HOOK_MCP: ${{ secrets.COOLIFY_MCP_DEPLOY_HOOK }} | |
| HOOK_AGENT: ${{ secrets.COOLIFY_AGENTRUNTIME_DEPLOY_HOOK }} | |
| run: | | |
| failed=0 | |
| trigger() { | |
| # `--fail` matters: without it curl exits 0 on a 4xx and a REJECTED hook reads as a | |
| # successful deploy. | |
| if curl --fail --silent --show-error -X GET "$2" -H "Authorization: Bearer $TOKEN" -o /dev/null; then | |
| echo "$1 deploy triggered" | |
| else | |
| echo "::error::$1 deploy hook did not accept the request" | |
| failed=1 | |
| fi | |
| } | |
| # All three are attempted before failing, so one bad hook does not hide the state of the | |
| # other two. | |
| trigger core-api "$HOOK_CORE" | |
| trigger mcp-server "$HOOK_MCP" | |
| trigger agent-runtime "$HOOK_AGENT" | |
| exit $failed | |
| # Triggering is not deploying: Coolify works asynchronously, so a green step means the | |
| # REQUEST was accepted, never that the new image is serving. GT-448 asks for a deploy | |
| # evidenced by a recorded run against the live service, and this job cannot provide it. | |
| - name: What this job did and did not prove | |
| if: steps.cfg.outputs.state == 'complete' | |
| run: | | |
| { | |
| echo '### Deploy triggered' | |
| echo '' | |
| echo 'Coolify accepted the webhook for all three services at `${{ github.sha }}`.' | |
| echo '' | |
| echo '> This proves the REQUEST was accepted. It does not prove the new image is' | |
| echo '> serving traffic. GT-448 asks for a recorded run against the live service.' | |
| } >> "$GITHUB_STEP_SUMMARY" |