Skip to content

Commit 70423f9

Browse files
dmealingclaude
andauthored
ci: make check workflows thin wrappers over scripts/ci-local.sh (#396)
* ci: make hygiene, conformance and integration-tests thin wrappers over ci-local.sh The three hosted check workflows carried their own step bodies, duplicating what scripts/ci-local.sh runs, so the two definitions could drift. Each job now checks out, installs its lane's toolchain and calls the script, following local-ci.yml's shape. Script additions, all opt-in so existing modes list identical steps: - --only leak-scan runs the leak scan alone (hygiene.yml's PR gate); MO_CI_LEAK_BASE points it at the PR's base branch. - --no-integration / --integration-only split a lane into its checks and its docker/Postgres half, so conformance.yml and integration-tests.yml each run their half of the same lanes. - MO_CI_JACOCO=1 keeps JaCoCo on in the Java reactor, preserving the instrumented second environment conformance.yml's nightly exists for. - gate_conf_csharp re-runs ApiDocsCrossPort after Cli.Tests builds the CLI; the whole-project run before it soft-skips on a clean checkout, which the workflow's explicit ordering used to cover. Jobs regroup onto the script's lanes: typecheck and completeness fold into ts-fast, conformance-kotlin into java-fast, fixture-lint and the drift jobs into gates, migrate-ts-pg into ts-slow. leak-scan keeps its job name (the required status). Triggers are unchanged. * ci: address pre-gate review — stale sidecar comment, Kotlin in validation README, --no-integration is not a skip * no-mistakes(review): fix stale Actions-disabled prose in CI docs * docs(agents): ci-local.sh defines the checks; workflows call it Replace 'mirrors the hosted lanes' — backwards after the thin-wrapper inversion — with the single-definition fact the script header and CONTRIBUTING.md now carry. Co-Authored-By: Claude Code <noreply@anthropic.com> --------- Co-authored-by: Claude Code <noreply@anthropic.com>
1 parent b136109 commit 70423f9

13 files changed

Lines changed: 219 additions & 382 deletions

File tree

‎.githooks/pre-push‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ echo "pre-push: TypeScript change detected — running the build + typecheck gat
7676
echo " (skip in an emergency with: git push --no-verify)" >&2
7777

7878
# typecheck resolves cross-package types through each dep's dist/, so build first —
79-
# exactly what .github/workflows/conformance.yml does before `typecheck`.
79+
# exactly what scripts/ci-local.sh's ts-fast lane does before `typecheck`.
8080
if ! ( cd "$ROOT" && bun run --filter '*' build ) >/tmp/metaobjects-prepush-build.log 2>&1; then
8181
echo "" >&2
8282
echo " ✖ pre-push BLOCKED: workspace build failed. Last lines:" >&2

‎.github/workflows/conformance.yml‎

Lines changed: 49 additions & 249 deletions
Large diffs are not rendered by default.

‎.github/workflows/hygiene.yml‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ name: hygiene
33
# Public-repo leak gate: scans a PR's added lines for absolute local paths and
44
# private/other-project names (structural patterns; the private denylist is local-only).
55
# The scanner lives in .githooks/leak-scan.sh so its pattern literals don't self-trip.
6+
#
7+
# THIN WRAPPER: scripts/ci-local.sh is the single definition of this check. This
8+
# workflow only checks out and calls it with `--only leak-scan`, pointing the scan at
9+
# the PR's base branch through MO_CI_LEAK_BASE. `leak-scan` is the status main's branch
10+
# protection requires, so the job name must not change.
611

712
on:
813
pull_request:
@@ -17,5 +22,8 @@ jobs:
1722
- uses: actions/checkout@v4
1823
with:
1924
fetch-depth: 0
25+
persist-credentials: false
2026
- name: Leak scan (added lines vs base)
21-
run: bash .githooks/leak-scan.sh "origin/${{ github.base_ref }}"
27+
env:
28+
MO_CI_LEAK_BASE: origin/${{ github.base_ref }}
29+
run: scripts/ci-local.sh --only leak-scan --strict-toolchains

‎.github/workflows/integration-tests.yml‎

Lines changed: 54 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ name: integration-tests
22

33
# Enforces the persistence + api-contract conformance corpora — the Docker /
44
# Testcontainers suites that are NOT in the default `mvn test` / `dotnet test` /
5-
# `bun test` path — against a real Postgres for all five ports. Per-language jobs
6-
# run in parallel; any port red blocks the gate.
5+
# `bun test` path — against a real Postgres for all five ports, plus the migrate-ts
6+
# and runtime-ts real-Postgres suites. Per-lane jobs run in parallel; any lane red
7+
# blocks the gate.
78
#
89
# COST: this 5-port Testcontainers matrix is EXPENSIVE, so it does NOT run on every
910
# push/PR. Triggers:
@@ -14,28 +15,45 @@ name: integration-tests
1415
# scripts/integration-test.sh <port>
1516
# The cheap public-repo SECURITY gate (hygiene / leak-scan) still runs on every PR.
1617
# Push-to-main coverage now comes from local-ci.yml on the self-hosted runner.
18+
#
19+
# THIN WRAPPER: scripts/ci-local.sh is the single definition of these checks. Each job
20+
# installs the toolchain its lane needs and calls the script with that lane's `--only`
21+
# selector and `--integration-only`, which runs just the lane's docker/Postgres half:
22+
# ts-slow — migrate-ts real-PG suite, runtime-ts real-PG dialect matrix, TS
23+
# persistence + api-contract corpora
24+
# java-slow — the Java and Kotlin integration modules
25+
# csharp / python — that port's integration corpora
26+
#
27+
# RELEASE BACKSTOP. The PRIMARY gate for the migrate-ts real-PG suites is local-ci.yml's
28+
# ts-slow lane, on every push to main; the ts-slow job here is the cold-environment
29+
# backstop on the v* tag. Tags are pushed AFTER publish (docs/RELEASING.md), so red HERE
30+
# means a broken release is already live on four immutable registries — treat it as an
31+
# incident, never as noise. That inversion is exactly how this lane once sat red for
32+
# eight releases.
1733

1834
on:
1935
push:
2036
tags:
2137
- 'v*'
2238
workflow_dispatch:
2339

40+
permissions:
41+
contents: read
42+
2443
jobs:
2544
release-gate:
2645
runs-on: ubuntu-latest
2746
strategy:
2847
fail-fast: false
2948
matrix:
30-
port: [ts, csharp, java, kotlin, python]
31-
# A single job-level Postgres sidecar shared by every port, instead of each
32-
# port booting (and pulling) its own container per scenario. Mirrors the
33-
# migrate-ts-pg job below. Each port's PG helper, when it sees
34-
# METAOBJECTS_TEST_PG_URL, connects to this sidecar and CREATEs a
35-
# uniquely-named database per scenario (dropping it on stop) — preserving the
36-
# "fresh empty DB per scenario" isolation the per-port containers gave, with
37-
# no image pull / container boot on the hot path. Local dev (no env var) still
38-
# boots per-port containers exactly as before.
49+
lane: [ts-slow, csharp, java-slow, python]
50+
# A single job-level Postgres sidecar shared by every lane, instead of each
51+
# port booting (and pulling) its own container per scenario. Each port's PG
52+
# helper, when it sees METAOBJECTS_TEST_PG_URL, connects to this sidecar and
53+
# CREATEs a uniquely-named database per scenario (dropping it on stop) —
54+
# preserving the "fresh empty DB per scenario" isolation the per-port containers
55+
# gave, with no image pull / container boot on the hot path. With the variable
56+
# set, the script's own sidecar logic stands aside.
3957
services:
4058
postgres:
4159
image: postgres:16
@@ -52,111 +70,68 @@ jobs:
5270
--health-retries 5
5371
steps:
5472
- uses: actions/checkout@v4
73+
with:
74+
persist-credentials: false
5575

56-
- name: Set up Bun (TS port)
57-
if: matrix.port == 'ts'
76+
- name: Set up Bun (TS lane)
77+
if: matrix.lane == 'ts-slow'
5878
uses: oven-sh/setup-bun@v2
5979
with:
6080
# Pin a known-good Bun (1.3.8 segfaults on exit; see the flake that cost a
6181
# full re-run). setup-bun caches the Bun binary but NOT `bun install`
6282
# output — the actions/cache step below does that.
6383
bun-version: '1.3.14'
6484

65-
- name: Cache Bun install cache (TS port)
66-
if: matrix.port == 'ts'
85+
- name: Cache Bun install cache (TS lane)
86+
if: matrix.lane == 'ts-slow'
6787
uses: actions/cache@v4
6888
with:
6989
path: ~/.bun/install/cache
7090
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}
7191
restore-keys: |
7292
${{ runner.os }}-bun-
7393
74-
- name: Set up .NET (C# port)
75-
if: matrix.port == 'csharp'
94+
- name: Set up .NET (C# lane)
95+
if: matrix.lane == 'csharp'
7696
uses: actions/setup-dotnet@v4
7797
with:
7898
dotnet-version: '8.0.x'
7999

80-
- name: Cache NuGet packages (C# port)
81-
if: matrix.port == 'csharp'
100+
- name: Cache NuGet packages (C# lane)
101+
if: matrix.lane == 'csharp'
82102
uses: actions/cache@v4
83103
with:
84104
path: ~/.nuget/packages
85105
key: ${{ runner.os }}-nuget-${{ hashFiles('server/csharp/**/*.csproj') }}
86106
restore-keys: |
87107
${{ runner.os }}-nuget-
88108
89-
- name: Set up JDK (Java and Kotlin ports)
90-
if: matrix.port == 'java' || matrix.port == 'kotlin'
109+
- name: Set up JDK (Java + Kotlin lane)
110+
if: matrix.lane == 'java-slow'
91111
uses: actions/setup-java@v4
92112
with:
93113
distribution: 'temurin'
94114
java-version: '21'
95115
cache: maven
96116

97-
- name: Set up uv (Python port)
98-
if: matrix.port == 'python'
117+
- name: Set up uv (Python lane)
118+
if: matrix.lane == 'python'
99119
uses: astral-sh/setup-uv@v3
100120
with:
101121
enable-cache: true
102122

103-
- name: Install workspace deps
104-
if: matrix.port == 'ts'
105-
run: bun install
106-
107-
- name: Run integration tests
123+
- name: Run integration tests (${{ matrix.lane }})
108124
env:
125+
LANE: ${{ matrix.lane }}
109126
# The shared sidecar's admin URL. Each port's PG helper sees this and
110-
# creates/drops a uniquely-named database per scenario off it, rather
111-
# than booting its own container. Unset locally → per-port container
112-
# fallback.
127+
# creates/drops a uniquely-named database per scenario off it.
113128
METAOBJECTS_TEST_PG_URL: postgres://metaobjects:metaobjects@localhost:5432/metaobjects_test
114-
run: ./scripts/integration-test.sh ${{ matrix.port }}
115-
116-
# migrate-ts PG integration tests — the apply / lifecycle / rollback +
117-
# introspection suites that exercise REAL Postgres behavior (advisory locks,
118-
# multi-tenant ledger, down-migrations) that pg-mem cannot fake. They
119-
# `describe.skip` unless MIGRATE_TS_PG_URL is set; a `services: postgres`
120-
# container supplies the URL.
121-
#
122-
# The PRIMARY gate for these suites is local-ci.yml's ts-slow lane, on every push
123-
# to main. This job is the cold-environment RELEASE BACKSTOP on the v* tag. Tags
124-
# are pushed AFTER publish (docs/RELEASING.md), so red HERE means a broken release
125-
# is already live on four immutable registries — treat it as an incident, never as
126-
# noise. That inversion is exactly how this lane sat red for eight releases.
127-
migrate-ts-pg:
128-
runs-on: ubuntu-latest
129-
services:
130-
postgres:
131-
image: postgres:16
132-
env:
133-
POSTGRES_USER: migrate
134-
POSTGRES_PASSWORD: migrate
135-
POSTGRES_DB: migrate_test
136-
ports:
137-
- 5432:5432
138-
options: >-
139-
--health-cmd "pg_isready -U migrate -d migrate_test"
140-
--health-interval 10s
141-
--health-timeout 5s
142-
--health-retries 5
143-
steps:
144-
- uses: actions/checkout@v4
145-
- uses: oven-sh/setup-bun@v2
146-
with:
147-
bun-version: '1.3.14'
148-
- name: Cache Bun install cache
149-
uses: actions/cache@v4
150-
with:
151-
path: ~/.bun/install/cache
152-
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}
153-
restore-keys: |
154-
${{ runner.os }}-bun-
155-
- run: bun install
156-
- name: Run migrate-ts suite against real Postgres
157-
env:
158-
MIGRATE_TS_PG_URL: postgres://migrate:migrate@localhost:5432/migrate_test
159-
# Arms the in-suite sentinel: if the URL above ever stops being set, the
160-
# suite FAILS instead of silently skipping and reporting a green release gate.
129+
# migrate-ts's real-PG suites run against the same sidecar, and the EXPECT
130+
# flags arm the in-suite sentinels: if a URL ever stops being set, the suite
131+
# FAILS instead of silently skipping and reporting a green release gate.
132+
MIGRATE_TS_PG_URL: postgres://metaobjects:metaobjects@localhost:5432/metaobjects_test
161133
MIGRATE_TS_PG_EXPECT: '1'
162-
run: cd server/typescript/packages/migrate-ts && bun test
134+
RUNTIME_TS_PG_EXPECT: '1'
135+
run: |
136+
METAOBJECTS_CI_M2_REPO="$HOME/.m2/repository" \
137+
scripts/ci-local.sh --only "$LANE" --integration-only --strict-toolchains

‎.github/workflows/local-ci.yml‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,8 @@ jobs:
162162
# push — strictly AFTER the immutable four-registry publish — and so sat red for
163163
# eight straight releases (v0.20.11 … v0.21.1) with nobody looking. Reuses this
164164
# job's existing sidecar; the suite is proven to coexist in one database in a
165-
# serial run (the hosted tag job runs it against a single migrate_test DB).
165+
# serial run (the hosted tag job, integration-tests.yml's ts-slow lane, runs it
166+
# in the same shared metaobjects_test sidecar database).
166167
MIGRATE_TS_PG_URL: postgres://metaobjects:metaobjects@localhost:${{ job.services.postgres.ports['5432'] }}/metaobjects_test
167168
# Makes the in-suite sentinel FAIL if the URL above ever rots away (renamed
168169
# variable, dropped sidecar) rather than describe.skip-ing in silence.

‎.no-mistakes.yaml‎

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
#
33
# WHY THIS EXISTS. With no commands declared, the gate's test step is an AGENT choosing
44
# "the smallest relevant tests" to run. This repository already has the answer committed:
5-
# `scripts/ci-local.sh`, which mirrors `.github/workflows/` — hygiene.yml's leak scan,
6-
# conformance.yml's eight jobs, and integration-tests.yml's Testcontainers matrix. Since
7-
# GitHub Actions was disabled on this repository (2026-09-16) that script is the ONLY thing
8-
# that runs those checks at all, so pinning the gate to it is what keeps them running.
5+
# `scripts/ci-local.sh`, the single definition of the checks — every check workflow in
6+
# `.github/workflows/` runs on GitHub as a thin wrapper over it (hygiene.yml's leak scan,
7+
# conformance.yml's lanes, integration-tests.yml's Testcontainers matrix), so pinning the
8+
# gate to it runs exactly the repository's checks, the same ones GitHub runs.
99
#
1010
# THE SPLIT. These two commands together are exactly `scripts/ci-local.sh --quick`, cut
1111
# along the script's own `--only` section boundaries so neither step repeats the other.
@@ -53,9 +53,8 @@
5353
# takes effect until it is merged to `main`.
5454
#
5555
# There is no `ci` section and none is needed: `no-mistakes status` reports this repository
56-
# as `ci_mode: local`, so the gate skips the CI step and monitors no forge checks. That is
57-
# already the right answer while Actions is disabled — a PR triggers zero workflows, so
58-
# there would be nothing to wait for.
56+
# as `ci_mode: local`, so the gate skips the CI step and monitors no forge checks — the
57+
# commands above already run the same script the GitHub workflows run.
5958

6059
commands:
6160
test: "scripts/ci-local.sh --only ts-fast --only ts-unit --strict-toolchains"

‎AGENTS.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,8 @@ would publish the committed (non-RC) version; only `publish-csharp.yml` has a `v
124124
(To toggle it: `gh api -X PUT repos/<owner>/<repo>/actions/permissions -F enabled=true` — `-F`
125125
for a TYPED boolean, since `-f` sends the string `"true"` and 422s.)
126126

127-
**`scripts/ci-local.sh` is still the pre-PR gate**, and it mirrors the hosted lanes —
127+
**`scripts/ci-local.sh` is still the pre-PR gate**, and it is the single definition of the
128+
hosted checks — every check workflow in `.github/workflows/` is a thin wrapper that calls it.
128129
`--quick` covers `hygiene.yml` in full plus the TypeScript half of `conformance.yml`, and the
129130
flagless full run
130131
adds the C#/Java/Kotlin/Python conformance lanes, the Java reactor and `integration-tests.yml`'s

‎CONTRIBUTING.md‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,12 @@ Cross-language persistence / api-contract corpora (Docker + Testcontainers) run
7373

7474
### Local CI (this IS the CI — run it before opening/merging a PR)
7575

76-
GitHub Actions is disabled on this repository, so the files in `.github/workflows/`
77-
still describe the checks but no longer run them. They are kept because the switch is
78-
reversible; meanwhile `scripts/ci-local.sh` is what runs them, and it mirrors all three
79-
check workflows:
76+
GitHub Actions is enabled on this repository. The three check workflows in
77+
`.github/workflows/` run on GitHub as thin wrappers over `scripts/ci-local.sh`,
78+
the single definition of the checks: each wrapper checks out, installs the
79+
toolchains its lane needs, and calls the script, so a local run is the same
80+
check a workflow run would be. Run the script locally before opening or merging
81+
a PR, so nothing red leaves your machine:
8082

8183
```bash
8284
scripts/ci-local.sh # full parity: hygiene.yml's leak-scan, all-port

‎fixtures/registry-conformance/README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ divergence:
325325
| Java | **live + green** (byte-identical; reconciled SP-G Units 4-7, gate re-enabled Unit 8) | `metadata/src/test/java/com/metaobjects/registry/RegistryManifestConformanceTest.java` | its `java` section (in the metadata `-Dtest=` list) |
326326
| Kotlin | **live + green** (byte-identical; composes the metamodel provider set) | `codegen-kotlin/src/test/kotlin/com/metaobjects/generator/kotlin/RegistryManifestConformanceTest.kt` | its `java` section (in the codegen-kotlin `-Dtest=` list) |
327327

328-
`.github/workflows/conformance.yml` describes where each port's runner is wired (the `conformance` matrix plus `conformance-kotlin`) but no longer runs them — Actions is disabled on this repository, see AGENTS.md; `scripts/ci-local.sh` is what runs them, and `--quick` covers TypeScript only. TS / C# / Python were live from the start; Java + Kotlin were re-enabled in SP-G Unit 8 after the Java metamodel-vocabulary reconciliation (Units 4-7) landed (see the **divergence analysis**:
328+
`scripts/ci-local.sh` is where each port's runner is wired (its `ts-fast`, `csharp`, `java-fast` — Java and Kotlin — and `python` lanes); `.github/workflows/conformance.yml` is a thin wrapper that calls the script once per lane, and `--quick` covers TypeScript only. TS / C# / Python were live from the start; Java + Kotlin were re-enabled in SP-G Unit 8 after the Java metamodel-vocabulary reconciliation (Units 4-7) landed (see the **divergence analysis**:
329329
[`docs/superpowers/specs/2026-06-02-sp-g-java-registry-divergence-analysis.md`](../../docs/superpowers/specs/2026-06-02-sp-g-java-registry-divergence-analysis.md) and the
330330
[reconciliation plan](../../docs/superpowers/plans/2026-06-02-sp-g-java-reconciliation-plan.md)).
331331

‎fixtures/validation-conformance/README.md‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -154,11 +154,11 @@ runners wrap the bind step so a native-parse failure maps to `valid=false`.
154154
## CI gate
155155

156156
All five port runners assert byte-identical boolean verdicts across all five
157-
generated validation artifacts. `.github/workflows/conformance.yml` describes
158-
them (TS/C#/Java/Python under the `conformance` matrix, Kotlin under
159-
`conformance-kotlin`) but no longer runs them — Actions is disabled here, see
160-
AGENTS.md. `scripts/ci-local.sh` is what runs them: the five live in its
161-
`csharp`, `java` and `python` sections plus `ts-fast`, so the flagless
157+
generated validation artifacts. `scripts/ci-local.sh` is what runs them, and
158+
`.github/workflows/conformance.yml` is a thin wrapper that calls it once per
159+
lane. The five live in the script's
160+
`csharp`, `java` (Java and Kotlin, as `java-fast`) and `python` sections plus
161+
`ts-fast`, so the flagless
162162
`scripts/ci-local.sh` covers all five and `--quick` covers TypeScript only. See
163163
[`docs/CONFORMANCE.md`](../../docs/CONFORMANCE.md).
164164

0 commit comments

Comments
 (0)