AGENTS.md@.github/workflows · git:20260822.d8b629d · 2026-08-22 · sha256 670269f9db19b25b
AGENTS.md@.github/workflows git:20260822.d8b629dA
Immutable. This exact content is served forever at /api/v1/blob/670269f9db19b25b.
# DOX — .github/workflows
Files in this directory. One row per file. Non-source area (migrated from `docs/file-index-skills-misc.md`; source of truth now here). See change: migrate-file-index-to-agents-tree.
| File | Purpose |
|------|---------|
| `_electron-build.yml` | Reusable workflow_call. Encapsulates 6-leg Electron matrix (darwin/linux/win32 × arm64/x64) with per-leg `if:` guard parsing `legs` input. Inputs: `version` (SemVer string applied via `npm pkg set version=... --workspaces --include-workspace-root`, not `npm version`, to avoid workspace `prepare` side effects), `ref` (git ref to check out), `legs` (default `all`; accepts `darwin`/`linux`/`win32`/comma-list like `darwin-arm64,linux-x64`), `source_only_bundle` (default `false`; passes `--source-only` to `bundle-server.mjs`), `artifact_retention_days` (default `14`). No outputs (artifacts only). Consumed by `publish.yml` (release path: `source_only_bundle: false`, `artifact_retention_days: 90`) + `ci-electron.yml` (CI path: `source_only_bundle: false`, `artifact_retention_days: 14`). Step `Assert runnable bundle (cli.ts exists)` runs after `Bundle dashboard server` when `inputs.source_only_bundle == false`; Node-native (`node -e`), no `shell: bash` (Windows-reachable invariant); asserts `packages/electron/resources/server/node_modules/@blackbelt-technology/pi-dashboard-server/src/cli.ts` + `package.json` exist; fails leg with non-zero exit + pointer to `fix-ci-electron-runnable-bundles` on miss; gates sync-versions.js drift + bundle-layout regressions shipping non-runnable artefact. Step `Assert no wmic in bundle` runs after `Assert runnable bundle` when `source_only_bundle == false`; runs `packages/electron/scripts/assert-no-wmic-in-bundle.mjs` (Node-native); fails leg on any wmic exec/spawn in bundled `@blackbelt-technology` code. **Optional `registry_url` input** (default `""`; change: add-nightly-verdaccio-build): when non-empty (nightly path), after `Build client` each leg runs node-native steps `Install/Start/Publish Verdaccio (nightly)` (npm i -g verdaccio → `scripts/nightly-verdaccio-serve.mjs` → `scripts/nightly-verdaccio-publish.mjs`) + `Route npm to Verdaccio` (appends `npm_config_registry` to `$GITHUB_ENV` so the bundle's `npm install` resolves `@blackbelt-technology/*` from the loopback registry). Empty (publish.yml/ci-electron.yml) → those steps skip, unchanged public-npm/source-only path. Step `Assert bundled plugins complete` runs after `Assert runnable bundle` when `source_only_bundle == false`; runs `packages/electron/scripts/assert-bundled-plugins-complete.mjs` (built-bundle plugin-completeness gate) on ALL non-source-only callers. **macOS floor + update gate** (change: upgrade-electron-runtime): darwin make step exports `MACOSX_DEPLOYMENT_TARGET: "12.0"` (was 10.15). Step `Gate the macOS update stream on the minimum supported OS` (darwin only) runs `packages/electron/scripts/inject-update-min-system-version.mjs` on the emitted `out/make/latest-mac.yml` — post-build, because `electron-builder.yml mac.minimumSystemVersion` never reaches update metadata under `--prepackaged`. Step `Verify the update-gate field is present and macOS-scoped` runs LAST (after the Windows NSIS step writes `latest.yml`) on EVERY leg: `latest-mac.yml` must carry the value, `latest-linux.yml`/`latest.yml` must NOT (fail-CLOSED hazard — no platform guard in `checkIfUpdateSupported`); a darwin leg with no `latest-mac.yml` fails rather than passing vacuously. Step `Verify macOS deployment target floor` expects plist `12.0` and delegates the otool leg to `packages/electron/scripts/verify-macos-floor.mjs` (multi-slice safe, EQUALITY not `-gt`, per-arch `case` removed). Step `Smoke the AppImage on Ubuntu 22.04 (glibc floor)` (linux/x64 only) `docker run ubuntu:22.04`, `--appimage-extract` (no FUSE in container), asserts `ldd` reports no `not found` symbol then boots under xvfb to `/api/health` — the OLDER image is the point; the default-runner smoke would pass even if Electron raised the glibc floor above the documented 2.35. See change: add-ci-electron-on-demand-build, fix-ci-electron-runnable-bundles, replace-wmic-with-powershell, add-nightly-verdaccio-build, upgrade-electron-runtime. |
| `_smoke.yml` | Reusable `workflow_call`. Encapsulates 7-leg standalone-install-smoke matrix: Linux × 6 (Node 22/24/25 × bookworm-slim/alpine) + Windows × 1 (Node 22). Input `ref` (string, required) — git ref to check out. Consumers: `ci-smoke.yml` (manual dispatch), `publish.yml` `smoke` job (release gate). Pure artifact-free smoke; no npm publish, no GitHub Release, no tag push. Replaces inline `standalone-install-smoke-{linux,windows}` jobs deleted from `ci.yml`. standalone-install-smoke-windows adds step `Windows introspection smoke` (`pwsh`: `npx tsx scripts/windows-introspection-smoke.ts`) — exercises live PowerShell Get-CimInstance paths, asserts no wmic stderr leak. See change: gate-publish-on-smoke-and-tests, replace-wmic-with-powershell. standalone-install-smoke-windows also adds step `Windows boot-parent-liveness smoke` (`pwsh`: `npx tsx scripts/windows-liveness-smoke.ts`) — asserts koffi Tier-2 (`OpenProcess`+`WaitForSingleObject`) loads on real Windows (`bootParentLivenessTier()==="tier2"`), guarding a silent Tier-1 degrade. See change: electron-attach-ownership-fixes. |
| `ci-electron.yml` | workflow_dispatch only. On-demand Electron build for feature branches. Computes version slug `<base>-ci.<UTC-stamp>.<branch-slug>.<sha7>` from root `package.json#version` + `date -u +%Y%m%d-%H%M%S` + sanitised `GITHUB_REF_NAME` (max 20 chars, `[^a-zA-Z0-9.-]` → `-`, leading/trailing `.-` stripped) + `GITHUB_SHA[:7]`. Validates slug against `publish.yml`'s SemVer regex. Delegates entirely to `_electron-build.yml` with `source_only_bundle: false`, `artifact_retention_days: 14`, `ref: ${{ github.sha }}`. Optional `legs` input forwarded. Prerelease segment SemVer-ranks below `<base>` — `electron-updater` default `allowPrerelease: false` skips these slugs so installed-user update channel is unaffected. No npm publish, no GitHub Release, no tag. Post `fix-ci-electron-runnable-bundles`, CI-dispatched artefacts ship runnable installer layout identical to release builds; earlier `true` value (set by `add-ci-electron-on-demand-build` Decision 3) invalidated by `eliminate-electron-runtime-install` removing every runtime install path; locked by `publish-workflow-contract.test.ts` `ci-electron.yml — runnable-bundle contract` describe block (asserts literal `source_only_bundle: false`). See change: add-ci-electron-on-demand-build, fix-ci-electron-runnable-bundles. |
| `ci-e2e-electron.yml` | Playwright-Electron suite for native-surface flows (zombie-adoption modal, Doctor version-skew row, tray foreign row) unreachable by web-client E2E + unit tests. **Cadence:** `workflow_dispatch` + `pull_request` path-filter (`packages/electron/**`, `tests/e2e-electron/**`, `playwright.electron.config.ts`, self) — ADVISORY (not a required check). Matrix `os: [ubuntu-latest, windows-latest]`, `fail-fast: false`: ubuntu exercises POSIX `decideIsZombie`, windows the win32 branch (bootParentAlive-only) in the REAL app (task 7.4b). OS-split `npm ci` fallback (bash vs pwsh) → `package` → ubuntu adds `playwright install-deps chromium` + `sysctl apparmor_restrict_unprivileged_userns=0` (noble) then `xvfb-run`; windows runs direct. Harness launches with `--no-sandbox`; Linux binary resolved by forge `executableName` `pi-dashboard` (NOT `chrome-sandbox`). Uploads `playwright-report-electron-<os>`. Second job `job-object-windows` (`continue-on-error`, advisory): downloads bundled Node + package + `npx tsx scripts/windows-job-object-smoke.ts` (spawn-mode Job Object kill-on-close, task 7.4a) — `INFRA` when a lightweight package can't boot the full spawn path (deep validation lives in qa VM smoke). Concurrency cancel-in-progress per ref. See change: electron-attach-ownership-fixes (harness), run-electron-e2e-native-surface (cadence + stabilization). |
| `ci-smoke.yml` | `workflow_dispatch` only. Thin shim around `_smoke.yml` passing `ref: ${{ github.ref }}`. Concurrency `ci-smoke-${{ github.ref }}` cancel-in-progress so re-dispatch on same branch cancels prior run. Use for installer-shaped feature branches (lockfile, `bundle-server.mjs`, native deps, preload-fastify) where PR's cheap `ci.yml` job lacks coverage. Sibling of `ci-electron.yml`. No npm publish, no GitHub Release, no tag push. See change: gate-publish-on-smoke-and-tests. |
| `ci.yml` | PR safety net on Node 22 (push/PR to develop). Runs `npm ci` (fallback `npm install` on npm/cli#4828 optional-deps bug), `npm run lint`, `npx biome lint . --reporter=github` (Tier A error gates, Tier B/C annotate), `npm test`, `npm run build`. Standalone-install smoke moved to `_smoke.yml`/`ci-smoke.yml`. |
| `deploy-site.yml` | Builds + deploys `site/` to GitHub Pages. Triggers: push to develop under `site/**`, `release: published`, `workflow_dispatch`. Release-triggered run re-dispatches on `develop` (`redispatch-on-release`) to satisfy Pages branch protection. Build job runs type check (continue-on-error), `npm run build`, `npm run size` bundle budget, uploads `site/dist`; deploy job uses `actions/deploy-pages@v4`. |
| `nightly.yml` | Scheduled full-fidelity Verdaccio round-trip (workflow_dispatch active; `cron '0 7 * * *'` present but COMMENTED per land-dark rollout — enable after one clean manual run). Jobs: `resolve` (compute `<base>-nightly.<YYYYMMDD>.<sha7>`, `<base>` = next patch of package.json version) → `verify-publish` (`npm publish --dry-run` all workspaces, cheap pack pre-gate, no network write) → `electron` (`uses: _electron-build.yml` with `registry_url: http://localhost:4873`, `source_only_bundle: false`, `legs: all`, `artifact_retention_days: 7`) → `report` (`if: failure()`, github-script opens/updates a single issue labelled `nightly` naming the failed leg(s) + run URL). ZERO public npm writes / NO Release / NO tag push / NO version-bump commit — locked by `nightly-workflow-contract.test.ts`. See change: add-nightly-verdaccio-build. |
| `publish.yml` | CI: builds DMG × 2 (macOS arm64 + x64), DEB+AppImage (Linux), NSIS+ZIP+portable (Windows) on native runners; publishes npm + GitHub Release. **Build matrix covers 6 (platform, arch) tuples**: darwin/arm64 (`macos-14`), darwin/x64 (`macos-15-intel` — GitHub's last hosted Intel x86_64 image after `macos-13` retired 2025-12-08; EOL 2027-08; change: add-darwin-x64-build), linux/x64 (`ubuntu-latest`), linux/arm64 (`ubuntu-24.04-arm`), win32/x64 (`windows-latest`), win32/arm64 (`windows-latest`). Missing rows = regression — spec `electron-build-pipeline > CI build matrix` enumerates each scenario to prevent drift. **Two triggers**: (a) push of any `v*` tag (release-cut skill / hand tag); (b) `workflow_dispatch` from GitHub Actions UI with required `version` input (e.g. `"0.4.1"`). **7-job graph** (change: gate-publish-on-smoke-and-tests, splits former monolithic `prepare`): `resolve` → `[ci-checks, smoke]` → `tag-and-push` → `publish` → `electron` → `github-release`. `resolve` (pure, no side effects) branches on `github.event_name` and outputs `version`, `tag`, `is_prerelease`, `ref`. `ci-checks` needs `[resolve]`; runs `npm ci && npm run lint && npm test && npm run build` on Node 22. `smoke` needs `[resolve]`; `uses: ./.github/workflows/_smoke.yml` with `ref: ${{ needs.resolve.outputs.ref }}`. **Release gate** (`ci-checks` + `smoke`) runs in parallel after `resolve` and BEFORE `tag-and-push` — gate failure on `workflow_dispatch` leaves no commit, no tag, no npm artifact. `tag-and-push` needs `[resolve, ci-checks, smoke]` + `if: github.event_name == 'workflow_dispatch'`; bumps every workspace `package.json` via `npm version -ws`, syncs cross-ref specifiers via `scripts/sync-versions.js`, regens lockfile, verifies, promotes `## [Unreleased]` → dated `## [<version>]` in `CHANGELOG.md`, commits + tags + pushes branch. On tag-push entry `tag-and-push` is skipped; `publish.if:` accepts `tag-and-push.result` of `success` (dispatch) or `skipped` (tag-push) while requiring `ci-checks` + `smoke` success. `publish` needs `[resolve, ci-checks, smoke, tag-and-push]`. `electron` needs `[resolve, publish]`. `github-release` needs `[resolve, publish, electron]`. All downstream jobs check out `ref: ${{ needs.resolve.outputs.tag }}` so both trigger paths publish same tree. **Idempotent ordered npm publish** (commit b9fcea9): per-package loop skips already-published versions via `npm view`, publishes 4 stable sub-packages first then `dashboard-plugin-runtime` then root metapackage last. **Electron-publish dependency-graph contract** (change: publish-fix-macos): `electron` job declares `needs: [resolve, publish]` + `strategy.fail-fast: false`; bundle-server resolves `@blackbelt-technology/dashboard-plugin-runtime@^<ver>` against live registry post-publish. **No-bash-on-Windows invariant** (change: eliminate-bash-on-windows-runners): cross-OS orchestration in `.mjs` scripts invoked by `node`; POSIX-only steps gated by `if: matrix.platform != 'win32'`; Windows-only `shell: pwsh`. Locked by `no-bash-on-windows.test.ts`. **Lockfile regen contract** (change: fix-release-lockfile-drift; relocated by `gate-publish-on-smoke-and-tests` from `prepare` to `tag-and-push`): `tag-and-push` runs `npm ci` → `npm version -ws --include-workspace-root` → `node scripts/sync-versions.js` → `npm install --package-lock-only --no-audit --no-fund` → `node scripts/verify-lockfile-versions.mjs` → CHANGELOG promote + commit + tag + push. **6-clause gate contract** (change: gate-publish-on-smoke-and-tests): contract test pins (1) `resolve.outputs.ref`, (2) `ci-checks` shape, (3) `smoke` `uses` + `ref`, (4) `tag-and-push` dispatch guard, (5) `publish.needs`, (5b) `publish.if:` accepts `tag-and-push` `success` or `skipped`. **macOS update-gate merge** (change: upgrade-electron-runtime): the arm64+x64 `latest-mac.yml` merge is no longer an inline heredoc — it calls `scripts/merge-latest-mac.py`, which REJECTS a leg-asymmetric `minimumSystemVersion` instead of inheriting from the glob-first file. New step `Assert the macOS update gate survived the merge` reads the MERGED file (what electron-updater actually consumes) and fails when the value is absent or wrong. |
| `sync-release-version.yml` | On `release: published/edited` or `workflow_dispatch`, fetches latest release via `gh api repos/$REPO/releases/latest`, writes `site/src/data/latest-release.json` (tag, name, url, publishedAt, assets[]), commits + pushes to `develop` as `github-actions[bot]`. `deploy-site.yml` picks up the `site/**` change and redeploys. |