Agentic AI Atlasby a5c.ai
OverviewWikiGraphFor AgentsEdgesSearchWorkspace
/
GitHubDocsDiscord
iiRecord
Agentic AI Atlas · Release recovery runbook — candidate publish, published-consumer validation, channel promotion
page:docs-release-recovery-runbooka5c.ai
Search record views/
Record · tabs

Available views

II.Record viewspp. 1 - 1
overviewarticlejsongraph
II.
Page JSON

page:docs-release-recovery-runbook

Structured · live

Release recovery runbook — candidate publish, published-consumer validation, channel promotion json

Inspect the normalized record payload exactly as the atlas UI reads it.

File · wiki/docs/release-recovery-runbook.mdCluster · wiki
Record JSON
{
  "id": "page:docs-release-recovery-runbook",
  "_kind": "Page",
  "_file": "wiki/docs/release-recovery-runbook.md",
  "_cluster": "wiki",
  "attributes": {
    "nodeKind": "Page",
    "sourcePath": "docs/release-recovery-runbook.md",
    "sourceKind": "repo-docs",
    "title": "Release recovery runbook — candidate publish, published-consumer validation, channel promotion",
    "displayName": "Release recovery runbook — candidate publish, published-consumer validation, channel promotion",
    "slug": "docs/release-recovery-runbook",
    "articlePath": "wiki/docs/release-recovery-runbook.md",
    "article": "\n# Release recovery runbook — candidate publish, published-consumer validation, channel promotion\n\n> **Superseded (WP-RN-07, 2026-10-03).** This is the PUBLIC train's runbook, kept as the\n> historical record of the 2026-08-13 recovery design. It names `@a5c-ai/*` packages,\n> `babysitter/<branch>/v*` tags, `NPM_TOKEN` and the public registry — none of which the\n> private train uses. The operating runbook of `a5c-ai/fabric` is\n> [docs/RELEASE-RUNBOOK.md](./RELEASE-RUNBOOK.md) (Artifact Registry, `fabric/<branch>/v*`\n> tags, WIF authentication); the FIX-005 follow-up in § 3.8 below moved to its § 7.1.\n\nStatus: Wave 5 / Wave 6 operating procedure for the remediation program (`fix/remediation-program`)\nAudience: the release owner (Yossi Elkrief) and package owners executing the recovery release\nIncident context: [docs/release-incident-2026-08-13.md](./release-incident-2026-08-13.md)\nRelease model reference: [docs/release-pipeline.md](./release-pipeline.md)\n\n> **Every command in the \"recovery sequence\" sections below mutates external\n> registry state or a git remote and therefore requires explicit release-owner\n> approval before it is run.** None of them has been executed. The Wave 5\n> rehearsal recorded in the appendix ran only the local, read-only gates.\n\n## 0. What this runbook encodes\n\nThe 2026-08-13 incident left 37 of 43 public packages with a stale `latest=6.0.0`\nwhile 6.0.3 artifacts existed untagged, one package (`@a5c-ai/hooks-adapter-genty`)\nunpublished, and two known-broken artifacts on the registry\n(`@a5c-ai/tasks-adapter@6.0.0`, `@a5c-ai/extensions-adapter@6.0.0`).\n\nThe remediation program closed the code path that caused it. Recovery is now a\nfixed, tool-enforced sequence:\n\n```\nresolve ONE immutable version\n  -> synchronize every manifest to it\n  -> local gates (fast checks + packed-artifact matrix)\n  -> publish dependency-ordered candidates under candidate-<version> ONLY\n  -> published-consumer validation of that EXACT version from npm\n  -> promote the channel tag (the only dist-tag mutation)\n  -> assert every public package's channel tag equals that version\n  -> create the immutable release tag\n  -> (separate approval) npm deprecate notices for the known-broken artifacts\n```\n\nPublication no longer promotes: `scripts/publish-package-from-tag.mjs` writes only\n`candidate-<version>` and refuses to touch `latest` / `staging` / `develop`\n(FIX-010). `scripts/release-promotion.cjs promote` is the single dist-tag\nmutation in the pipeline, and it refuses to run without machine-readable\nvalidation evidence naming that exact version with every required check\nsuccessful.\n\n## 1. Preconditions (must all be true before step 2)\n\n- [ ] The remediation branch is merged (or the release is cut from a ref that\n      contains commits `25c8a959f..1eab5d4ad`, FIX-001..FIX-016).\n- [ ] The registry snapshot in `docs/release-incident-2026-08-13.md` has been\n      **refreshed** — npm state is time-sensitive.\n- [ ] A release version is chosen that is unused across **all 43** public\n      packages (6.0.4 or later; verify against the refreshed snapshot).\n- [ ] `NPM_TOKEN` for the publishing account is available to the workflows, and\n      the release owner has approved the registry mutations in this document.\n\nRefresh the snapshot and prove the candidate version is unused (read-only):\n\n```bash\n# Authoritative inventory — 43 public packages, derived from tracked manifests.\nnpm run inventory:packages\n\n# The package name list every loop below iterates (43 lines).\nnpm run --silent inventory:packages | node -e 'let s=\"\";process.stdin.on(\"data\",c=>s+=c).on(\"end\",()=>JSON.parse(s).forEach(p=>console.log(p.name)))' > /tmp/public-packages.txt\nwc -l < /tmp/public-packages.txt   # must be 43\n\n# Refresh dist-tags/versions for every public package (read-only `npm view`).\nmkdir -p artifacts\nwhile read -r pkg; do\n  echo \"== $pkg\"\n  npm view \"$pkg\" dist-tags versions time.modified --json\ndone < /tmp/public-packages.txt | tee \"artifacts/registry-snapshot-$(date -u +%Y%m%dT%H%M%SZ).txt\"\n\n# Prove the selected version is unused everywhere (must print no ALREADY PUBLISHED line).\nRELEASE_VERSION=6.0.4\nwhile read -r pkg; do\n  npm view \"$pkg@$RELEASE_VERSION\" version >/dev/null 2>&1 && echo \"ALREADY PUBLISHED: $pkg@$RELEASE_VERSION\"\ndone < /tmp/public-packages.txt\n```\n\n## 2. Local pre-flight gates (safe to run; run them all)\n\nThese are the gates the Wave 5 rehearsal executed (appendix). Run them on the\nexact ref that will be released.\n\n```bash\n# Fast repository checks\nnpm run verify:metadata\nnpm run guard:packages\nnpm run test:binary-renames\nnpm run test:release-tooling\n\n# Full packed-artifact matrix over all 43 public packages (slow; the release gate).\n# Per-package: build -> npm pack --ignore-scripts -> surface inventory -> shebangs\n#              -> clean temp-consumer install -> root+subpath imports -> consumer\n#              typecheck -> bin smoke -> the package's own verify:release gate.\nnpm run verify:release-artifacts\n# One package at a time (what the rehearsal used):\nnode scripts/verify-release-artifacts.mjs --package @a5c-ai/tasks-adapter\n\n# Wave-5 named behaviors\nnpm run test:packaged-surface-parity --workspace=@a5c-ai/tasks-adapter        # tasks root + MCP subpath imports\nnpm run test:packaged-surface-parity --workspace=@a5c-ai/extensions-adapter   # both extensions bins\nnode --test scripts/__tests__/hooks-atlas-ownership.test.mjs                  # hooks leaves/CLI isolated Atlas resolution\nnode --test scripts/__tests__/hooks-adapter-genty-packed.test.mjs             # the previously unpublished hooks leaf\nnpm run test:babysitter-metapackage                                          # babysitter CLI exit-code propagation\nnpm run test:node-engine-floor --workspace=@a5c-ai/adapters-gateway          # gateway Node engine floor\nnpm exec --yes --package=vitest -- vitest run --config packages/adapters/core/vitest.config.ts \\\n  packages/adapters/core/tests/pty.test.ts packages/adapters/core/tests/pty-consumer.packaged.test.ts\n```\n\nThe publication waves the release will follow are derived, never hand-written:\n\n```bash\nnode scripts/release-matrix.cjs --group all-publishable --format waves\nnode scripts/release-matrix.cjs --group hooks-leaves --format workspaces\n```\n\n`@a5c-ai/hooks-adapter-genty` must publish before `@a5c-ai/hooks-adapter-cli`;\nthe derived waves already encode that order.\n\n## 3. Recovery sequence — APPROVAL REQUIRED FROM HERE ON\n\n### 3.1 Resolve and record the one release version\n\n```bash\nRELEASE_BRANCH=main            # or staging / develop\nSHORT_SHA=$(git rev-parse --short=12 HEAD)\n\n# THE authoritative release identity, resolved exactly once. The JSON payload\n# carries branch, releaseVersion, distTag, releaseTag and shortSha.\nnode scripts/release-version.cjs resolve \\\n  --branch \"$RELEASE_BRANCH\" \\\n  --sha \"$SHORT_SHA\" \\\n  --commit \"$(git rev-parse HEAD)\" \\\n  --write release-version.json\n\nRELEASE_VERSION=\"$(node scripts/release-version.cjs resolve \\\n  --branch \"$RELEASE_BRANCH\" --sha \"$SHORT_SHA\" --print releaseVersion)\"\nRELEASE_BRANCH_DIST_TAG=\"$(node scripts/release-version.cjs resolve \\\n  --branch \"$RELEASE_BRANCH\" --sha \"$SHORT_SHA\" --print distTag)\"   # latest | staging | develop\n\n# Validate an explicitly chosen version against the resolver instead of\n# trusting a manifest.\nnode scripts/release-version.cjs verify \\\n  --branch \"$RELEASE_BRANCH\" --sha \"$SHORT_SHA\" --version \"$RELEASE_VERSION\"\n\n# The non-production dist-tag every candidate publishes under.\nCANDIDATE_DIST_TAG=\"$(node scripts/release-promotion.cjs candidate-tag --version \"$RELEASE_VERSION\")\"\necho \"$CANDIDATE_DIST_TAG\"   # -> candidate-6.0.4\n```\n\nThe resolver derives the release version from the **root** `package.json` plus the\nbranch channel, so bumping to the approved recovery version means bumping the\nroot manifest first (`node scripts/bump-version.mjs` / an approved version-bump\nPR) and re-running `resolve`. `--root-version <v>` overrides it for a rehearsal.\n\n### 3.2 Synchronize every manifest, then prove it\n\n```bash\nnode scripts/sync-workspace-versions.mjs --version \"$RELEASE_VERSION\"\n# Hard-fails unless EVERY manifest version and every internal dependency pin\n# is exactly $RELEASE_VERSION. This is the check the incident lacked.\nnode scripts/release-version.cjs verify-manifests --version \"$RELEASE_VERSION\"\n\n# Refuses a release that would move any channel tag backward (read-only).\nnode scripts/release-version.cjs assert-channel-tags \\\n  --version \"$RELEASE_VERSION\" \\\n  --dist-tag \"$RELEASE_BRANCH_DIST_TAG\" \\\n  --mode preflight\n```\n\n`$RELEASE_BRANCH_DIST_TAG` (resolved in 3.1) is `latest` for `main`, `staging`\nfor `staging`, `develop` for `develop`.\n\n### 3.3 Publish dependency-ordered candidates (registry mutation #1)\n\n**Preferred path — let the workflow do it.** Pushing the release branch runs\n`.github/workflows/publish.yml`, which performs 3.1–3.6 in order. The manual\nequivalent below exists for recovery only.\n\n**Recovery path — from an immutable release tag.** Create and push the tag, then\n`.github/workflows/publish-packages-from-tag.yml` derives the exact version from\nthe tag name, re-synchronizes every manifest, and publishes the derived waves:\n\n```bash\n# Idempotent local annotated tag carrying release provenance. Never moves an\n# existing release tag, never pushes.\nnode scripts/release-version.cjs ensure-tag \\\n  --version \"$RELEASE_VERSION\" \\\n  --branch \"$RELEASE_BRANCH\" \\\n  --commit \"$(git rev-parse HEAD)\" \\\n  --source manual\n\n# APPROVAL REQUIRED: pushing the tag starts the recovery publication.\ngit push origin \"babysitter/$RELEASE_BRANCH/v$RELEASE_VERSION\"\n\n# Or dispatch the recovery workflow against an existing tag (--ref is mandatory:\n# without it gh runs the DEFAULT branch's copy of the workflow file):\ngh workflow run publish-packages-from-tag.yml \\\n  --ref \"babysitter/$RELEASE_BRANCH/v$RELEASE_VERSION\" \\\n  -f tag=\"babysitter/$RELEASE_BRANCH/v$RELEASE_VERSION\"\n```\n\nManual per-package publication (only if the workflow is unavailable; run the\nwaves in the derived order, never ad hoc):\n\n```bash\nnpm ci --force\nnpm run build   # the publish helper is invoked with --skip-build\n\nnode scripts/release-matrix.cjs --group all-publishable --format waves | while read -r wave; do\n  for workspace in $wave; do\n    # APPROVAL REQUIRED: this is `npm publish --tag candidate-<version>`.\n    node scripts/publish-package-from-tag.mjs \\\n      --workspace=\"$workspace\" \\\n      --release-version=\"$RELEASE_VERSION\" \\\n      --skip-build\n  done\ndone\n```\n\n`publish-package-from-tag.mjs` hard-fails when the workspace manifest version,\nthe tag version and the requested version disagree; it runs each package's\n`verify:release` gate before `npm publish --ignore-scripts`; it publishes under\n`candidate-<version>` and **never** writes a channel tag.\n\nAfter this step: every public package is installable at the exact version and\nunder `candidate-<version>`. No channel has moved. `latest` still resolves to the\nprevious (stale) release — that is intended.\n\n### 3.4 Published-consumer validation of the exact version (read-only against npm)\n\n```bash\n# Workflow path (what publish.yml / publish-packages-from-tag.yml call):\ngh workflow run live-stack-published.yml \\\n  --ref \"babysitter/$RELEASE_BRANCH/v$RELEASE_VERSION\" \\\n  -f release_version=\"$RELEASE_VERSION\" \\\n  -f candidate_dist_tag=\"$CANDIDATE_DIST_TAG\" \\\n  -f ref=\"babysitter/$RELEASE_BRANCH/v$RELEASE_VERSION\"\n# `channel` is deliberately left empty pre-promotion: the channel still resolves\n# the PREVIOUS release until 3.5.\n\n# Local equivalent of the published_consumer job (installs all 43 packages at the\n# exact version into a clean consumer, imports every root + exported runtime\n# subpath, smokes every declared bin):\nnode scripts/verify-published-release.mjs --version \"$RELEASE_VERSION\"\n```\n\nGate the promotion on the evidence artifact, not on a green checkmark:\n\n```bash\ngh run download <run-id> --name published-consumer-validation --dir artifacts/published-consumer\n\n# Proves the exact version passed every REQUIRED check (no registry access).\nnode scripts/release-promotion.cjs assert-validated \\\n  --version \"$RELEASE_VERSION\" \\\n  --dist-tag \"$RELEASE_BRANCH_DIST_TAG\" \\\n  --evidence artifacts/published-consumer/validation.json\n```\n\nIf validation fails, **stop**. The candidate stays installable by exact version\nand under `candidate-<version>` for diagnosis; the channel is untouched. Fix\nforward with a new patch version — never overwrite, never unpublish.\n\n### 3.5 Promote the channel (registry mutation #2 — the only dist-tag write)\n\n```bash\n# Rehearse first: prints the per-package dist-tag plan and mutates nothing.\nnode scripts/release-promotion.cjs promote \\\n  --version \"$RELEASE_VERSION\" \\\n  --dist-tag \"$RELEASE_BRANCH_DIST_TAG\" \\\n  --evidence artifacts/published-consumer/validation.json \\\n  --dry-run\n\n# APPROVAL REQUIRED: moves every public package's channel tag to the validated\n# version and re-asserts it.\nnode scripts/release-promotion.cjs promote \\\n  --version \"$RELEASE_VERSION\" \\\n  --dist-tag \"$RELEASE_BRANCH_DIST_TAG\" \\\n  --evidence artifacts/published-consumer/validation.json\n```\n\nThis is the step that repairs the incident: it moves all 37 stale `latest` tags\n(and the 5 already-current ones, idempotently) to one tested version.\n\n### 3.6 Assert the registry end state over the FULL inventory\n\n```bash\nnode scripts/release-version.cjs assert-channel-tags \\\n  --version \"$RELEASE_VERSION\" \\\n  --dist-tag \"$RELEASE_BRANCH_DIST_TAG\"\n```\n\nThe release is not complete until this passes for **all 43** packages — never a\nsample. Re-running it is the idempotency check: a repeated release passes and\nmoves nothing.\n\n### 3.7 Create the immutable release tag (if not already created in 3.3)\n\n```bash\ngh workflow run release-tags.yml \\\n  --ref \"$RELEASE_BRANCH\" \\\n  -f release_version=\"$RELEASE_VERSION\" \\\n  -f branch=\"$RELEASE_BRANCH\"\n```\n\n`release-tags.yml` accepts the version rather than re-deriving it, and records\nprovenance in the annotated tag body\n(`node scripts/release-version.cjs tag-message ...`).\n\n### 3.8 Pin the newly published Genty hooks leaf in the hooks CLI — REQUIRED FOLLOW-UP\n\n`@a5c-ai/hooks-adapter-genty` has never existed on npm, so\n`packages/adapters/hooks/cli/package.json` deliberately does **not** pin it: an\nexact pin on a version that is not in the registry fails both clean-consumer\nverification and the exact-internal-dependency registry gate in\n`scripts/publish-package-from-tag.mjs`. The deferral is recorded in the manifest\nitself under the `//deferred-dependency` key.\n\nThe moment 3.4 publishes the Genty leaf, that constraint disappears and the pin\nmust be added — otherwise `@a5c-ai/hooks-adapter-cli` keeps resolving the Genty\nadapter only through workspace hoisting, which is the FIX-006 defect class:\n\n```bash\n# AFTER @a5c-ai/hooks-adapter-genty@$RELEASE_VERSION exists on the registry:\nnpm view \"@a5c-ai/hooks-adapter-genty@$RELEASE_VERSION\" version   # must succeed\n\n# 1. Add the exact pin next to the other eleven leaves and delete the\n#    \"//deferred-dependency\" key from packages/adapters/hooks/cli/package.json.\n# 2. Regenerate the lockfile from the repository root (Node 22 toolchain):\nnpm install --package-lock-only --ignore-scripts\n\n# 3. Re-run the gates that now cover it:\nnode --test scripts/__tests__/publish-package-from-tag.test.mjs\nnode --test scripts/__tests__/release-matrix.test.mjs\nnode --test scripts/__tests__/dependency-ownership.test.mjs\nnode scripts/verify-release-artifacts.mjs --package @a5c-ai/hooks-adapter-cli\n```\n\nOnce the pin is in place, the publish helper's exact-internal-dependency\nregistry check covers the Genty leaf for the CLI on every subsequent release,\nwhich is what FIX-005's remaining two acceptance criteria ask for. Record the\ncommit that adds the pin in the release issue.\n\n## 4. Deprecation notices — SEPARATE, EXPLICIT APPROVAL\n\nThese are **not** part of the recovery release. Each requires its own\nrelease-owner decision, recorded with command, actor, timestamp and result in\n`docs/release-incident-2026-08-13.md` or the recovery release issue. Do **not**\nunpublish anything as ordinary remediation.\n\n```bash\n# FIX-002 — tasks adapter 6.0.0 omits its MCP SDK runtime dependency.\nnpm deprecate @a5c-ai/tasks-adapter@6.0.0 \\\n  \"6.0.0 is missing its @modelcontextprotocol/sdk runtime dependency and fails on import from a clean consumer (FIX-002). Upgrade to 6.0.4 or later.\"\n\n# FIX-004 — extensions adapter 6.0.0 tarball is missing its declared bin target.\nnpm deprecate @a5c-ai/extensions-adapter@6.0.0 \\\n  \"6.0.0 ships without the declared dist/extension-adapter.js bin target; both binaries are unusable (FIX-004). Upgrade to 6.0.4 or later.\"\n\n# Verify (read-only):\nnpm view @a5c-ai/tasks-adapter@6.0.0 deprecated\nnpm view @a5c-ai/extensions-adapter@6.0.0 deprecated\n\n# Undo, if a notice was applied in error:\nnpm deprecate @a5c-ai/tasks-adapter@6.0.0 \"\"\n```\n\nDeprecation is advisory: it does not remove the version and does not change any\ndist-tag. Apply it only **after** 3.6 succeeds, so the replacement the message\nnames actually resolves.\n\n## 5. Evidence to record in the release issue\n\n- The refreshed pre-release registry snapshot (section 1).\n- Local gate results (section 2), including the `artifacts/release-verifier/`\n  per-package reports and `summary.json`.\n- The publish workflow run URL and the derived publication waves.\n- The `published-consumer-validation` artifact (`validation.json`) for the exact\n  version.\n- The `release-promotion.cjs promote` output and the\n  `release-promotion-evidence` artifact.\n- The final `assert-channel-tags` output over all 43 packages.\n- Tarball checksums for the released artifacts (`shasum -a 512` of each\n  `npm pack` output, or the registry `dist.integrity` values).\n- Any deprecation decisions with approver and timestamp.\n\n## Appendix — Wave 5 local candidate rehearsal (2026-08-13)\n\nExecuted on `fix/remediation-program` at `1eab5d4ad`. **No registry state was\nmutated**: nothing was published, deprecated, dist-tagged or unpublished, and\nnothing was pushed. This rehearsal proves the artifacts a candidate publication\n*would* upload are usable from a clean consumer.\n\nDeliberately **not** executed (they require registry state that does not exist\nyet, or release-owner approval): sections 3.3–3.7 and section 4, and\n`scripts/verify-published-release.mjs`, which installs from the registry and can\nonly run after the candidate versions are published.\n\n### Packed-artifact matrix (`node scripts/verify-release-artifacts.mjs --package <name>`)\n\nPer-package steps: `build`, `pack`, `surfaces`, `shebangs`, `install`, `imports`,\n`typecheck`, `bins`, and `verifyRelease` where the package declares one.\n\nCommand: `node scripts/verify-release-artifacts.mjs --package <name>` (12 packages\nin one dependency-ordered invocation). Reports: `artifacts/release-verifier/`.\n\n| Package | Result | Failing step |\n| --- | --- | --- |\n| `@a5c-ai/atlas` | PASS | — |\n| `@a5c-ai/tasks-adapter` | PASS (FIX-002/FIX-012) | — |\n| `@a5c-ai/extensions-adapter` | PASS (FIX-004) | — |\n| `@a5c-ai/comm-adapter` | PASS (FIX-009) | — |\n| `@a5c-ai/adapters-gateway` | PASS (FIX-008) | — |\n| `@a5c-ai/hooks-adapter-genty` | PASS (FIX-005) | — |\n| `@a5c-ai/hooks-adapter-claude` | PASS (FIX-006) | — |\n| `@a5c-ai/hooks-adapter-codex` | PASS (FIX-006) | — |\n| `@a5c-ai/hooks-adapter-gemini` | PASS (FIX-006) | — |\n| `@a5c-ai/hooks-adapter-cli` | PASS (FIX-005/FIX-006) | — |\n| `@a5c-ai/babysitter-sdk` | PASS | — |\n| `@a5c-ai/babysitter` | PASS (FIX-007) — after the gate defect below was fixed | — |\n\nTotals: 12 passed, 0 failed of 12.\n\n#### Full 43-package matrix (2026-08-14)\n\nRerun after the release path stopped tolerating known packed-artifact failures\n(`.github/workflows/publish.yml` now passes `allow_known_failures: 'false'`;\n`scripts/known-package-defects.json` `packedArtifact` is empty and stays empty).\n\nCommand: `node scripts/verify-release-artifacts.mjs` (no `--package`, no\n`--allow-known-failures`). Running the complete matrix for the first time\nsurfaced four consumer-facing defects that the 12-package rehearsal never\ncovered. All four are fixed at the source — none is allowlisted:\n\n| Package | Defect found by the strict matrix | Fix |\n| --- | --- | --- |\n| `@a5c-ai/genty-platform` | published `./runtime` subpath resolved to a declaration file with no top-level export, so a consumer `import` failed to typecheck (TS2306) | `export {}` in the intentionally-empty barrel |\n| `@a5c-ai/transport-adapter` | `adapters-transport-proxy --help` (and the deprecated `adapters-proxy`) answered \"Error: Missing targetProvider\" with exit 1 | real usage text on stdout, exit 0, pinned by `tests/bin-smoke.test.ts` |\n| `@a5c-ai/channels-adapter` | `adapters-channels --help` was treated as the config path and answered \"invalid config\" with exit 1 | `--help`/`--version` handled before the config path, pinned by `src/__tests__/cli-bin-smoke.test.ts` |\n| `@a5c-ai/kradle` | `kradle-server --help` started the HTTP server and hung until the gate timed it out | both flags answered before the server module is imported, pinned by `tests/bin-smoke.test.js` |\n\nOne gate correction, in the same class as the bin-only-metapackage correction\nabove: `@a5c-ai/genty-ui` is a React Native component library whose entrypoint\ntransitively evaluates react-native's Flow-typed `index.js`. No publishing\nchange makes that importable by bare Node. The package now declares its runtime\nthrough the standard top-level `\"react-native\"` resolution field, and the\nverifier skips exactly that one step for packages carrying that declaration —\n`surfaces`, `install`, `typecheck`, `bins` and `verify:release` still run, and\ndirect-dependency ownership is audited separately for all 43 packages.\n\n#### Release-gate defect found by this rehearsal, and fixed\n\nThe first pass failed `@a5c-ai/babysitter` at the `imports` step:\n\n```\nError: Cannot find package '<consumer>/node_modules/@a5c-ai/babysitter/index.js'\n  imported from <consumer>/fix011-import-check.mjs   (ERR_MODULE_NOT_FOUND)\n```\n\n`@a5c-ai/babysitter` is a **bin-only metapackage**: no `main`, no `module`, no\n`exports`, `files` = `bin/` + `README.md`. Its `build`, `pack`, `surfaces`,\n`shebangs` and `install` steps all passed — the failure was the gate asserting\nsomething the package never promised.\n\nRoot cause: `scripts/lib/package-surface.cjs` `runtimeImportSpecs()`\nsynthesized a root import specifier for any manifest without an `exports`\nfield, without first checking that the package declares an importable root, so\nNode fell back to legacy main resolution and looked for an `index.js` this\npackage deliberately never ships.\n\nWhy it was release-blocking, not cosmetic: `scripts/verify-published-release.mjs`\n(FIX-010, the post-publication gate whose evidence unlocks promotion) imports\nthe **same** `runtimeImportSpecs`, so the `published_consumer` job would have\nfailed on `@a5c-ai/babysitter` for the same reason and `promote_release_channel`\nwould never have run.\n\nFix (commit \"fix(release-tooling): package-surface omits root import spec for\nbin-only packages\" on `fix/remediation-program`):\n\n- `runtimeImportSpecs()` returns no root spec when the manifest declares no\n  `main`, no `module` and no `exports`;\n- a new shared `consumerSurfaceProblem()` keeps \"nothing to check\" from becoming\n  a pass: a package with **neither** an importable root **nor** a bin now fails\n  loudly in both gates (`surfaces` step pre-publication, `root-import` check\n  post-publication);\n- two fixtures under `scripts/__tests__/fixtures/fix011/`\n  (`bin-only-metapackage`, `no-consumer-surface`) and four regression tests —\n  two in `scripts/__tests__/verify-release-artifacts.test.mjs`, two in\n  `scripts/__tests__/verify-published-release.test.mjs` — all proven RED against\n  the pre-fix source and GREEN after.\n\nAfter the fix `@a5c-ai/babysitter` passes with `imports` recorded as skipped\n(`package declares no importable root or exported runtime subpath`) and its\n`babysitter` bin smoked.\n\n### Wave-5 named behaviors\n\nEvery behavior named in the Wave 5 checklist was confirmed by an executable\ngate, not by inspection.\n\n| Wave 5 behavior | Gate executed | Result |\n| --- | --- | --- |\n| Tasks root + MCP subpath imports | `npm run test:packaged-surface-parity --workspace=@a5c-ai/tasks-adapter` | PASS — packed tarball installs, every `exports` subpath imports and typechecks in a clean consumer, tarball matches the documented surface |\n| Both extensions bins | `npm run test:packaged-surface-parity --workspace=@a5c-ai/extensions-adapter` | PASS — `adapters-extensions` and `extensions-adapter` both run from a clean install; the legacy bin emits its deprecation warning and returns the delegated exit code |\n| All hooks leaves + hooks CLI resolution | `node --test scripts/__tests__/hooks-atlas-ownership.test.mjs` | PASS 6/6 — every Atlas-importing hooks package declares it directly; a leaf resolves Atlas through its OWN dependency in a **non-hoisted** consumer; the CLI installs, imports and runs from its tarball non-hoisted |\n| Previously unpublished Genty hooks leaf | `node --test scripts/__tests__/hooks-adapter-genty-packed.test.mjs` | PASS 1/1 — installs and imports from its packed tarball in a clean consumer |\n| Babysitter CLI exit behavior | `npm run test:babysitter-metapackage` | PASS 6/6 — packed shim is byte-identical to the workspace shim; exits 0 on resolve 0, **7 on resolve 7**, 1 with exactly one handled error on rejection |\n| Gateway minimum Node | `npm run test:node-engine-floor --workspace=@a5c-ai/adapters-gateway` | PASS 5/5 — manifest floor == runtime constant == README == CI matrix; `node:sqlite` reached only via `dist/runtime/node-sqlite.js`; below-floor runtimes rejected with the actionable diagnostic |\n| Interactive PTY behavior | `vitest run --config packages/adapters/core/vitest.config.ts tests/pty.test.ts tests/pty-consumer.packaged.test.ts` | PASS 16/16 — required vs. preferred contract holds; an installed-but-broken node-pty fails loudly in both modes instead of silently degrading to pipes |\n\n### Rehearsal verdict\n\n**Ready.** All 12 verified tarballs install, import, typecheck and expose their\nbins from a clean consumer, and every Wave 5 behavior is green. The one gate\ndefect this rehearsal exposed (the bin-only-metapackage root-import false\npositive, which would have blocked channel promotion in FIX-010) was fixed with\nregression coverage in both gates. Repository gates re-run green afterwards:\n`verify:metadata`, `guard:packages`, `test:binary-renames`, and\n`test:release-tooling` (107/107).\n\nNothing on the registry has changed. Section 3 remains unexecuted and still\nrequires release-owner approval.\n",
    "documents": []
  },
  "outgoingEdges": [],
  "incomingEdges": [
    {
      "from": "page:docs",
      "to": "page:docs-release-recovery-runbook",
      "kind": "contains_page"
    }
  ]
}

Shortcuts

Back to overview
Open graph tab