II.
Page JSON
Structured · livepage:docs-specs-platform-gaps-phase1-rn-07-release
WP-RN-07 record: release machinery → Artifact Registry, `fabric/<branch>/v*` tags, waves, verifier, recovery json
Inspect the normalized record payload exactly as the atlas UI reads it.
{
"id": "page:docs-specs-platform-gaps-phase1-rn-07-release",
"_kind": "Page",
"_file": "wiki/docs/specs/platform-gaps/phase1/rn-07-release.md",
"_cluster": "wiki",
"attributes": {
"nodeKind": "Page",
"sourcePath": "docs/specs/platform-gaps/phase1/rn-07-release.md",
"sourceKind": "repo-docs",
"title": "WP-RN-07 record: release machinery → Artifact Registry, `fabric/<branch>/v*` tags, waves, verifier, recovery",
"displayName": "WP-RN-07 record: release machinery → Artifact Registry, `fabric/<branch>/v*` tags, waves, verifier, recovery",
"slug": "docs/specs/platform-gaps/phase1/rn-07-release",
"articlePath": "wiki/docs/specs/platform-gaps/phase1/rn-07-release.md",
"article": "\n# WP-RN-07 record: release machinery → Artifact Registry, `fabric/<branch>/v*` tags, waves, verifier, recovery\n\nExecuted 2026-10-03 on branch `phase1/rename-train` of `a5c-ai/fabric` (draft PR #4). Spec: `docs/specs/platform-gaps/15-private-isolation-rename.md` WP-RN-07 (§1.2 invariants, §1.3 retag discipline, §4.1–§4.3), `00-intent.md` D10/D17–D21, `40-packaging.md` §2.2/§2.3/§5. Owner decisions at Gate A: \"Approve (Recommended)\"; verifier workflow \"Enable now\" (enabled and first dispatched by the orchestrator); naming \"Keep both\". The public repository `a5c-ai/babysitter` was never written to (`pushed_at` `2026-09-16T19:59:50Z` at start and end; tag count 404 at start and end, 0 `fabric/` tags there; `upstream` remote push = `no_push`), nothing was published to any registry (`npm publish` ran only with `--dry-run`, locally against a fixture `npm` and once against Artifact Registry with a reader credential, which the registry refused at the existence query — see §6), and no tag was created (fabric tags: the same 3 `gke-cutover-*` tags before and after).\n\n## 1. What the train looks like now\n\n```\npublish-npm-private.yml (push main|staging|develop; dispatch = dry run unless dry_run=false)\n resolve_release ──► verify_release_artifacts (release-artifact-verifier.yml, 43 pkgs, no tolerance)\n │ │\n └──────────────────────┴──► publish_candidates (needs both; if verifier == success)\n WIF auth ─► google-artifactregistry-auth ─► npm ping AR\n verify --write release-version.json (identity re-validated)\n sync-workspace-versions ─► verify-manifests ─► publish-isolation-guard\n assert-channel-tags --mode preflight --registry AR\n build waves (release-matrix --format waves, npm run build per member)\n publish waves (publish-package-from-tag.mjs, FABRIC_RELEASE_PUBLISH=1,\n candidate-<version>, --registry AR) [dry run: --dry-run]\n assert-channel-tags --dist-tag candidate-<v> --mode final\n ──► published_consumer_validation (live-stack-published.yml, exact version)\n ──► promote_release_channel (release-promotion.cjs promote --evidence)\n ──► assert_release_channel_tags (every package, channel == version)\n ──► create_release_tag (release-tags.yml → fabric/<branch>/v<version>)\n\npublish-packages-from-tag.yml (push tags fabric/**; dispatch tag=)\n release_identity (tag-source: version/channel/registry from the tag; refuses a tag the train created)\n ──► verify_release_artifacts (strict, against the TAG tree)\n ──► publish_non_plugin_packages (same sequence; existing versions only candidate-tagged → idempotent)\n ──► published_consumer_validation ──► promote_release_channel (+ final assertion)\n\nlive-stack-published.yml WIF + AR credential; published_consumer = fresh project + .npmrc routing +\n install/import/bin smoke of all 43 + §3.1 global-install smoke; live lanes install\n the §3.1 CLIs from AR at the exact version (no --force), run the live stack\nfix-npm-tags.yml dispatch-only AR dist-tag repair (audit | set), dry run unless dry_run=false\nrelease-tags.yml fabric/<branch>/v<version>, fabric-release-* provenance, this repo only\n```\n\nDerived publication waves at HEAD (13 waves, 43 packages; `node scripts/release-matrix.cjs --group all-publishable --format waves`): 1 `catalog control-plane harness-observability harness-tasks harness-triggers operator trust` · 2 `harness-extensions harness-hooks-core harness-policy` · 3 `harness-core` + the 12 `harness-hooks-*` leaves · 4 `console-ui harness-codecs harness-config harness-hooks-cli harness-mock model-gateway` · 5 `harness-gateway harness-launch harness-tools` · 6 `harness-cli` · 7 `harness-sdk` · 8 `console-tui harness-channels orchestrator-sdk` · 9 `agent-runtime-engine observer` · 10 `agent-runtime-core` · 11 `agent-runtime-platform` · 12 `agent-runtime-cli` · 13 `orchestrator`.\n\n## 2. What changed, per file\n\n| File | Change |\n|---|---|\n| `scripts/lib/release-version.cjs` | `TAG_PREFIX = 'fabric'` (exported, with `TAG_MESSAGE_KEY_PREFIX = 'fabric-release-'`); tag body `fabric release <v> (<branch>)` + `fabric-release-{version,branch,source,commit,run}`; `parseTagMessage` reads only those keys; `parseReleaseTag` rejects every other namespace (`babysitter/*` included); `npmDistTags`/`assertChannelTags` take `registry` and pass `--registry` (default: the root manifest's `fabric.privateRegistry`); reconcile error names the `fabric/<branch>/v<version>` tag |\n| `scripts/release-version.cjs` | every emitted identity (`resolve`, `verify`, `from-tag`, `tag-source`) carries `registry`; `--github-output` therefore exports `registry=`; `assert-channel-tags --registry <url>` (default AR); header documents the private train and the unchanged channel semantics |\n| `scripts/publish-package-from-tag.mjs` | private scope from the `fabric` block; G1 asserted on the manifest at publication time (`manifestProblems` + exact `publishConfig.registry`); `--registry <AR>` on `publish`/`view`/`dist-tag add`; no `--access`; `FABRIC_RELEASE_PUBLISH=1` explicit opt-in replaces the `NODE_AUTH_TOKEN` gate; `--dry-run` runs the whole sequence with `npm publish --dry-run`; a `/`-shaped ref outside `fabric/` is refused as a channel source; npm output naming the public registry is a hard failure |\n| `scripts/lib/release-promotion.cjs`, `scripts/release-promotion.cjs` | `exactVersionExists` and the planned `dist-tag add` commands carry `--registry <AR>`; `assert-installed --prefix <dir>` for the throwaway global prefix |\n| `scripts/verify-published-release.mjs` | the fresh consumer gets `.npmrc` = `@fabricstack:registry=<AR>` (recorded in `install.log`) |\n| `scripts/verify-release-artifacts.mjs` | the clean consumer gets the same routing line (internal deps still resolve from checkout-packed tarballs; an escaped edge now fails at AR, never resolves a stranger's package on npmjs) |\n| `scripts/fix-broken-latest-tags.mjs` | rewritten: inventory-derived, `--registry <AR>` on every call, refuses the public registry; audit mode (latest → prerelease / empty artifact / missing latest, proposes last healthy release) and `--set <dist-tag>=<exact>` whole-inventory repair that refuses to retag if any package lacks the version; dry run unless `--fix`; `--package`, `--json` |\n| `scripts/check-package-metadata.cjs` | `RELEASE_MATRIX_SURFACES = [publish-npm-private.yml, publish-packages-from-tag.yml]` — the coverage gate is live again (`verify:metadata`: 43 packages, 0 violations) |\n| `scripts/ci/publish-isolation-guard.cjs` | G2(c)/G3 whole-tree rules unconditional (`npmjs-publish-context`, `npmjs-credential` are findings in every workflow; no legacy-train notice class; `--private-train` no-op alias); header rewritten |\n| `scripts/lib/registry-pin.cjs` | doc: the flag is a no-op alias everywhere since WP-RN-07 |\n| `scripts/ci/fixtures/publish-guards/g3/{positive/legacy-train-notice-only → negative/legacy-train-publish}` | the public-train shape is now a NEGATIVE fixture (`npmjs-publish-context`, `npmjs-credential`) |\n| `.github/workflows/publish-npm-private.yml` | NEW — see §1 |\n| `.github/workflows/publish-packages-from-tag.yml` | `fabric/**`; WIF + helper; registry from the tag identity; wave-derived build replaces the hand-listed `@a5c-ai` builds; `FABRIC_RELEASE_PUBLISH=1`; candidate assertion; no `registry-url`, no `NPM_TOKEN`; the `sync_external_plugins_develop` job removed (it pushes public marketplace repositories — WP-RN-11's surface) |\n| `.github/workflows/live-stack-published.yml` | WIF + helper in `published_consumer` and every live lane; no `registry-url`; §3.1 global-install smoke step (WP-RN-06 AC2, `--@fabricstack:registry=<AR>` per call, `--prefix`, EEXIST fails, exact bin sets `orchestrator` then `harness harness-hooks orchestrator`); live lanes install `orchestrator orchestrator-sdk harness-sdk agent-runtime-cli harness-hooks-cli` at the exact version, no `--force`, EEXIST fails; `assert-installed` on those five; `harness install`; bins `--version` smoke |\n| `.github/workflows/fix-npm-tags.yml` | dispatch-only (push trigger registers it, job gated), WIF + helper, modes audit/set, `dry_run` default true |\n| `.github/workflows/release-tags.yml`, `release-artifact-verifier.yml` | headers for the private namespace / the consumer routing line (logic unchanged) |\n| `.github/workflows/ci.yml` | guard comment no longer spells the public registry host; `release-tooling` job comment describes the expected-green state |\n| `.github/workflows/ar-smoke.yml` | the dry-run assertion refuses any `npmjs` mention (stricter than the host literal) |\n| `.github/workflows/publish.yml` | untouched: it is the GKE deploy pipeline and carried no npm publish job (WP-RN-01 de-mirrored it; the `@a5c-ai/genty-web-app` build at line 178 is a retirement-listed private shell, not a publish path) |\n| `docs/RELEASE-RUNBOOK.md` | NEW operator runbook (invariants, normal train, dry-run commands, consumer validation, half-published recovery, dist-tag repair, credentials, FIX-005 follow-up, pending G3/G4 owner actions, public-namespace checks) |\n| `docs/release-pipeline.md`, `docs/VERSIONING.md`, `docs/user-guide/private-install.md`, `docs/release-recovery-runbook.md` | model rewritten for AR; links to the runbook; the public-train runbook carries a superseded banner |\n| `packages/adapters/hooks/cli/package.json` | `//deferred-dependency` note → `docs/RELEASE-RUNBOOK.md § 7.1` |\n| tests | see §4 |\n\n## 3. Decisions recorded\n\n- **Staging/develop version shape kept**: spec 15 §4.1 says the dist-tag/channel scheme \"carries over unchanged … the resolver logic ports as-is\" and §4.2 adds nothing about the version shape, so `staging` resolves `7.0.1-staging.<sha>` (FIX-001 commit-pinned prerelease) and `main` resolves `7.0.0`. Nothing re-derives either.\n **Roadmap conflict and reconciliation (review M4, 2026-10-04):** `50-roadmap-workpackages.md` wrote the M1 evidence as the literal `fabric/staging/v7.0.0` (M1 exit criterion 1, risk 7, WP-RM-02 AC1), which `parseReleaseTag` rejects (`from-tag --tag fabric/staging/v7.0.0` → \"must encode a staging prerelease of the form <major>.<minor>.<patch>-staging.<short-sha>\", exit 1), so the criterion could never be satisfied as written. Owner decision at the pre-Gate-B interview: **\"Spec 15 wins: staging = 7.0.1-staging.<sha>\"** — the resolver's inherited channel scheme is kept, and the roadmap wording was amended with a dated note: the staging evidence for M1 is the tag `fabric/staging/v7.0.1-staging.<sha>` publishing all 43 `@fabricstack/*` packages at that exact version under dist-tag `staging`; `fabric/main/v7.0.0` (channel `latest`) is the promotion evidence of the later main train. The resolver's rejection message now names both accepted shapes (`fabric/main/v<X.Y.Z>` and `fabric/<staging|develop>/v<X.Y.Z>-<branch>.<sha>`); the runbook §0 carries the same reconciliation row.\n- **Registry is part of the release identity** (`registry` field) and named explicitly on every `npm view` / `dist-tag add` / `publish` of the train, on top of G1 and the committed `.npmrc` routing.\n- **Opt-in instead of a token gate**: AR credentials live in `$HOME/.npmrc` after the helper, so \"credentials present\" is not observable the way `NODE_AUTH_TOKEN` was; publication now requires `FABRIC_RELEASE_PUBLISH=1`, set only by the two publish workflows after WIF auth. A local invocation can never publish; `--dry-run` rehearses.\n- **Scope routing per call for global installs** (`--@fabricstack:registry=<AR>`), not `--registry <AR>`: a global `--registry` would send third-party dependencies to AR (a standard repository, not a proxy) and 404. The WP-RN-06 AC2 command list used `--registry $AR`; the runbook and the workflow use the scoped form.\n- **No external-plugin sync in the release path**: `sync-external-plugins.yml`/`sync-atlas-plugins.yml` push public marketplace repositories; they are WP-RN-11's and are not called by either AR publish workflow (pinned by `verify-release-artifacts.test.mjs`).\n- **Build order = publication waves**, sequential within the publish job (`npm run build --if-present --workspace=<pkg>` per member): several packages chain root `build:*` scripts through `--prefix`, so parallel wave builds would race on shared outputs; correctness over speed in the release lane. The verifier (its own job) builds every package the same way.\n- **G2(c)/G3 hard in default mode**: with every publish path on AR there is no legacy train left to tolerate; the fixture that documented the tolerance is now the negative that proves the rule.\n- **`ci.yml`/`ar-smoke.yml` wording**: the orchestrator's gate greps every workflow for the literal public-registry host (comments included), so the guard comment and the smoke assertion no longer spell it; the assertion got stricter (`npmjs`), the rule text lives in the script.\n\n## 4. Tests rewritten (before → after) and added\n\nBefore this WP `npm run test:release-tooling` was red in CI with exactly 9 inherited names (run 37144046853, `# tests 163 / pass 154 / fail 9`); each asserted the PUBLIC train's `publish.yml` shape.\n\n| Before (failing) | After (passing) — same invariant, private shape, equal or stricter |\n|---|---|\n| both release workflows PUBLISH every public hooks leaf, derived from the inventory | same name; surfaces = `publish-npm-private.yml` + `publish-packages-from-tag.yml`; every leaf covered by derivation AND no leaf named in executable text at all; no `@a5c-ai/hooks-adapter-` |\n| both release workflows BUILD the derived hooks leaves before publishing them | same name; both build the whole train from the derived waves in a step preceding the wave-publish step; no hand-listed hooks builds |\n| publication order publishes the Genty leaf before the hooks CLI | same name; both consume the waves, publish only through the helper (no literal `npm publish`, no `publish_staging_*` jobs); `waveOf(leaf) < waveOf(cli)` kept |\n| both release workflows still cover the whole inventory once comments are ignored | same name; surfaces retargeted (0 violations on both) |\n| FIX-010: the publish workflow calls the published-consumer workflow with the exact release version | same name; `with.release_version` and `with.candidate_dist_tag` from `needs.resolve_release.outputs`, `needs` includes `publish_candidates`, `ref: github.sha` |\n| FIX-010: channel promotion is a separate job that depends on the published-consumer validation | unchanged assertions, new workflow |\n| FIX-010: the channel-tag assertion and the release tag follow the validated promotion | unchanged assertions, new workflow |\n| the release path runs the packed-artifact gate with no tolerance left | new workflow; also `packages: ''` |\n| FIX-011: the branch release cannot publish unless the packed-artifact matrix passes | `publish_candidates` needs + `if`-checks the verifier; it is the ONLY job running the helper; validation/promotion/assertion/tag are transitively gated |\n\nAlso adjusted: `the recovery workflow acts on the RESOLVED release tag, never on github.sha` (the external-plugin sync assertion became \"no such job may exist\"); `FIX-005: the hooks CLI Genty pin …` (runbook = `docs/RELEASE-RUNBOOK.md`, leaf = `@fabricstack/harness-hooks-agent-runtime`); `release-version.test.mjs` (20 tag literals → `fabric/`, `babysitter/*` rejection + `TAG_PREFIX` pinned); `publish-package-from-tag.test.mjs` (`FABRIC_RELEASE_PUBLISH` opt-in, `fabric/` refs); `publish-guards.test.mjs` (default-mode findings, no notices, the six release workflows scanned, no workflow mentions the public host, touching-private list = 11 workflows). No test deleted or skipped.\n\nNew `scripts/__tests__/private-release-train.test.mjs` (15 tests at the first review, 21 after the 2026-10-04 review fixes — §9; the \"19\" this record carried until the second review predated the two additions that landed with the rehearsal fixes; cross-platform fake `npm` — `.cmd` shim on Windows): resolver prints AR + `fabric/` tag for main/staging/develop and `verify`/`from-tag` carry the registry; public tags and provenance rejected; every workflow parses (`yaml`) and none mentions the public host or an npmjs secret; WIF + helper + `.npmrc` diff check on every AR workflow, no `registry-url`, `id-token: write` where auth runs; strict matrix gate before publish on both paths with step order sync → preflight → build → publish → candidate assertion and explicit `--registry`; promotion depends on validation, exactly one `promote`, assertion then tag, `fabric/**` tag trigger, no tag job in recovery; `release-tags.yml` shape; coverage gate live on both surfaces (0 violations over 43); consumer lane scope routing + §3.1 global-install smoke + exact-version live-lane installs; helper: no publish without opt-in, `--dry-run` shape (`--tag candidate-<v> --ignore-scripts --registry <AR> --dry-run`, no `--access`), opt-in publish + idempotent candidate-tag on re-run, G1 refusal with zero npm calls, `fabric/` refs only; dist-tag repair: inventory-derived, every call ends with `--registry <AR>`, read-only audit, `--fix`, `--set` refuses a half-published train and a mutable version.\n\n## 4b. Cross-platform test infrastructure (so the orchestrator's gate can be green on this machine)\n\nThe orchestrator's gate includes `npm run test:release-tooling`, which had never been green on this Windows machine: the fake-`npm` fixtures of four suites were `#!/bin/sh` files that cmd.exe cannot resolve (RN-05: 37 fail pristine, \"npm-stub-on-PATH tests, impossible on Windows\"), and the verifier's bin smoke spawned the POSIX `.bin/<name>` shim where npm on Windows links `<name>.cmd`. Fixed without touching any assertion: (1) `scripts/__tests__/release-version.test.mjs` and `publish-package-from-tag.test.mjs` fakes ported from sh to node (same behaviour: log, E404/error/appear-after/published-list), and all four fakes (`release-promotion`, `verify-published-release` included) gain an `npm.cmd` that delegates to the same node file; (2) the `verify-published-release.test.mjs` fake `install` now links `<bin>.cmd` like real npm does on Windows; (3) `scripts/verify-release-artifacts.mjs` and `scripts/verify-published-release.mjs` smoke `<bin>.cmd` through a shell on Windows (the verifier's `exec` no longer substitutes `npm.cmd` for a `.cmd` command). Windows result: release-version 31/31, publish-package-from-tag, release-promotion, verify-published-release 12/12, verify-release-artifacts 24/24, hooks-atlas-ownership 6/6 — the suites that were environmental are now proof on both platforms; CI (Linux) unchanged.\n\nIncident while recording this (kept for honesty, like PKG-01's): an inline `node -e \"...\"` edit of this record carried backticks inside a bash double-quoted string, so bash executed the backtick fragments as command substitutions — one of them started `scripts/verify-release-artifacts.mjs` over the full matrix (read-only; builds into temp). It was stopped after 5 packages, the stray node process killed, `git status` confirmed only the 7 intended files were modified, and the edit was redone from a script file. Nothing was published, committed or pushed by the slip.\n\n## 5. Gates and runs\n\n| Check | Result |\n|---|---|\n| `node scripts/release-version.cjs resolve --branch staging --sha 78df5013cffc` | `7.0.1-staging.78df5013cffc` / `staging` / `fabric/staging/v7.0.1-staging.78df5013cffc` / `registry: https://us-central1-npm.pkg.dev/boot-464019/fabric-npm/` |\n| `… resolve --branch main …` | `7.0.0` / `latest` / `fabric/main/v7.0.0` / same registry; `verify --version 7.0.0 --print registry` → the AR URL |\n| `… from-tag --tag babysitter/main/v6.0.3` | exit 1: `does not match fabric/<branch>/v<version>` |\n| `verify-manifests --version 7.0.0` | `All synchronized manifests are at release version 7.0.0.` |\n| `publish-isolation-guard.cjs --self-test` | `16/16 assertions passed; repository scan clean (131 files, 0 findings, 0 legacy-train notice(s))` (re-run 2026-10-04 on 6c9f9eed9; the first review saw 15/15 — the `legacy-name-template` fixture added one assertion) |\n| `public-push-guard.cjs --self-test` | `24/24 … repository scan clean (354 files, 0 findings)` (re-run 2026-10-04 on 6c9f9eed9; 350 files at the first review) |\n| `npm run verify:metadata` | `Metadata verification passed. Publishable inventory: 43 public packages.` (coverage gate live on both surfaces) |\n| every workflow YAML | js-yaml + yaml parse: 0 invalid (31 files) |\n| `grep -rln registry.npmjs.org .github/workflows` | nothing (live-stack-published retargeted too) |\n| docs | markdownlint 0 errors, docs-link-check 0 failures, docs-style-check 0 issues |\n| `node --test scripts/__tests__/private-release-train.test.mjs` | 21/21 (re-run 2026-10-04 on 6c9f9eed9 after the §9 additions; 15/15 at the first review) |\n| `npm run test:release-tooling` (this machine, Windows) | before: 163 tests / 109 pass / 54 fail (Windows baseline, incl. the 9 CI names); after: 178 / 146 / 32 — 0 newly failing names, 22 baseline names fixed; the 32 remaining are the `#!/bin/sh` fake-`npm` suites that cannot shadow `npm.cmd` on Windows (release-version FIX-001 assertions, publish-package-from-tag, verify-published-release, verify-release-artifacts bin-smoke/.cmd, hooks CLI packed consumer) — documented as environmental by RN-05/RN-06/PKG-01/PKG-02; CI is authoritative |\n| CI `release-tooling` job | run 37147345462 (eaffe818c): **success** (first green since the WP-RN-05 split); run 37147698801 (d0c5e47b2): **success**; run 37149333707 (7635c68e9, final head): **success** — https://github.com/a5c-ai/fabric/actions/runs/37149333707 |\n| CI `test` job (Lint, Tests, Package) | run 37147698801: **success**; run 37149333707 (final head): **success**; also success: Public-push guard, Publish-isolation guards (G1-G6), Observer Dashboard, Workspace Coverage. Inherited and untouched (not this WP's): Gateway Node engine floor (22) / (22.13.0) — failure, as before this WP. **Docs QA was NOT inherited on 7635c68e9 (run 37149333707) or 55036e19c (run 37150847430)** — correction 2026-10-04 (review M5), replacing the earlier \"failure, as before this WP\" claim for that job: on both heads Docs QA failed *earlier* than the baseline, in `docs:links`, on `RELEASE-RUNBOOK.md` links unresolved in the strict Docusaurus scope — a new failure introduced by this WP's 870b425ee (the runbook's cross-links), found by triage-C. Fixed in 20ef45aa9 (`docs/RELEASE-RUNBOOK.md` added to `strictDocInclude` in `docs-site/docusaurus.config.js`; the links to the user guide and the superseded runbook rewritten as `pathname:///` links); from 20ef45aa9 on (run 37152039889) Docs QA reaches `docs:freshness` and fails there with the baseline's identical 7-entry freshness list — inherited only from that commit onward. Run 37147345462 had Publish-isolation red on three publish-guards assertions still encoding the legacy-train tolerance (fixed in d0c5e47b2) and its `test`/Observer jobs cancelled by the next push |\n| orchestrator gate (verbatim, this machine) | started on the final tree (7635c68e9) to the scratchpad; the Claude Code session **killed it mid-suite for critically low system memory** (\"stopped because the system is running low on memory … do not start it again on your own\") after `All synchronized manifests are at release version 7.0.0.` and 31 logged tests (the `not ok` lines in the partial log are whole test FILES aborted by the kill, not assertions, plus the hooks-CLI packed-consumer test that passes 6/6 alone — the catalog-rebuild race PKG-02 documented). Not restarted per the notice. Equivalent proof: every suite of `test:release-tooling` run individually on this machine after the cross-platform fix is green (§4b), and CI's `release-tooling` job is green on every pushed head (eaffe818c, d0c5e47b2, 7635c68e9); the other four gate members are green locally (verify-manifests, publish-isolation-guard 15/15 + 0 findings, public-push-guard 24/24 + 0 findings, the npmjs grep lists nothing) |\n\n## 6. Dry-run proof and what the registry answered\n\n`node scripts/publish-package-from-tag.mjs --workspace=@fabricstack/trust --release-version=7.0.0 --skip-build --dry-run` against the real Artifact Registry with the reader credential (`google-artifactregistry-auth` for `tal@a5c.ai`, `npm ping` → `PONG`): the helper stopped at the existence query with `Registry query for @fabricstack/trust@7.0.0 failed (exit 1) without reporting E404 … npm error code E403 … The caller does not have permission` — the fail-closed path (a non-404 answer is never read as \"unpublished\"). The same 403 came back for `@fabricstack/smoke` (which exists) and for a made-up name, from `npm view` and from a direct bearer-token `GET`, so it is the account's read permission on the npm endpoint, not a resolver defect; `gcloud artifacts … get-iam-policy` shows `tal@a5c.ai` as `roles/artifactregistry.reader` (phase 0 record), which was enough for the phase-0 install transcript. Follow-up for the owner (not in this WP's power): confirm the reader binding still resolves for npm reads (possibly a conditional/expired grant); CI's service account is the writer and is the identity every workflow uses. The helper's `--dry-run` shape itself is proven by the fixture-backed test (`publish --workspace … --tag candidate-<v> --ignore-scripts --registry <AR> --dry-run`, no `--access`, no mutation).\n\n**Correction (review M1, 2026-10-04) — what the whole-train rehearsal actually is.** The sentence this record first carried here (\"the whole-train rehearsal is the `workflow_dispatch` default of `publish-npm-private.yml`, which is what Gate B should run first\") was false on two counts, both found by the release-machinery review: (a) the helper ran its exact-internal-dependency gate under `--dry-run` against the registry, and on the first train the registry is empty, so every wave ≥ 2 failed (`--workspace=@fabricstack/harness-policy … --dry-run` → \"Required internal dependency @fabricstack/harness-tasks@7.0.0 is not published yet\", exit 1 against a fake E404 npm); (b) `publish-npm-private.yml` is unregistered on any branch GitHub can dispatch from (`gh api repos/a5c-ai/fabric/actions/workflows/publish-npm-private.yml` → 404) until it lands on `staging`, and that push IS the live train (`dry_run=false` on a push event). Fixes: the helper's `--dry-run` now accepts an exact internal pin that a publishable workspace of the checkout carries at exactly that version (\"published by an earlier wave of this train\"; every other pin keeps the strict registry check), and a new `--rehearsal` mode (implies `--dry-run`) issues no registry query at all for a credential-less job while still running the real `npm publish --dry-run` (npm never contacts the registry on a dry run — `lib/commands/publish.js`: `if (!dryRun) await libpub(...)`); the real publication path is byte-for-byte the strict one (fixture tests: dry-run passes on a two-wave fixture with an empty registry, the opt-in path still fails with the dependency-order message, a pin outside the train still fails in both modes). `ci.yml` gained the job **`release-train-rehearsal`** (pull_request): the train's `publish_candidates` sequence end-to-end for the staging identity of the PR head — resolve → lockstep check + guards → release plan written and validated → sync + verify-manifests + guard → derived wave build → SDK package health → wave `publish --rehearsal` (same parallelism) → proof that all 13 waves completed, 43/43 packages dry-run-published, 43 publish commands all carrying `--dry-run`, no `published` line, no `dist-tag add`, no `npmjs` in any log; the packed-artifact gate is logged as covered by `release-artifact-verifier.yml` on the same PR and the two registry-facing assertions as skipped by design. No credential, no `id-token: write`, no `FABRIC_RELEASE_PUBLISH`. **Gate B should read it this way: the first real run of `publish-npm-private.yml` is the staging merge; the rehearsal evidence is this job (run URL in §9) plus the verifier's 43/43.**\n\n## 7. Verifier run(s)\n\n| Run | Head | Result |\n|---|---|---|\n| 37144046853 (orchestrator's dispatch, 1 of 3) | 78df5013c | failed at step `Release tooling tests` (`# fail 9`, the inherited names); the packed-artifact matrix never ran — the inherited red was the only blocker of the first 43-package exactness + bin-smoke run |\n| 37147369892 (dispatch 2 of 3) | eaffe818c | `Release tooling tests` **passed** (the 9 inherited names green on Linux); failed at `Publish-isolation gate self-test` on the same three publish-guards assertions (fixed in d0c5e47b2) |\n| 37147720098 (dispatch 3 of 3) | d0c5e47b2 | **success** — https://github.com/a5c-ai/fabric/actions/runs/37147720098 — every step green; `Verify packed artifacts` (full matrix, `--exactness`, `allow_known_failures=false`): **43 passed / 0 failed** — agent-runtime-cli, agent-runtime-core, agent-runtime-engine, agent-runtime-platform, catalog, console-tui, console-ui, control-plane, harness-channels, harness-cli, harness-codecs, harness-config, harness-core, harness-extensions, harness-gateway, harness-hooks-agent-runtime, harness-hooks-antigravity, harness-hooks-claude, harness-hooks-cli, harness-hooks-codex, harness-hooks-copilot, harness-hooks-core, harness-hooks-cursor, harness-hooks-gemini, harness-hooks-hermes, harness-hooks-oh-my-pi, harness-hooks-openclaw, harness-hooks-opencode, harness-hooks-pi, harness-launch, harness-mock, harness-observability, harness-policy, harness-sdk, harness-tasks, harness-tools, harness-triggers, model-gateway, observer, operator, orchestrator, orchestrator-sdk, trust — the first packed 43-package exactness + bin-smoke run anywhere; exports-exactness scan 43 manifests / 6386 files / 19042 specifiers / 0 findings. No AR lane is blocked on Gate B: the pre-publication gate resolves internal deps from checkout-packed tarballs and needs no registry credential; the post-publication consumer (live-stack-published.yml) is the lane that waits for the first publication |\n\n## 8. Honesty: what is proven and what waits for Gate B\n\n- **Prepared + dry-run-proven, not done**: AC1 (an end-to-end staging release publishing all 43 packages to AR, moving `staging`, creating one `fabric/staging/v…` tag), AC2 (`verify`/channel assertions against AR with published packages), AC3 (a from-tag recovery rehearsal under a candidate dist-tag), and the published-consumer validation against AR (including the §3.1 global-install smoke) can only run once Gate B merges the branch and the train publishes. Every mechanism is wired, parses, is shape-tested, and is exercised against fixture registries; nothing was published to any registry.\n- **AC4 holds now**: the public repository's tag namespace shows no new tags (404 before and after; 0 `fabric/` tags).\n- **Windows**: 32 release-tooling tests are environmental here (`sh` fake-`npm` shims); CI's `release-tooling` job is the proof of the full suite. The baseline run of the suite was started before the first edit and finished after several edits, so its failing-name set is contaminated by an edit-in-flight (one test later fixed); the comparison was repeated against the final tree.\n- **Reader 403 (review M6) — resolved 2026-10-04**: §6 keeps the original fail-closed transcript. The orchestrator verified with a fresh `gcloud` access token that the human reader binding works: `gcloud artifacts repositories get-iam-policy fabric-npm --location=us-central1 --format=json` shows `roles/artifactregistry.reader` for `user:tal@a5c.ai`; `npx -y google-artifactregistry-auth --repo-config=.npmrc --credential-config=\"$HOME/.npmrc\"` followed by `npm view @fabricstack/smoke --@fabricstack:registry=https://us-central1-npm.pkg.dev/boot-464019/fabric-npm/` printed `0.0.0-smoke.4`; a direct bearer-token `GET` of the package document answered `200`. The 2026-10-03 E403 was an expired token (the helper had been run against a stale credential), not a missing or conditional binding. Prerequisite kept: **re-authenticate first** (`npx google-artifactregistry-auth`; tokens live about an hour) before any local read-only runbook command; the runbook's \"CI-only until verified\" markers are lifted (runbook §0a, §6). No workflow depends on the human credential.\n- **Workflow state (review M2, amended by review-2 M8) — 2026-10-04**: `live-stack-published.yml`, `release-tags.yml`, `publish-packages-from-tag.yml` and `fix-npm-tags.yml` were `disabled_manually` on `a5c-ai/fabric` (never run there) and nothing listed enabling them as a Gate-B prerequisite; the owner enabled all four at the pre-Gate-B interview (\"Enable all four now\"). **No trigger fires pre-merge** — the legacy copies of those files on `main`/`staging` are `workflow_call`/dispatch-only or trigger on `babysitter/**` tags — **but a MANUAL dispatch of `fix-npm-tags.yml` without `--ref`, or with `--ref main`, or with `--ref staging` before the merge, would run the legacy npmjs copy**, and that copy is armed: `main` and `staging` carry the same blob (`git rev-parse origin/staging:.github/workflows/fix-npm-tags.yml origin/main:…` → `44112b847` twice), whose `workflow_dispatch` input `dry_run` defaults to `false`, which checks out `staging`, uses `registry-url: https://registry.npmjs.org/` with `NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}`, and whose `else` branch runs `scripts/fix-broken-latest-tags.mjs --fix` — a live `npm dist-tag add @a5c-ai/<pkg>@<v> latest` against the PUBLIC packages with the still org-wide `NPM_TOKEN` (M7 pending). The runbook never lists that form (its §5 dispatches carry `-f mode=…`, which the legacy inputs reject with 422), so the exposure was narrow, but until this amendment only human restraint prevented it — the earlier sentence here (\"Nothing can fire pre-merge\") asserted more than the triggers show. **Mitigation taken by the orchestrator on 2026-10-04**: `fix-npm-tags.yml` was re-DISABLED (workflow 352401959, `disabled_manually`, updated 2026-10-04T09:22:41+03:00, 0 runs ever on fabric); the other three — `live-stack-published.yml` 352401978, `release-tags.yml` 352401989, `publish-packages-from-tag.yml` 352401984 — stay `active`, as does `release-artifact-verifier.yml` 352401988. The orchestrator re-enables `fix-npm-tags.yml` immediately after PR #4 merges to `staging`, as part of Gate B, once M7 is verified. Do not dispatch `fix-npm-tags.yml` until PR #4 is on `staging` and `main` and M7 is verified. Every `gh workflow run` in the runbook carries `--ref` for the same reason (review M3), and the runbook now states that `--ref staging` is itself the legacy copy until the merge.\n- **Not done by design**: `sync-external-plugins.yml` / `sync-atlas-plugins.yml` (public marketplace repos, WP-RN-11); `documented_install` lane of `live-stack-published.yml` still exercises the public marketplace bootstrap and stays channel-gated (skipped pre-promotion; WP-RN-11); `scripts/release-notes.mjs`, `bump-version.mjs`, `rollback-release.sh` legacy helpers untouched (no workflow invokes them); the `fabricstack` npmjs org (G4) remains an owner action. **G3 / review M7 — owner action, required BEFORE anyone dispatches `fix-npm-tags.yml`, i.e. before its Gate-B re-enable** (moved forward from \"before Gate B\" by review-2 M8, 2026-10-04): the organization secret `NPM_TOKEN` is still visible to all repositories (`gh api orgs/a5c-ai/actions/secrets/NPM_TOKEN --jq .visibility` → `all`, re-checked 2026-10-04), fabric included, and the legacy workflow copies on `main` and `staging` reference it; the owner restricts its repository selection in the GitHub UI. Verification: `gh api orgs/a5c-ai/actions/secrets/NPM_TOKEN --jq .visibility` → `selected`, and `fabric` absent from `gh api orgs/a5c-ai/actions/secrets/NPM_TOKEN/repositories --jq '.repositories[].name'`.\n- **Owner decision 2026-10-05 — \"Consumer-only gate for the first train\"**: after publish run 37279009004 (`7.0.1-staging.643131fbd416`: all 43 candidates published and asserted, the exact-version published-consumer validation green, the three `openai/gpt-5.5` live lanes red — an invalid `OPENAI_API_KEY` organization secret, `invalid_api_key`, plus Claude Code 2.1.2xx's unauthenticated `/api/hello` preflight against the transport-adapter proxy, fixed in the same fix-forward PR), the owner chose not to rotate the key now and to gate the first train's promotion on the exact-version consumer job. Implemented on `fix/consumer-only-gate`: `promote_release_channel` (branch train and from-tag recovery) depends on the callee outputs `consumer_result` and `promotable` and evaluates with `!cancelled()` while checking every upstream result explicitly; `record_validation` records the lanes with `--live-lanes-gate` derived from the repository/organization variable `FABRIC_LIVE_LANES_GATE` (unset or `informational` → informational, listed under `informationalFailures`; `required` → the full gate, and then the called workflow's aggregate result must be green too; the script's own default is `required`); no lane is `continue-on-error`. Unchanged: the consumer job itself (install/import/bin smoke of all 43 + §3.1 global-install), the packed-artifact gate, the candidate assertion, the tag-after-promotion order, the no-npmjs invariants. Condition to restore the full gate: a valid `OPENAI_API_KEY` (runbook §7.2). Spec 15 §4.2 does not state the live-lane gate as binding (WP-RN-07's test requirement names the consumer job), so no spec amendment was needed; the runbook §0a/§1/§3/§7.2 carry the dated amendment.\n- **No guard or test weakened**: the flipped fixture became a negative; `--private-train` is accepted as a no-op; every rewritten assertion is listed in §4 with its stricter form.\n\n## 9. Release-machinery review fixes (2026-10-04)\n\nThe release-machinery review of 20ef45aa9 scored 87 (threshold 90; every criterion ≥ 80; no blocking finding) with seven mustFix items. Owner decisions at the pre-Gate-B interview, verbatim: M4 \"Spec 15 wins: staging = 7.0.1-staging.<sha>\"; M2 \"Enable all four now\" (done by the orchestrator); M6/M7 \"I'll do both before Gate B\". Each fix is its own commit on `phase1/rename-train`; the gates of §5 were re-run (tails in the review-fix task output). Nothing was published, no tag was created, no workflow was enabled or disabled by the fixes, and the public repository's `pushed_at` stayed `2026-09-16T19:59:50Z`.\n\n| Finding | What changed | Proof |\n|---|---|---|\n| **M1** whole-train dry-run rehearsal impossible pre-merge (helper's dependency gate fails on an empty registry from wave 2; `publish-npm-private.yml` undispatchable before it is on `staging`, and that push is live) | `scripts/publish-package-from-tag.mjs`: under `--dry-run`, an exact internal pin carried by a publishable workspace of the checkout at that exact version is accepted as \"published by an earlier wave of this train\" (logged), every other pin keeps the strict registry check; new `--rehearsal` (implies `--dry-run`) issues no registry query at all and hard-fails a pin outside the train; the exact `npm publish … --dry-run` command is logged. Real path untouched. `ci.yml`: new job `release-train-rehearsal` (see §6 correction) — the train's `publish_candidates` sequence for the staging identity of the PR head, `--rehearsal` per package in the derived waves, proof step (13/13 waves, 43/43 would-publish lines, 43 publish commands all with `--dry-run`, no `published`, no `dist-tag add`, no `npmjs`), no credential / OIDC / opt-in. Runbook §0a/§2 rewritten: first real run = staging merge; this job + verifier 43/43 = rehearsal evidence; the stale ci.yml comment calling the verifier \"owner-disabled\" corrected | `private-release-train.test.mjs`: \"--dry-run satisfies an exact internal pin from this train when the registry has nothing yet; the real path still refuses it\" (two-wave fixture, empty fake registry: dry-run exit 0 with one `view` of the package itself and none of the pin, opt-in path exit 1 with the dependency-order message, a `0.9.0` pin not in the checkout exit 1 in dry-run); \"--rehearsal issues no registry query at all …\" (exactly one npm call, `publish … --dry-run`; non-train pin → exit 1, zero npm calls; `--rehearsal` + opt-in still dry-run only); \"ci.yml rehearses the whole train in dry-run mode on every PR, without a credential and without any way to publish\" (job shape, order, notices, proof greps). CI: the `Release train rehearsal (dry-run, all packages)` job of the ci.yml run on the final head — URL below |\n| **M2** four workflows `disabled_manually` | Owner enabled `live-stack-published.yml`, `release-tags.yml`, `publish-packages-from-tag.yml`, `fix-npm-tags.yml` (2026-10-04); recorded in §8 and runbook §0a with the verification command; the \"disabled\" caveats dropped. **Amended by review-2 M8 (§9.3)**: `fix-npm-tags.yml` re-disabled the same day until Gate B re-enables it after the merge and M7 | `gh api repos/a5c-ai/fabric/actions/workflows --paginate --jq '.workflows[] \\| \"\\(.state) \\(.path)\"'` → `active` for `live-stack-published`, `release-tags`, `publish-packages-from-tag`, `release-artifact-verifier`; `disabled_manually` for `fix-npm-tags.yml` (352401959, 0 runs) since the M8 mitigation (2026-10-04) |\n| **M3** runbook dispatches without `--ref` bind to `main`'s legacy npmjs workflows | Every `gh workflow run` in `docs/RELEASE-RUNBOOK.md` (and in the superseded `docs/release-recovery-runbook.md`) carries `--ref <staging\\|tag>`; the runbook's header note explains the default-branch binding and the legacy copies on `main`; §4.3 notes that a dispatch must come from the tag or `staging`/`main` (the helper refuses a `/`-shaped feature-branch ref) | `private-release-train.test.mjs`: \"every `gh workflow run` in the runbooks names its --ref\" (backslash continuations joined; prose mentions excluded) |\n| **M4** roadmap literal `fabric/staging/v7.0.0` rejected by the resolver | Roadmap M1 criterion 1, risk 7, WP-RM-02 AC1 amended with a dated note (`fabric/staging/v7.0.1-staging.<sha>` publishes all 43 at that version under `staging`; `fabric/main/v7.0.0` is the promotion evidence); `parseReleaseTag` rejection names both accepted shapes; §3 of this record and runbook §0 carry the reconciliation | `node scripts/release-version.cjs from-tag --tag fabric/staging/v7.0.0` → exit 1, \"…an exact version like 7.0.0 is never published from staging (it is main's, as fabric/main/v7.0.0). Accepted tag shapes: fabric/main/v<major>.<minor>.<patch> … and fabric/<staging\\|develop>/v<major>.<minor>.<patch>-<branch>.<short-sha> …\"; `release-version.test.mjs` 31/31; the resolver test in `private-release-train.test.mjs` pins the message |\n| **M5** Docs QA misreported as inherited on 7635c68e9/55036e19c | §5 CI table corrected: failing in `docs:links` on this WP's RELEASE-RUNBOOK.md links on both heads; fixed in 20ef45aa9; inherited (`docs:freshness`, identical 7-entry list) from 20ef45aa9 on | triage-C record (run 37150847430 / 37149333707) |\n| **M6** human reader credential → 403 on every AR read | First recorded as owner-committed before Gate B (§8, runbook §0a/§6) with the verification commands; **resolved 2026-10-04** by the orchestrator with a fresh token — the E403 was an expired credential; the runbook's \"CI-only until verified\" markers are lifted and the \"re-authenticate first\" prerequisite is kept | `gcloud artifacts repositories get-iam-policy fabric-npm --location=us-central1 --format=json` → `roles/artifactregistry.reader` for `user:tal@a5c.ai`; `npx google-artifactregistry-auth` + `npm view @fabricstack/smoke --@fabricstack:registry=<AR>` → `0.0.0-smoke.4`; direct bearer `GET` → 200 |\n| **M7** `NPM_TOKEN` org secret visible to all repositories | Recorded as owner-committed before Gate B (§8, runbook §7.2) with the verification command; **ordering moved forward by review-2 M8 (§9.3)**: required before anyone dispatches `fix-npm-tags.yml`, i.e. before its Gate-B re-enable | not performed by this task (owner action); still `all` on 2026-10-04; verification: `gh api orgs/a5c-ai/actions/secrets/NPM_TOKEN --jq .visibility` → `selected`, `fabric` absent from its repositories |\n\n### 9.1 What the rehearsal job found on its first runs — three defects of the real train\n\nThe `release-train-rehearsal` job runs the train's `publish_candidates` sequence over the real tree, and its first runs failed on three defects that the live staging train would have hit in the same step, before publishing anything. Each was fixed in its own commit with a pinning test; none is a rehearsal-only problem.\n\n| Run (head) | Failure | Root cause | Fix |\n|---|---|---|---|\n| 37176658216 (1b9dd488b) | step \"Record and validate the release plan\": `Error: Cannot find module './release-version.json'` | `scripts/release-version.cjs verify` advertised `--write` in its usage but only `resolve` and `from-tag` honoured it; `publish-npm-private.yml` runs `verify … --write release-version.json` and then requires the file — the identical step | 2a359be61: `verify --write` records the VALIDATED identity (and `--commit`), writes nothing on a mismatch; test: written plan equals the resolver identity, both workflows carry the `verify … --write` contract |\n| 37176949592 (2a359be61) | wave 1: every helper `GITHUB_REF_NAME \"4/merge\" is neither a release branch nor a fabric/<branch>/v<version> release tag` | on a `pull_request` the ref is `<n>/merge`; the helper refuses a `/`-shaped non-`fabric/` ref as a channel source by design (kept). Rehearsal-specific: the train runs on a push where the ref is the channel branch | b6fc8f4c4 set it via step `env:` — ignored, because the runner writes `GITHUB_*` defaults after the step's `env:` block (run 37178013213 proved it); 7b4cedd10 exports it in the step's shell (`export GITHUB_REF_NAME=\"$REHEARSAL_BRANCH\"`), the same channel the release plan names |\n| 37178840931 (7b4cedd10) | wave 1: `harness-observability` and `trust` reached `[dry-run] would publish`; `catalog`, `control-plane`, `harness-tasks`, `harness-triggers`, `operator` refused: `the checked-out workspace manifest is 0.0.0-fixture.0 but the release version is 7.0.1-staging.<sha>` | `discoverPackages()` walked every `package.json` under the root into a name-keyed map; `scripts/ci/fixtures/publish-guards/g5/negative/duplicate-bin/packages/<name>/package.json` carry the REAL names at `0.0.0-fixture.0` and, walked after `packages/`, shadowed the workspaces. The real train's publish job runs the same helper over the same tree: wave 1 of the live train would have failed identically (the first review's local probes — `trust`, `harness-policy` — were names without a fixture) | cb8ab8c54: packages are the root `workspaces` members (`resolveWorkspaceDirs`, the inventory's and the lockstep check's source); two members with one name are a hard failure; test: a fixture manifest with a real name outside the workspaces does not shadow the member, duplicates fail with zero npm calls, no whole-tree walk remains. Real-tree probe: `--workspace=@fabricstack/catalog --release-version=7.0.0 --skip-build --rehearsal` → `[dry-run] would publish @fabricstack/catalog@7.0.0 … (rehearsal: registry not queried)` |\n\n### 9.2 The green rehearsal and the CI state of the final head\n\n**ci.yml run 37179946474 (head cb8ab8c54) — <https://github.com/a5c-ai/fabric/actions/runs/37179946474>**: `Release train rehearsal (dry-run, all packages)` **success** (job 111370301481, <https://github.com/a5c-ai/fabric/actions/runs/37179946474/job/111370301481>); its proof step printed `waves completed: 13/13; packages dry-run-published: 43/43; publish commands: 43` and `Rehearsal reached wave 13/13; 43/43 packages dry-run-published; 0 publish commands without --dry-run` — no `published` line, no `dist-tag add`, no `npmjs` in any helper log, no credential, no OIDC token, no opt-in. Other jobs of the same run: Release tooling tests **success** (the 21-test `private-release-train` suite included), Lint/Tests/Package **success**, Public-push guard **success**, Publish-isolation guards **success**, Observer Dashboard **success**, Workspace Coverage **success**; Docs QA **failure** (inherited: `docs:links` passed, `docs:freshness` fails with the baseline's identical 7-entry stale list — verified on runs 37176658216 and 37178840931 as well), Gateway Node engine floor (22) / (22.13.0) **failure** (inherited, as before this WP). The run's overall conclusion is therefore `failure` for the three inherited jobs only. Release-tooling was green on every pushed head of this revision (37176658216, 37176949592, 37178013213, 37178840931, 37179946474).\n\n**ci.yml run 37180964707 (head 6c9f9eed9, the final head of this revision) — <https://github.com/a5c-ai/fabric/actions/runs/37180964707>** (closes the \"its ci.yml run was not awaited\" gap of the previous revision of this record): `Release train rehearsal (dry-run, all packages)` **success** (job 111373287978: `waves completed: 13/13; packages dry-run-published: 43/43; publish commands: 43`), Release tooling tests **success** (job 111373288077, `# tests 184 / # pass 184`), Lint/Tests/Package **success** (job 111373288079, 47/47 steps), Public-push guard, Publish-isolation guards, Observer Dashboard, Workspace Coverage **success**; the three inherited reds only: Docs QA (job 111373288051 — `docs:lint`, `docs:snippets`, `docs:links` pass; `docs:freshness` fails on the identical 7-entry stale list: `04-agent-events`, `GAP-PROMPT-011`, `reference-repos/README`, `repo-with-babysitter-processes`, `faq`, `glossary`, `advanced-multi-phase`), Gateway Node engine floor (22) job 111373288074 and (22.13.0) job 111373288031 (62 `##[error]` lines each, the pre-existing TS2307/TS7006 set, scope-normalised diff against the baseline empty). Verifier run **37180964710** (`release-artifact-verifier.yml`, pull_request, 6c9f9eed9) — <https://github.com/a5c-ai/fabric/actions/runs/37180964710> — **success** (packed matrix 43/43, `allowKnownFailures=false`).\n\n**Gate B reads it this way:** the first real run of `publish-npm-private.yml` is the staging merge; its rehearsal evidence is this job (the whole `publish_candidates` sequence over the real tree, dry-run, all 43 packages through all 13 waves — which already found and forced the fix of three defects the live train would have hit, §9.1) plus the verifier's 43/43 (`release-artifact-verifier.yml` on the same PR).\n\nNice-to-haves taken: the dry-run fixture now carries an exact internal pin (the M1 class cannot recur silently); runbook §4.2 makes \"re-run the failed `publish_candidates` job\" the first recovery option and keeps §4.3 for a dead run; §4.3 carries the `/`-ref dispatch note; §4 states that the failed wave is *partially* published (parallel helpers, `wait` collects every sibling). Not taken: `operator --version` / `orchestrator-mcp --version` printing versions (product-code changes in two CLIs, outside a review-fix revision; the live lanes smoke `--version` on the five §3.1 CLIs that implement it and the verifier smokes `--help` on these two).\n\n### 9.3 Second release-machinery review (2026-10-04, score 91) — M8 and nice-to-haves\n\nThe second release-machinery review of 6c9f9eed9 scored 91 (threshold 90; every criterion ≥ 88; no blocking finding) and confirmed M1–M7 addressed. It raised one docs-only mustFix, **M8**, and this revision of the record is its fix; nothing in code, workflows or manifests changed.\n\n| Finding | What changed | Proof |\n|---|---|---|\n| **M8** \"Nothing can fire pre-merge\" (this record §8, runbook §0a/§5) omitted the MANUAL dispatch path of the then-active legacy `fix-npm-tags.yml` copy on `main`/`staging` (`dry_run` default `false`, npmjs + `NPM_TOKEN`, `--fix`) | §8 M2 paragraph rewritten with the precise statement (no trigger fires; a manual dispatch without `--ref` / `--ref main` / `--ref staging` before the merge runs the legacy copy live); the orchestrator's mitigation recorded (`fix-npm-tags.yml` re-disabled 2026-10-04, re-enabled at Gate B after the merge and M7); M7 moved to \"before anyone dispatches `fix-npm-tags.yml`\" (§8, §9 table, runbook §0a/§7.2); runbook header note, §0a and §5 state that the `staging` copy is also legacy until the merge and carry the sentence \"Do not dispatch `fix-npm-tags.yml` until PR #4 is on `staging` and `main` and M7 is verified.\" | `gh api repos/a5c-ai/fabric/actions/workflows/352401959 --jq .state` → `disabled_manually` (updated 2026-10-04T09:22:41+03:00; `…/runs` → `total_count: 0`); `private-release-train.test.mjs` \"every `gh workflow run` in the runbooks names its --ref\" now also asserts the runbook carries the do-not-dispatch sentence — 21/21 |\n| nice-to-have: test count / gate tails / §9.2 | §4 says 21 tests; §5 tails refreshed to 16/16 (publish-isolation-guard) and 354 files (public-push-guard), 21/21 (suite); §9.2 closed with run 37180964707 and verifier run 37180964710 | the three commands re-run on 6c9f9eed9 (tails in §5) |\n| nice-to-have: first-train abort | runbook §4 and §5 state that a post-promotion abort of the FIRST staging train is fix-forward only (no earlier version on AR for `--set staging=<previous>`; the script refuses a version any package lacks) and that consumers of the brand-new scope are unaffected | runbook §4, §5 |\n| nice-to-have: M6 | resolved (see §8): fresh token, policy shows reader, `npm view` → `0.0.0-smoke.4`, bearer GET 200; \"re-authenticate first\" kept | runbook §0a, §6 |\n\nNot taken in this revision (recorded for the follow-up list): `ci.yml` re-`cat`s every accumulated helper log after each wave (410 notice lines for 43 publishes) — cosmetic, CI-only; the 16 real-named fixture manifests under `scripts/ci/fixtures/publish-guards/g5/negative/duplicate-bin/packages/` remain a shadowing hazard for any OTHER tool that walks `package.json` files (the publish helper itself is immune since cb8ab8c54); ruleset 24103129 still enforces no required status checks; `operator --version` / `orchestrator-mcp --version`; the ESM-only posture note in `docs/reference/package-publics.md`. The first review's remaining nice-to-haves (console-tui vitest suite in CI, `ci-oracle/staging-test-shape` deletion, pkg-02 §5a lockfile numbers) are also carried to the follow-up list in the PR body.\n",
"documents": []
},
"outgoingEdges": [],
"incomingEdges": [
{
"from": "page:docs",
"to": "page:docs-specs-platform-gaps-phase1-rn-07-release",
"kind": "contains_page"
}
]
}