page:docs-specs-platform-gaps-phase1-rn-07-release
WP-RN-07 record: release machinery → Artifact Registry, `fabric/<branch>/v*` tags, waves, verifier, recovery reference
Executed 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 (pushedat 2026-09-16T19:59:50Z at start and end; tag count 404 at start and end, 0 fabric/ tags there; upstream remote push = nopush), 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).
WP-RN-07 record: release machinery → Artifact Registry, `fabric/<branch>/v*` tags, waves, verifier, recovery
Executed 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).
1. What the train looks like now
publish-npm-private.yml (push main|staging|develop; dispatch = dry run unless dry_run=false)
resolve_release ──► verify_release_artifacts (release-artifact-verifier.yml, 43 pkgs, no tolerance)
│ │
└──────────────────────┴──► publish_candidates (needs both; if verifier == success)
WIF auth ─► google-artifactregistry-auth ─► npm ping AR
verify --write release-version.json (identity re-validated)
sync-workspace-versions ─► verify-manifests ─► publish-isolation-guard
assert-channel-tags --mode preflight --registry AR
build waves (release-matrix --format waves, npm run build per member)
publish waves (publish-package-from-tag.mjs, FABRIC_RELEASE_PUBLISH=1,
candidate-<version>, --registry AR) [dry run: --dry-run]
assert-channel-tags --dist-tag candidate-<v> --mode final
──► published_consumer_validation (live-stack-published.yml, exact version)
──► promote_release_channel (release-promotion.cjs promote --evidence)
──► assert_release_channel_tags (every package, channel == version)
──► create_release_tag (release-tags.yml → fabric/<branch>/v<version>)
publish-packages-from-tag.yml (push tags fabric/**; dispatch tag=)
release_identity (tag-source: version/channel/registry from the tag; refuses a tag the train created)
──► verify_release_artifacts (strict, against the TAG tree)
──► publish_non_plugin_packages (same sequence; existing versions only candidate-tagged → idempotent)
──► published_consumer_validation ──► promote_release_channel (+ final assertion)
live-stack-published.yml WIF + AR credential; published_consumer = fresh project + .npmrc routing +
install/import/bin smoke of all 43 + §3.1 global-install smoke; live lanes install
the §3.1 CLIs from AR at the exact version (no --force), run the live stack
fix-npm-tags.yml dispatch-only AR dist-tag repair (audit | set), dry run unless dry_run=false
release-tags.yml fabric/<branch>/v<version>, fabric-release-* provenance, this repo onlyDerived 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.
2. What changed, per file
| File | Change |
|---|---|
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 |
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 |
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 |
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 |
scripts/verify-published-release.mjs | the fresh consumer gets .npmrc = @fabricstack:registry=<AR> (recorded in install.log) |
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) |
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 |
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) |
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 |
scripts/lib/registry-pin.cjs | doc: the flag is a no-op alias everywhere since WP-RN-07 |
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) |
.github/workflows/publish-npm-private.yml | NEW — see §1 |
.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) |
.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 |
.github/workflows/fix-npm-tags.yml | dispatch-only (push trigger registers it, job gated), WIF + helper, modes audit/set, dry_run default true |
.github/workflows/release-tags.yml, release-artifact-verifier.yml | headers for the private namespace / the consumer routing line (logic unchanged) |
.github/workflows/ci.yml | guard comment no longer spells the public registry host; release-tooling job comment describes the expected-green state |
.github/workflows/ar-smoke.yml | the dry-run assertion refuses any npmjs mention (stricter than the host literal) |
.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) |
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) |
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 |
packages/adapters/hooks/cli/package.json | //deferred-dependency note → docs/RELEASE-RUNBOOK.md § 7.1 |
| tests | see §4 |
3. Decisions recorded
**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.
- **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
stagingresolves7.0.1-staging.<sha>(FIX-001 commit-pinned prerelease) andmainresolves7.0.0. Nothing re-derives either. - **Registry is part of the release identity** (
registryfield) and named explicitly on everynpm view/dist-tag add/publishof the train, on top of G1 and the committed.npmrcrouting. - **Opt-in instead of a token gate**: AR credentials live in
$HOME/.npmrcafter the helper, so "credentials present" is not observable the wayNODE_AUTH_TOKENwas; publication now requiresFABRIC_RELEASE_PUBLISH=1, set only by the two publish workflows after WIF auth. A local invocation can never publish;--dry-runrehearses. - **Scope routing per call for global installs** (
--@fabricstack:registry=<AR>), not--registry <AR>: a global--registrywould 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. - **No external-plugin sync in the release path**:
sync-external-plugins.yml/sync-atlas-plugins.ymlpush public marketplace repositories; they are WP-RN-11's and are not called by either AR publish workflow (pinned byverify-release-artifacts.test.mjs). - **Build order = publication waves**, sequential within the publish job (
npm run build --if-present --workspace=<pkg>per member): several packages chain rootbuild:*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. - **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.
- **
ci.yml/ar-smoke.ymlwording**: 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.
4. Tests rewritten (before → after) and added
Before 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.
| Before (failing) | After (passing) — same invariant, private shape, equal or stricter |
|---|---|
| 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- |
| 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 |
| 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 |
| both release workflows still cover the whole inventory once comments are ignored | same name; surfaces retargeted (0 violations on both) |
| 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 |
| FIX-010: channel promotion is a separate job that depends on the published-consumer validation | unchanged assertions, new workflow |
| FIX-010: the channel-tag assertion and the release tag follow the validated promotion | unchanged assertions, new workflow |
| the release path runs the packed-artifact gate with no tolerance left | new workflow; also packages: '' |
| 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 |
Also 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.
New 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.
4b. Cross-platform test infrastructure (so the orchestrator's gate can be green on this machine)
The 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.
Incident 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.
5. Gates and runs
| Check | Result |
|---|---|
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/ |
… resolve --branch main … | 7.0.0 / latest / fabric/main/v7.0.0 / same registry; verify --version 7.0.0 --print registry → the AR URL |
… from-tag --tag babysitter/main/v6.0.3 | exit 1: does not match fabric/<branch>/v<version> |
verify-manifests --version 7.0.0 | All synchronized manifests are at release version 7.0.0. |
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) |
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) |
npm run verify:metadata | Metadata verification passed. Publishable inventory: 43 public packages. (coverage gate live on both surfaces) |
| every workflow YAML | js-yaml + yaml parse: 0 invalid (31 files) |
grep -rln registry.npmjs.org .github/workflows | nothing (live-stack-published retargeted too) |
| docs | markdownlint 0 errors, docs-link-check 0 failures, docs-style-check 0 issues |
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) |
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 |
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 |
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 |
| 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) |
6. Dry-run proof and what the registry answered
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).
**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.**
7. Verifier run(s)
| Run | Head | Result |
|---|---|---|
| 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 |
| 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) |
| 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 |
8. Honesty: what is proven and what waits for Gate B
- **Prepared + dry-run-proven, not done**: AC1 (an end-to-end staging release publishing all 43 packages to AR, moving
staging, creating onefabric/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. - **AC4 holds now**: the public repository's tag namespace shows no new tags (404 before and after; 0
fabric/tags). - **Windows**: 32 release-tooling tests are environmental here (
shfake-npmshims); CI'srelease-toolingjob 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. - **Reader 403 (review M6) — resolved 2026-10-04**: §6 keeps the original fail-closed transcript. The orchestrator verified with a fresh
gcloudaccess token that the human reader binding works:gcloud artifacts repositories get-iam-policy fabric-npm --location=us-central1 --format=jsonshowsroles/artifactregistry.readerforuser:tal@a5c.ai;npx -y google-artifactregistry-auth --repo-config=.npmrc --credential-config="$HOME/.npmrc"followed bynpm view @fabricstack/smoke --@fabricstack:registry=https://us-central1-npm.pkg.dev/boot-464019/fabric-npm/printed0.0.0-smoke.4; a direct bearer-tokenGETof the package document answered200. 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. - **Workflow state (review M2, amended by review-2 M8) — 2026-10-04**:
live-stack-published.yml,release-tags.yml,publish-packages-from-tag.ymlandfix-npm-tags.ymlweredisabled_manuallyona5c-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 onmain/stagingareworkflow_call/dispatch-only or trigger onbabysitter/**tags — **but a MANUAL dispatch offix-npm-tags.ymlwithout--ref, or with--ref main, or with--ref stagingbefore the merge, would run the legacy npmjs copy**, and that copy is armed:mainandstagingcarry the same blob (git rev-parse origin/staging:.github/workflows/fix-npm-tags.yml origin/main:…→44112b847twice), whoseworkflow_dispatchinputdry_rundefaults tofalse, which checks outstaging, usesregistry-url: https://registry.npmjs.org/withNODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}, and whoseelsebranch runsscripts/fix-broken-latest-tags.mjs --fix— a livenpm dist-tag add @a5c-ai/<pkg>@<v> latestagainst the PUBLIC packages with the still org-wideNPM_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.ymlwas 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.yml352401978,release-tags.yml352401989,publish-packages-from-tag.yml352401984 — stayactive, as doesrelease-artifact-verifier.yml352401988. The orchestrator re-enablesfix-npm-tags.ymlimmediately after PR #4 merges tostaging, as part of Gate B, once M7 is verified. Do not dispatchfix-npm-tags.ymluntil PR #4 is onstagingandmainand M7 is verified. Everygh workflow runin the runbook carries--reffor the same reason (review M3), and the runbook now states that--ref stagingis itself the legacy copy until the merge. - **Not done by design**:
sync-external-plugins.yml/sync-atlas-plugins.yml(public marketplace repos, WP-RN-11);documented_installlane oflive-stack-published.ymlstill exercises the public marketplace bootstrap and stays channel-gated (skipped pre-promotion; WP-RN-11);scripts/release-notes.mjs,bump-version.mjs,rollback-release.shlegacy helpers untouched (no workflow invokes them); thefabricstacknpmjs org (G4) remains an owner action. **G3 / review M7 — owner action, required BEFORE anyone dispatchesfix-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 secretNPM_TOKENis 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 onmainandstagingreference it; the owner restricts its repository selection in the GitHub UI. Verification:gh api orgs/a5c-ai/actions/secrets/NPM_TOKEN --jq .visibility→selected, andfabricabsent fromgh api orgs/a5c-ai/actions/secrets/NPM_TOKEN/repositories --jq '.repositories[].name'. - **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 threeopenai/gpt-5.5live lanes red — an invalidOPENAI_API_KEYorganization secret,invalid_api_key, plus Claude Code 2.1.2xx's unauthenticated/api/hellopreflight 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 onfix/consumer-only-gate:promote_release_channel(branch train and from-tag recovery) depends on the callee outputsconsumer_resultandpromotableand evaluates with!cancelled()while checking every upstream result explicitly;record_validationrecords the lanes with--live-lanes-gatederived from the repository/organization variableFABRIC_LIVE_LANES_GATE(unset orinformational→ informational, listed underinformationalFailures;required→ the full gate, and then the called workflow's aggregate result must be green too; the script's own default isrequired); no lane iscontinue-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 validOPENAI_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. - **No guard or test weakened**: the flipped fixture became a negative;
--private-trainis accepted as a no-op; every rewritten assertion is listed in §4 with its stricter form.
9. Release-machinery review fixes (2026-10-04)
The 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.
| Finding | What changed | Proof |
|---|---|---|
**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 |
**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[] \ |
**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) |
**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\ |
| **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) |
| **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 |
**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 |
9.1 What the rehearsal job found on its first runs — three defects of the real train
The 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.
| Run (head) | Failure | Root cause | Fix |
|---|---|---|---|
| 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 |
| 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 |
| 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) |
9.2 The green rehearsal and the CI state of the final head
**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).
**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).
**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).
Nice-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).
9.3 Second release-machinery review (2026-10-04, score 91) — M8 and nice-to-haves
The 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.
| Finding | What changed | Proof |
|---|---|---|
**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 |
| 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) |
| 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 |
| 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 |
Not taken in this revision (recorded for the follow-up list): ci.yml re-cats 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.