NPM publish-readiness pass across all four @hulumi/* packages. After M1 the repo can produce four publishable tarballs that satisfy pnpm publish --dry-run end-to-end.
- Removed
"private": truefrompackages/{baseline,policies,drift}/package.json. (k8s-baseline/package.jsonalready lacked the field; left unset.) - Added
repository/bugs/homepageto all four packages, all pointing tokerberosmansour/hulumi.repository.directoryset to the per-package path so npmjs.com renders the correct sub-path link. - Bumped
@hulumi/k8s-baseline1.0.0 → 1.2.0 to satisfy the atomic-release invariant. - Created
packages/{baseline,policies,k8s-baseline}/README.md(npmjs.com-rendered).packages/drift/README.mdleft as-is (existing, used as the model). - Copied repo-root
LICENSEbyte-identical (MD53b83ef96387f14655fc854ddc3c6bd57) into all four packages. - Updated
README.mdv1.1.0 → v1.2.0 references; removed(pre-release)annotation for k8s-baseline. - Updated
docs/ARCHITECTURE.mdline 7 to reflect the atomic 1.2.0 across all four packages. - Folded the version reconciliation into the existing
[1.2.0] — 2026-05-01CHANGELOG.mdentry (no tag had been cut, so editing the unshipped entry was correct, not history rewriting). - Deleted
package-lock.json; addedpackage-lock.jsonandyarn.lockto.gitignoreso pnpm-divergent lockfiles can't sneak back in. - Added
.claude/to.gitignore(coding-agent harness session state shouldn't be committed). - Extended
packages/k8s-baseline/tests/release-readiness.test.tswith a newdescribe("Feature: Atomic four-package publish-readiness …")block — 16 new test cases enforcing per-packageprivate/publishConfig/repository/bugs/homepageshape, atomic version invariant, README presence, and LICENSE byte-equality with repo root.
- k8s-baseline 1.0.0 → 1.2.0 (rather than 1.1.0 → 1.2.0 sequencing). The atomic four-package release is non-negotiable per
release-readiness.test.ts. Keeping k8s-baseline at 1.0.0 while shipping 1.2.0 of the others would require either relaxing the atomic invariant (large blast radius) or a separate k8s-baseline-only release (carries its own coordination cost). The skip from 1.0.0 directly to 1.2.0 is documented in the CHANGELOG entry. "private"field omitted, not set tofalse. k8s-baseline already lacked the field; the other three had"private": true. Removed entirely rather than flipping tofalsebecause the absence is the npm-publish default.release-readiness.test.tsassertsprivate === undefined || private === falseso both shapes pass.- Per-package README is self-contained. Wrote each README assuming the reader landed on npmjs.com without seeing the GitHub repo. Each links back to specific repo paths via absolute URLs (
https://github.com/kerberosmansour/hulumi/...) rather than relative paths, because relative paths only work on GitHub's web UI, not on npmjs.com. - CHANGELOG edit, not new entry. No git tag exists for v1.2.0; the entry was a planned-release narrative, not a shipped artifact. Editing it in place to reconcile the k8s-baseline version was correct, not retroactive rewriting.
- Folded
.claude/into.gitignoreproactively. The agent harness leaves session state behind; a single accidentalgit add -Awould commit it without the ignore rule. yarn.lockblocked in.gitignorealong withpackage-lock.json. Belt-and-braces: the original audit finding was aboutpackage-lock.json, but blocking yarn's lockfile is the same defense.
pnpm publish --dry-runsucceeds for all four packages without npm credentials. Verified — dry-run only validates package shape; no auth required.npm pack --dry-runincludes LICENSE and README without explicitfilesarray entries. Verified — npm has built-in conventions that include LICENSE/README/CHANGELOG/NOTICE at the tarball root regardless offiles.- The atomic-release invariant in
release.ymlis enforced byrelease-readiness.test.ts. Verified — the existing test asserts the four package names appear inrelease.yml's pack loop; M1 adds the version-equality assertion. - Editing the unshipped
[1.2.0]CHANGELOG entry is not history rewriting. Verified —git tag -l 'v*'returned empty. .claude/is harness session state, not user-authored content. Verified — directory contents are tooling state.
- The
@huluminpm scope is unclaimed. Out of M1's scope (P0, user action). Whenpnpm publishruns for real, scope ownership must exist. Tested with--dry-runonly. hulumi.iodomain status. Out of M1's scope; addressed in M2 via.github/SECURITY-CONTACTSplaceholder (and ultimately by the user registering the domain or rerouting all references).- Whether
@hulumi/k8s-baselineconsumers exist outside the repo. None are known internally, but a public pre-release of1.0.0-pre.1may have been distributed (e.g., via tarball upload, manual install). If any external consumer pinned1.0.0-pre.1, the 1.0.0 → 1.2.0 jump bypasses 1.0.0 entirely. CHANGELOG documents this; no action item identified.
- Authored the runbook with prettier-failing markdown. Forgot to run
pnpm run formatafter writingdocs/slo/current/RUNBOOK-hulumi-pre-public-launch.md; the baseline format check failed and had to be repaired before BDD-first could begin. Cost: one extra round ofpnpm run format+ re-run. - Initially considered a separate
[1.2.1]CHANGELOG entry for the k8s-baseline reconciliation. Caught before committing — the v1.2.0 hadn't shipped, so editing in place was correct. Lesson: check git tags before assuming a CHANGELOG entry is historical.
- Missing the prettier step on new markdown files. The runbook's own static-analysis gates (Section 4.2) include format check; the runbook authoring step itself isn't covered by the slo-plan skill. Future: format the runbook as part of slo-plan output, OR have slo-execute pre-flight Step 0 always include a format pass before declaring baseline green.
- CHANGELOG-vs-tag confusion is a recurring class. Treating a CHANGELOG entry as historical when no tag exists is a mistake to make once. Recorded here so future-me can move faster.
- Test fixture noise.
kubernetes-secret-from-asm.test.tsemits 66 unhandled-rejection logs from existing fail-closed test scenarios. They're cosmetic noise, not failures (exit 0, all 167 tests pass). Investigated to confirm the noise was pre-existing, not caused by my changes. Adds friction to baseline test reading.
manifest.private === undefined || manifest.private === false— encoded per package inrelease-readiness.test.ts:103-110.manifest.publishConfig.access === "public"— encoded.manifest.publishConfig.provenance === true— encoded.manifest.license === "Apache-2.0"— encoded.repository.url,bugs.url,homepageall containhttps://github.com/kerberosmansour/hulumi— encoded.- All four package versions are equal (atomic-release invariant) — encoded as
Set(versions).size === 1. - All four package versions match
^1\.2\.\d+/— encoded. - Each package has a non-empty
README.md— encoded asexistsSync+statSync(...).size > 0. - Each package's
LICENSEis byte-identical to repo-rootLICENSE— encoded as exact-string equality.
None new. M1 is mechanical; no new collections, queues, retries, caches, or recursion introduced.
npm pack --dry-run --json | jq '.[0].files[].path'is the right inspection for tarball contents. The.filesarray surfaces every path withmode,size, and the on-disk source — useful for confirmingdist/shape without unpacking.pnpm publish --dry-runprintstotal files: <N>matching thenpm packcount. Confirmed all four match: 211 / 96 / 59 / 137 files.md5 packages/*/LICENSE LICENSEconfirms byte-identity in one shot.
- New constant
PUBLISHABLE_PACKAGESinrelease-readiness.test.ts— single source of truth for the four-package set in tests. Future BDD additions for the atomic-release invariant should reuse it. - New constant
CANONICAL_REPO_URL— single source for the GitHub URL. If the repo ever moves, this is the only edit. - New interface
PublishablePackageJson— typed shape of the manifest fields M1 cares about.
it.each(PUBLISHABLE_PACKAGES)("@hulumi/%s ...", (pkg) => { ... })cleanly parallelizes per-package assertions without duplicating code. Vitest'sit.eachwith a tuple+template-string title produces readable failure output.- Custom assertion messages (
expect(x, "specific reason").toBe(y)) made every failure cite the exact package name and field — saved time relative to genericexpect(x).toBe(y)failures.
- Tarball contents diff against
filesdeclaration. Today the test asserts the file set is correct vianpm pack --dry-runexternal command (smoke test, not vitest). A vitest test that walksJSON.parse(package.json).filesand asserts every glob expands to the dist files would be tighter — but not required for M1's Definition of Done. pnpm install --frozen-lockfilesucceeds afterpackage-lock.jsondeletion. Tested manually in the Evidence Log; not encoded as a vitest test. Would belong in CI'srelease-readinessinvocation if it's not already there.
- Run
pnpm run formatbefore committing any new markdown. The runbook's own format gate fires whether the file is indocs/or anywhere else under prettier's scan. - Check
git tag -l 'v*'before editing CHANGELOG entries. Saves the "is this rewriting history?" mental cycle. .claude/is harness state — never commit. Already in.gitignoreafter M1.- For mechanical milestones, BDD-first still pays off. The 16 release-readiness assertions caught me from misformatting one homepage URL during the package.json edits — failed test pointed at the exact package, exact field.
- Read
release.ymlearly in any milestone touching package.json. It's the source of the atomic-release invariant; missing it would have shipped a version-skewed M1.
/slo-planshould runpnpm run format(or the project's formatter) on the runbook file it just authored. Today it produces prettier-failing markdown. One-line fix in slo-plan's Step 0.- Consider adding a "package.json hygiene check" to the v4 template's Section 4.2 static-analysis table for monorepo runbooks —
private-flag andpublishConfigshape lint as a cheap CI step.