From a5796db96ffbf51cd40af2069ac7c9c2723286a6 Mon Sep 17 00:00:00 2001 From: SoryAK Date: Fri, 11 Sep 2026 15:08:36 -0400 Subject: [PATCH] ci: gate main on Actions and publish core from version tags Document the Protect main required check, and add a tag-only publish.yml so npm Trusted Publisher can ship @mba-ai/core without an NPM_TOKEN. --- .Manual/jenkins-ci-pipeline.md | 3 + .github/workflows/publish.yml | 67 ++++++++++++++ Jenkinsfile | 3 +- README.md | 1 + docs/adr/0095-jenkins-ci-with-poll-scm.md | 5 +- docs/ci.md | 101 +++++++++++----------- 6 files changed, 126 insertions(+), 54 deletions(-) create mode 100644 .github/workflows/publish.yml diff --git a/.Manual/jenkins-ci-pipeline.md b/.Manual/jenkins-ci-pipeline.md index 9e38e47..b5c9052 100644 --- a/.Manual/jenkins-ci-pipeline.md +++ b/.Manual/jenkins-ci-pipeline.md @@ -1,5 +1,8 @@ # Jenkins CI Pipeline +> **The merge gate is GitHub Actions**, not this job. See [`docs/ci.md`](../docs/ci.md). +> This page is the old local Poll SCM monitor ([ADR-0095](../docs/adr/0095-jenkins-ci-with-poll-scm.md)). + ## Feature Name Automated build verification for the MBA repo — every push to `main` is typechecked, tested, and built by Jenkins. diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..342330e --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,67 @@ +# Publish `@mba-ai/core` to npm on a version tag (`v0.1.16`). +# Does not run on pull requests or on push to main. +# Auth is npm Trusted Publisher (OIDC). No NPM_TOKEN. +# +# On npmjs.com → @mba-ai/core → Trusted Publisher → GitHub Actions: +# Organization or user: SoryAK +# Repository: MBA +# Workflow filename: publish.yml +# Environment: (leave empty) +# Allowed actions: npm publish + +name: Publish + +on: + push: + tags: + - "v*" + +permissions: + contents: read + id-token: write + +concurrency: + group: npm-publish + cancel-in-progress: false + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-node@v7 + with: + node-version: 22 + package-manager-cache: false + + - name: npm CLI for trusted publishing + run: | + npm install -g npm@latest + npm --version + + - run: npm ci + - run: npm run typecheck + - run: npm test + - run: npm run build + + - name: Tag matches core version and changelog + run: | + set -euo pipefail + tag="${GITHUB_REF_NAME#v}" + ver="$(node -p "require('./packages/core/package.json').version")" + if [ "$tag" != "$ver" ]; then + echo "tag v$tag does not match @mba-ai/core $ver" + exit 1 + fi + if ! grep -qE "^## \\[${ver}\\] " packages/core/CHANGELOG.md; then + echo "packages/core/CHANGELOG.md has no ## [$ver] section" + exit 1 + fi + if grep -qE "^## \\[Unreleased\\]" packages/core/CHANGELOG.md; then + echo "changelog still has an Unreleased heading" + exit 1 + fi + + - name: Publish @mba-ai/core + run: npm publish -w @mba-ai/core diff --git a/Jenkinsfile b/Jenkinsfile index f8cc4d0..e740ee2 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,4 +1,5 @@ -// MBA CI pipeline — runs on every push to main (see docs/ci.md for job setup). +// MBA CI pipeline — local reference only. The merge gate is GitHub Actions +// (.github/workflows/ci.yml) plus the Protect main ruleset; see docs/ci.md. // Requires a NodeJS tool named 'node-22' configured in Jenkins (Manage Jenkins → Tools). pipeline { agent any diff --git a/README.md b/README.md index cfdb059..7bd4e09 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,7 @@ After CLI changes, rebuild `@mba-ai/core` so a linked `mba` picks them up (`npm ## Docs - [`.Manual/model-behavioral-adapters.md`](.Manual/model-behavioral-adapters.md) — system manual +- [`docs/ci.md`](docs/ci.md) — merge gate (GitHub Actions) - [`docs/adr/`](docs/adr/) — architecture decision records ## License diff --git a/docs/adr/0095-jenkins-ci-with-poll-scm.md b/docs/adr/0095-jenkins-ci-with-poll-scm.md index d8f112c..dab17fa 100644 --- a/docs/adr/0095-jenkins-ci-with-poll-scm.md +++ b/docs/adr/0095-jenkins-ci-with-poll-scm.md @@ -1,6 +1,9 @@ # ADR-0095: Jenkins CI with Poll SCM (local, no internet exposure) -**Status:** Accepted +**Status:** Accepted for the local Jenkins trigger. The **merge gate** on the +public repo is GitHub Actions plus the Protect main ruleset; see +[docs/ci.md](../ci.md). This ADR remains the record of why Poll SCM was +chosen when the repo could not use Actions. ## Context diff --git a/docs/ci.md b/docs/ci.md index 0338fc0..1fb5571 100644 --- a/docs/ci.md +++ b/docs/ci.md @@ -1,75 +1,72 @@ -# CI — Jenkins Pipeline +# CI — GitHub Actions merge gate -MBA builds on every push to `main` via a Jenkins declarative pipeline defined in -[`Jenkinsfile`](../Jenkinsfile) at the repo root. The job polls GitHub every 5 -minutes (Poll SCM) and builds automatically when a new commit lands on `main`. +MBA’s quality gate is [`.github/workflows/ci.yml`](../.github/workflows/ci.yml). +It runs on every **pull request** and on every **push to `main`**. The +**Protect main** ruleset requires that check to be green before a PR can +merge. A red typecheck, test, or build cannot land on `main` unless someone +explicitly bypasses the ruleset. -## What a build does +## What a run does -1. **Checkout** — clones the latest `main` from GitHub (`SoryAK/MBA`, public — no credential needed) -2. **Install** — `npm ci` (exact deps from `package-lock.json`) +1. **Install** — `npm ci` (exact deps from `package-lock.json`) +2. **Audit** — `npm audit --omit=dev --audit-level=high` 3. **Typecheck** — `npm run typecheck` (`tsc --noEmit`) 4. **Test** — `npm test` (`vitest run`) 5. **Build** — `npm run build` (all workspaces with a build script) -Any stage failing marks the build red. +The check name GitHub requires is **`test`** (the job in that workflow). +CodeQL also runs on PRs; it is not part of the merge requirement. -## One-time Jenkins setup - -### 1. Node 22 tool +## Local parity -The pipeline references a NodeJS tool named **`node-22`**. +A green local `npm run typecheck && npm test && npm run build` should mean a +green Actions run (audit is extra on CI). -- Manage Jenkins → **Tools** → **NodeJS** → *Add NodeJS* -- **Name:** `node-22` (must match exactly) -- Either tick *Install automatically* (Jenkins downloads Node 22.x) or point - **Tool home** at an existing install, e.g. `/usr/lib/node_modules` is NOT - correct — use the directory *containing* the `bin/` folder, e.g. - `/opt/node-22` or wherever `node` lives (`dirname $(dirname $(readlink -f $(command -v node)))`). +After CLI UI changes, rebuild `@mba-ai/core` so a linked `mba` matches CI’s +`dist`. The daemon still runs from source (`tsx`); restart the user unit +after service-path changes. -### 2. Create the job +## Ruleset -1. **New Item** → name it `MBA` → type **Pipeline** → OK -2. **Pipeline** section: - - **Definition:** *Pipeline script from SCM* - - **SCM:** Git - - **Repository URL:** `https://github.com/SoryAK/MBA.git` - - **Branch:** `main` - - **Script Path:** `Jenkinsfile` -3. **Save** → **Build Now** to verify the first run is green. +Repository **Settings → Rules → Protect main** (branch `main`): -### 3. Trigger on push — Poll SCM (active) +- Must go through a pull request +- Required status check: `test` (GitHub Actions) +- No force-push, no deleting `main` -The job uses **Poll SCM**: Jenkins checks GitHub every 5 minutes and builds -when a new commit lands on `main`. +The repo owner can still bypass in an emergency. Treat that as exceptional. -- Job **Configure** → **Triggers** → tick **Poll SCM** -- **Schedule:** `H/5 * * * *` (every 5 min; `H` staggers the exact minute) -- **Save** +## npm publish (Trusted Publisher) -Verify it's working: the job page gains a **Git Polling Log** link -(`/job/MBA/scmPollLog`). Each poll should end with `No changes` until you push -a new commit, at which point the next poll triggers a build. +`@mba-ai/core` publishes from [`.github/workflows/publish.yml`](../.github/workflows/publish.yml) +when you push a tag that matches the package version (`v0.1.16` → `0.1.16`). +It does not run on PRs or on `main`. `@mba-ai/mcp-server` is not in this +workflow. -> **Why polling and not a webhook?** Jenkins runs on `localhost`, which GitHub -> can't reach. A webhook would need a public tunnel (ngrok/cloudflared) kept -> running. Polling needs no internet exposure and is the robust choice for a -> local Jenkins. If Jenkins ever moves to a server, switch to the webhook below -> for near-instant triggers. +GitHub Actions authenticates to npm with OIDC. There is no `NPM_TOKEN`. -### 4. (Future) GitHub webhook — only if Jenkins is publicly reachable +One-time on [npmjs.com](https://www.npmjs.com/package/@mba-ai/core) → package +settings → **Trusted Publisher** → **GitHub Actions**: -GitHub → repo **Settings → Webhooks → Add webhook**: +| Field | Value | +| --- | --- | +| Organization or user | `SoryAK` | +| Repository | `MBA` | +| Workflow filename | `publish.yml` | +| Environment | leave empty | +| Allowed actions | `npm publish` | -- **Payload URL:** `http://:8080/github-webhook/` -- **Content type:** `application/json` -- **Events:** *Only selected events* → **Pushes** +Keep two-factor authentication required on the account. -Requires the **GitHub plugin** (Manage Jenkins → Plugins) and a Jenkins -instance reachable from the internet. +Release path: bump `packages/core/package.json` (and the lockfile), add a +`## [x.y.z]` section to `packages/core/CHANGELOG.md`, merge to `main`, then +`git tag vX.Y.Z && git push origin vX.Y.Z`. -## Local parity +## Jenkins (reference only) -The pipeline runs exactly the scripts in `package.json`, so a green local -`npm run typecheck && npm test && npm run build` should always mean a green -build. +[`Jenkinsfile`](../Jenkinsfile) is the old local Poll SCM monitor +([ADR-0095](adr/0095-jenkins-ci-with-poll-scm.md)). It is not the merge gate. +Keep the file as a map of the same stages. If a local Jenkins job is still +polling `main`, turn that poll off so you are not running two monitors. +A self-hosted Actions runner is the replacement if a check ever needs this +machine’s GPU or LAN — not a second CI product.