Manual Test Runbook — F11: Module Versioning + Private Registry
Owner: Sagar | Time: ~6 min (Parts A + B offline) / +5 min (optional Part C live release) | Sandbox: none required (git + metadata only)
Overview
F11 versions the SnowOps module library and publishes it as a private
registry: each module is a git tag (<module>/v<version>) recorded in
modules/registry.json, with a per-module CHANGELOG.md. Consumers pin a tag in
their source. Three pieces:
apps/module-registry/— the TS tool. Pure core (validate / index / release plan / pin audit) behind aCollectorseam (FsRegistryCollectorreads the manifest + CHANGELOGs +git tag;FixtureCollectorfor tests).modules/registry.json+ per-moduleCHANGELOG.md— the source of truth..github/workflows/module-release.yml— on merge to main, validates then tags + releases every module whose manifest version isn't tagged yet.
Convention + pin strategy: docs/conventions/module-versioning.md.
Parts A + B verify validation, index-building, release planning, and the pin audit offline. Part C optionally exercises a real tag + GitHub Release.
Part A — Tool + registry validation (offline, ~4 min)
A1. Build + typecheck + unit tests
Expect: 3 suites, 27 tests pass — semver ordering, validation (CHANGELOG
sync, duplicate name, bad semver, version regression), release planning, the
consumer pin audit (unpinned / ref-mismatch / unknown-version), the renderer,
and a guard that loads the real committed modules/registry.json + every
CHANGELOG and asserts the registry is internally valid and in sync.
A2. Validate the live registry + build the index
cd ../..
node apps/module-registry/dist/index.js \
--manifest modules/registry.json \
--out-dir /tmp/f11 --now 2026-06-01T00:00:00.000Z --fail-on-issues true
echo "exit=$?"
cat /tmp/f11/registry-index.json | node -e "const i=require('/dev/stdin');console.log(i.modules.map(m=>m.name+' -> '+m.tag).join('\n'))" 2>/dev/null \
|| node -e "const i=require('/tmp/f11/registry-index.json');console.log(i.modules.map(m=>m.name+' -> '+m.tag).join('\n'))"
Expect: stderr registry: OK — 10 modules, 0 validation error(s), 0 pin
finding(s), 10 to release; exit 0. The index lists 10 modules with tags
contracts/v0.1.0, baseline/v1.0.0, … audit-log-archive/v1.0.0. (All 10 are
"to release" until the first tags exist — see Part C.)
A3. A broken registry is caught
Temporarily desync a CHANGELOG and re-run:
node -e "const fs=require('fs');const p='modules/registry.json';const m=JSON.parse(fs.readFileSync(p));m.modules.find(x=>x.name==='acr').version='2.0.0';fs.writeFileSync('/tmp/bad-registry.json',JSON.stringify(m,null,2))"
node apps/module-registry/dist/index.js --manifest /tmp/bad-registry.json --repo-root . \
--out-dir /tmp/f11bad --fail-on-issues true; echo "exit=$?"
Expect: exit 2; stderr shows ✗ [changelog-out-of-sync] acr: CHANGELOG top
version '1.0.0' != manifest version '2.0.0'. (No repo files were modified — the
edit went to /tmp.)
Part B — Consumer pin audit (offline, ~1 min)
B1. A correctly-pinned consumer passes
node apps/module-registry/dist/index.js \
--consumer-dir apps/module-registry/examples/consumer-pinned \
--out-dir /tmp/f11p --fail-on-issues true; echo "exit=$?"
Expect: exit 0; registry-report.json pinAudit.checked == 2,
findings == [].
B2. An unpinned / stale consumer fails with remediation
node apps/module-registry/dist/index.js \
--consumer-dir apps/module-registry/examples/consumer-unpinned \
--out-dir /tmp/f11u --fail-on-issues true; echo "exit=$?"
Expect: exit 2; two findings — [unpinned] 'modules/azure/baseline' … (pin it
to baseline/v1.0.0) and [unknown-version] ref 'acr/v9.9.9' ….
Part C — Live release (optional, ~5 min)
Exercises the real tag + GitHub Release path. Safe — it only creates git tags and Releases, no cloud resources. Requires push rights +
ghauth.
C1. Dry-run the release workflow
In the GitHub UI: Actions → module-release → Run workflow with dry_run =
true. Expect the run to validate the registry and print the release plan
(summary.md artifact) without creating tags.
C2. Publish (merge path)
Merge a PR that bumps a module's version in modules/registry.json + adds the
matching CHANGELOG section. On merge to main, the workflow creates
<module>/vX.Y.Z and a GitHub Release with the CHANGELOG body. Re-running is
idempotent — already-tagged modules are skipped.
Expect: the bumped module's tag present; gh release list shows its Release.
Sign-off
| Field | Value |
|---|---|
| Result | ☐ PASS ☐ FAIL |
| Run by | |
| Date | |
| Commit | |
| Notes |