AGENTS.md@.github/workflows · git:20261008.86e8e4d · 2026-10-08 · sha256 93743a3ad4c91cae
AGENTS.md@.github/workflows git:20261008.86e8e4dA
Immutable. This exact content never changes and is served at /api/v1/blob/93743a3ad4c91cae.
# 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; runs extractor canary over the Electron prebuilt first). Missing main binary in mounted DMG → `::error::` + detach + exit 1 (was warn+skip). Step `Install the Electron prebuilt (macOS floor-check canary sample)` (darwin) runs `node install.js` in the dir from `pi-dashboard-resolve-tool.cjs electron` — electron has no `scripts` field, so nothing else populates `dist/`. Scanned by `no-hardcoded-node-modules-paths.test.ts`. See change: fix-ci-pipeline-followups. 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. 22.04 AppImage smoke prints a per-phase boot timing table (electron_start, spawn_header+loader, first/last plugin-loader, server_running, health_200; 1 s resolution) on success and failure; shared `denoise` filter; 90 s budget; passes `PI_DASHBOARD_TS_LOADER` through. See change: fix-appimage-cold-boot-latency. |
| `_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. See change: surface-denial-remedy-in-previews — Windows leg runs `scripts/windows-forbidden-subjects-smoke.ts` (host `%SystemRoot%` listed, no POSIX junk). |
| `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-browser.yml` | Sharded browser-E2E (`tests/e2e/`, 168+ Playwright specs) against the Docker harness. Triggers: `workflow_dispatch` (+ informational `shards` input), `pull_request` on `develop` GATED on the `e2e-browser` label (types `opened`/`synchronize`/`reopened`/`labeled`), and `schedule` cron `'30 7 * * *'` **COMMENTED** (land-dark, like `nightly.yml`; enable at task 5.5 once the baseline is green — exactly one of active/commented, contract-enforced). No `push`. ADVISORY: `continue-on-error` on the PR path only; flipped to required in a separate change after two green nightlies. Jobs: `e2e` (matrix `shard: [1..18]`, `runs-on: ubuntu-latest`, `timeout-minutes: 120`, `fail-fast: false`, `PI_E2E_SEED=1`, `PW_E2E_BOOT_TIMEOUT_MS=1200000` (a cache-less runner spends ~6-8min building the image before the health poll can succeed), `TEST_COPY_MODE=1` (GitHub runners refuse the entrypoint's `mount -t overlay` → the container crash-looped; copy-mode is the spec-sanctioned no-capability fallback, free here because globalSetup boots from an EMPTY workspace), `pnpm exec playwright test --shard=${{ matrix.shard }}/18`, uploads `blob-report-<n>` and `harness-failure-<n>` (globalSetup's container state+log snapshot when the boot never goes healthy — the bundle that made the overlay failure diagnosable), then `if: always()` `Tear down harness` reading the workspace from `test-results/.e2e-managed` **as JSON** (`globalSetup` writes `JSON.stringify`; `require()` of the extensionless file loads it as JS and throws, so the old form's `|| true` left the path empty and the belt-and-braces teardown silently no-op'd on every run) and running `docker/test-down.sh` there — `globalTeardown` is primary, this covers a KILLED Playwright process) + `e2e-browser-relay` (the VARIANT-harness leg: `timeout-minutes: 30`, same label gate + PR-advisory `continue-on-error`, `PI_E2E_SEED=1` + `PI_BROWSER_RELAY_FAKE=1` + `PW_E2E_BOOT_TIMEOUT_MS` + `TEST_COPY_MODE=1`, runs ONLY `tests/e2e/browser-relay.spec.ts`, uploads `blob-report-relay` + the boot-log/failure bundles, `if: always()` teardown. It exists because the relay faucet cannot be a shared default — a live relay occludes the chat for every other spec (S2) — so without this leg the whole file would silently skip in all 18 shards; the spec's own `test.skip` names this job) + `merge-report` (`needs: [e2e, e2e-browser-relay]`, `if: always() && needs.e2e.result != 'skipped'`, `continue-on-error` on the PR path — a non-advisory merge job reds a PR whose shards all died before producing a blob, since `download-artifact` matches nothing — downloads `blob-report-*`, `playwright merge-reports --reporter html` → uploads `playwright-report`). One shard per runner: each shard owns its own harness container, so one shard's death cannot cascade; `test-up.sh` reads the runner's real `MemTotal` and refuses an oversubscribing start, so the runner's RAM figure is not the constraint. `--shard` splits by CONTIGUOUS file order, so one shard can inherit a cluster of slow files: N=6 measured a 38→115 min spread, N=12 still capped shard 6 at 120 min on every dispatch (mcp-*/model-*/notify-*), so N=18 cuts that shard's spec count by a third. No `--global-timeout` — the job's `timeout-minutes` is the whole-run budget (#450). `merge-report` runs on `always() && needs.e2e.result != 'skipped'` (a bare `always()` would red every unlabeled PR) and merges with the LOCKFILE's playwright (`pnpm exec playwright merge-reports`), not a hardcoded version. Locked by `packages/shared/src/__tests__/e2e-browser-workflow-contract.test.ts` (matrix length == every `--shard` denominator, `timeout-minutes` on every job, `if: always()` teardown, merged-report upload, PR advisory). See change: stabilize-browser-e2e (#433 part 2). |
| `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). Job `runtime-overlay-e2e` (ubuntu, 90 min; change: electron-runtime-release-pipeline): throwaway Verdaccio (`scripts/runtime-e2e-registry.mjs config` → `VERDACCIO_CONFIG`) → publish `base` → `bundle-server.mjs` with `npm_config_registry` → forge package → publish `good`/`broken` → `playwright … runtime-overlay-update` under xvfb (`PW_ELECTRON_GLOBAL_TIMEOUT_MS` 1 h). Paths filter adds `packages/server/src/runtime-overlay/**` + the two runtime scripts. |
| `ci-gateway-platform.yml` | `workflow_dispatch` + PR-on-paths (transport sources, the two arms, itself). Runs the gateway's transport contract on the OS that DECIDES it, which one runner structurally cannot see: `posix` matrix (ubuntu + macOS) shims `pi-dashboard` to this checkout and runs `qa/tests/29-gateway-posix-no-tcp.sh` (default start advertises a socket and binds NO TCP port); `windows` runs `qa/tests/28-gateway-windows.ps1` (loopback fallback, no socket artifact, hard-drop reconnect as one session, second-standard-user credential read). Kept OUT of `ci.yml` on purpose — that is a cheap one-runner PR net, this boots real dashboards on three OSes. See change: add-pi-gateway-transport-identity (tasks 5.1/5.5/5.7/12.53/13.8). |
| `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 + develop-push net on Node 22, parallel jobs (change: speed-up-ci-affected-tests, D5). Triggers: `push` develop, `pull_request` develop (default types — NO `labeled`, so a label can never publish a fake-green run), `workflow_dispatch` (always full). Top-level `permissions` contents:read. `concurrency` `ci-${{ github.event.pull_request.number \|\| github.sha }}`, `cancel-in-progress` only for PRs. Jobs: `select` (`fetch-depth: 0`, `pnpm install`, `node scripts/select-affected-tests.mjs` — PR `--base origin/<base> --merge-base`, push `--base <before>`, dispatch or `ci:full` label `--full`; uploads artifact `test-selection`; outputs `mode`/`has_real_process`/`ci_scenarios`) · `ci` (guards, spec validate, skill/fixed-tick/settings-path/publish-import checks, PR plugin install-load, kb dist freshness, `lint`, `lint:e2e`, Biome, build + warning gate, post-build publish-imports — NO tests, NO chromium) · `unit` (matrix `shard: [1,2,3,4]` == `SHARD_COUNT`; `assigned.mjs` → empty shard skips steps; chromium; `packages/client/dist/index.html` assertion; `pnpm run test:parallel <files>` or `--shard=n/4` when `enumerated=false`; `verify-executed.mjs`; uploads `vitest-report-unit-<n>` `if: always()`) · `real-process` (`if: has_real_process`; `pnpm run test:real-process <files>`; uploads `vitest-report-real-process`) · `ci-scenarios` (`if: ci_scenarios` — false only for OpenSpec-only diffs; `pnpm run test:ci-scenarios <gated files>`; uploads `vitest-report-ci-scenarios`) · `docker-plugin-load` · `music-pytest` · `ci-result` (`if: always()`, needs EVERY job incl. `select`, runs `check-result.mjs` with `NEEDS_JSON: ${{ toJSON(needs) }}`). Locked by `packages/shared/src/__tests__/ci-affected-selection-contract.test.ts` + `ci-vitest-report-artifact.test.ts`. See changes: isolate-real-process-tests, speed-up-ci-affected-tests. |
| `deploy-site.yml` | Builds + deploys `site/` (hand-written static page; `npm run build` = `node build.mjs` copy + reference check) plus the neutral shell (`packages/shell` → `site/dist/app/`) to GitHub Pages via `actions/deploy-pages@v4`. Triggers: push to `develop` under `site/**` (minus `design-scratch/`), `packages/shell/**`, or the workflow itself; `workflow_dispatch`. **No `release:` trigger** — the default Actions token cannot start runs from release events (never has); the release path is `publish.yml`'s `site-redeploy` job dispatching this workflow on `develop`. Runs `npm run check-release` non-blocking. Locked by `site-deploy-workflow-contract.test.ts` (E9/E10/E12/E14) + `pnpm-migration-contract.test.ts` X6. See change: fix-deploy-site-ship-shell. |
| `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. |
| `nightly-tests.yml` | Full vitest suite every night (change: speed-up-ci-affected-tests, D6) — the safety net under affected-only selection. Triggers: ACTIVE `cron '0 3 * * *'` + `workflow_dispatch`. Top-level `permissions` contents:read; only `report` adds issues:write + actions:read (least privilege). Jobs: `select` (`--full`) → `unit` ×4 (chromium, dist assertion, `verify-executed`, slow tier included) · `real-process` · `ci-scenarios` (plain `pnpm run test:ci-scenarios`) → `report` (`if: always()`, `fetch-depth: 0`, `scripts/test-selection/nightly-report.mjs`: failing files per job, jobs with NO report, bisect range since the last green SCHEDULED develop run; on a scheduled run opens/updates the single open `nightly-tests` issue or comments+closes it on green; uploads refreshed `test-timings`). Independent of land-dark `nightly.yml`; never publishes/tags. Locked by `packages/shared/src/__tests__/nightly-tests-workflow-contract.test.ts`. |
| `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"`). **9-job graph** (change: gate-publish-on-smoke-and-tests, splits former monolithic `prepare`; `site-redeploy` added by fix-deploy-site-ship-shell; `discord-announce` added alongside it): `resolve` → `[ci-checks, smoke]` → `tag-and-push` → `publish` → `electron` → `github-release` → `[site-redeploy, discord-announce]`. **Discord announce terminal job**: `discord-announce` needs `[resolve, github-release]`, `if:` github-release success AND `is_prerelease != 'true'`, `continue-on-error: true` (shipped release never reds on webhook outage); reads `secrets.DISCORD_RELEASE_WEBHOOK` via env `DISCORD_WEBHOOK_URL` only, exits 0 when unset (forks); `gh release view` → jq embed (description capped 3800 chars) with `allowed_mentions: { parse: [] }` → `curl --fail-with-body`. Locked by `publish-workflow-contract.test.ts` `discord-announce contract` D1–D6. `resolve` (pure, no side effects) branches on `github.event_name` and outputs `version`, `tag`, `is_prerelease`, `ref`. `ci-checks` needs `[resolve]`; runs `pnpm install --frozen-lockfile && pnpm run lint && pnpm test && pnpm 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 `pnpm install --frozen-lockfile` → version bump → `node scripts/sync-versions.js` → `pnpm install --lockfile-only` → `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. **Site-redeploy terminal job** (change: fix-deploy-site-ship-shell, D8): `site-redeploy` needs `[github-release]`, dispatches `sync-release-version.yml --ref develop`, waits for that run via timestamp-bounded `gh run watch --exit-status`, then dispatches `deploy-site.yml --ref develop` — because the default Actions token cannot raise `release` events that start runs, `workflow_dispatch` (which always creates a run) is the only working release path for the site; the sequencing prevents the deploy racing the download-block commit. Locked by `publish-workflow-contract.test.ts` E10a/E10b + `site-deploy-workflow-contract.test.ts`. Version stamping (tag-and-push bump + publish stamp) uses `npm pkg set version=... --workspaces --include-workspace-root`, never `npm version` (reify under npm@latest → EALLOWREMOTE on remote-tarball `xlsx`); pinned by `packages/shared/src/__tests__/publish-version-step-contract.test.ts`. See change: v0.9.0 release fix. **Runtime release** (change: electron-runtime-release-pipeline): PACKAGES has server second-to-last (before meta); in-loop server branch runs `scripts/assert-bundled-plugins-published.mjs` then `scripts/generate-runtime-lock.mjs` → `\|\| break` (no server/meta on miss, design R2). Prereleases `--tag beta` (was `next`); GitHub prereleases stay drafts. Job `runtime-asset` (needs publish; native matrix darwin-arm64/x64, linux-x64/arm64, win32-x64 = `RUNTIME_ASSET_TARGETS`) runs `node --import tsx scripts/build-runtime-asset.mjs` → artifact `electron-runtime-<platform>-<arch>`; `github-release` needs it. Job `runtime-release-assert` (needs github-release) runs `scripts/assert-runtime-release.mjs` (test-plan #X16). |
| `sync-release-version.yml` | Rewrites the download block in `site/index.html` (inline release data — no `latest-release.json` since the static-page rewrite) from `gh api repos/$REPO/releases/latest` via `site/design-scratch/scripts/sync-release.mjs` (assets matched by SHAPE: extension + arch suffix), commits to `develop` as `github-actions[bot]` (a default-token commit does NOT trigger another workflow — the push path filter cannot fire on it). The working release path: `publish.yml` `site-redeploy` dispatches this workflow, waits via correlation-token run lookup + `gh run watch --exit-status` + explicit conclusion check, then dispatches `deploy-site.yml --ref develop` explicitly. Trigger: `workflow_dispatch` ONLY (`correlation` input). `release:` removed — it fired only for hand-published prerelease drafts and that run was incomplete (its GITHUB_TOKEN commit cannot start `deploy-site.yml`). Manual recovery = TWO dispatches: `sync-release-version`, then `deploy-site`. Locked by `site-deploy-workflow-contract.test.ts` E11 + `sync-release-version.yml has no release-event trigger` describe. See change: fix-deploy-site-ship-shell, fix-ci-pipeline-followups. |