feat(townhouse): embed compose templates + image-manifest in npm tarball (Story 45.2)#43
Merged
ALLiDoizCode merged 5 commits intomainfrom May 9, 2026
Merged
Conversation
…all (Story 45.2)
- Add packages/townhouse/compose/{townhouse-hs,townhouse-dev}.yml source templates
- Add packages/townhouse/src/compose-loader.ts with loadComposeTemplate() +
materializeComposeTemplate() API (mode 0o600 writes, ComposeLoaderError)
- Add scripts/render-compose-template.mjs for CI digest-substitution step
- Update tsup.config.ts onSuccess hook to copy/render compose templates
- Flip DEFAULT_CONNECTOR_IMAGE from tag form to digest form per CONNECTOR_RELEASE_CONTRACT.md
- Update docker-compose-townhouse.yml connector image to digest form
- Remove --dry-run from publish-townhouse-images.yml npm-publish step
- Add render + tarball verification CI steps to publish-townhouse-images.yml
- Add 12 unit tests (compose-loader), 7 integration (validity), 5 integration (tarball)
- Update connector-image-contract.test.ts to accept digest-form image refs
- Document compose templates API in README, CLAUDE.md, CONNECTOR_RELEASE_CONTRACT.md
Critical path: 45.1 → 45.2 (this) → 45.4
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two rounds of bmad-code-review on Story 45.2 (embed compose templates + image-manifest in npm tarball). Round 1 applied 19 patches across compose loader, render scripts, CI tarball-verify, dev compose secret handling, manifest-alignment test, and orchestrator RepoDigests cache. Round 2 caught 1 BLOCKER (CI verify regex matched 0 lines because of a leading-space bug) plus 5 MAJOR + 5 MINOR follow-up issues; all 11 are fixed here. Significant changes: - compose-loader: validate profile + townhouseHome at runtime; reject system paths; lstat both directories AND file paths to refuse symlink writes; only narrow modes (never widen tighter modes like 0o500 → 0o700). - tsup + render-compose-template: split error handling so ENOENT warns but schema/digest errors throw; both now import a single getImageDigest helper from scripts/lib/image-manifest-digest.mjs (eliminates the drift hazard Round 1 deferred). - CI workflow: regex now correctly anchors on YAML shape (^[[:space:]]+image:[[:space:]]+...@sha256:[a-f0-9]{64}[[:space:]]*$), uses mktemp dirs and anchored grep, and enforces a non-zero IMAGE_LINES floor so a structural refactor can't pass via 0===0. - HS compose template: townhouse-api gets the missing environment block (TOWNHOUSE_CONFIG=/.townhouse/config.yaml + TOWNHOUSE_WALLET_PASSWORD:? passthrough so docker compose up fails fast on unset password); top comment documents the intentional canonical-port choice and the HS-vs-dev-stack non-concurrent-run constraint. - dev compose template: 7 secret env vars switched from ${VAR:-} (silent empty-string default) to ${VAR:?msg} (fail on unset). - Tests: new manifest-alignment test (D2) hard-fails in CI when manifest absent; new negative test asserts compose-config exits non-zero without TOWNHOUSE_WALLET_PASSWORD; idempotency test now compares file content; tarball-contents test asserts dist/compose freshness precondition with an actionable error. - orchestrator.pullImages: cache check now matches RepoTags ∪ RepoDigests so digest-form DEFAULT_CONNECTOR_IMAGE is recognized as cached. - Docs: README + story spec + deferred-work updated; spec scope-guard amendments ratify the orchestrator + root docker-compose touch retroactively. 98 unit tests + 13 integration tests pass; lint clean. Story status moves review → in-progress; AC #12 close-out (sprint-status flip to done) gated on tag-push + registry verify per spec. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CI Lint & Build on PR #43 caught `Array<[string, string]>` at packages/townhouse/tsup.config.ts:60 violating @typescript-eslint/array-type "Use 'T[]' instead". One-line fix. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3922927 to
d5129fe
Compare
CI Format Check on PR #43 caught style drift in: - src/__integration__/compose-template-validity.test.ts - src/__integration__/connector-image-contract.test.ts - src/__integration__/tarball-contents.test.ts - src/compose-loader.test.ts - src/compose-loader.ts Whitespace-only - pnpm format:check clean; compose-loader unit tests remain 12/12 green. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
First @toon-protocol/townhouse npm publish. RC tag chosen per Story 45.2
spec ("v0.1.0-rc2 or whatever the next test tag is") so the live-publish
path can be smoke-tested against the patched tarball-verify gate without
burning the v0.1.0 version namespace. Promote to v0.1.0 after the rc2
publish surfaces clean and an operator install of npx
@toon-protocol/townhouse@0.1.0-rc2 round-trips the compose templates +
manifest correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
townhouse hs upsubcommand).packages/townhouse/compose/{townhouse-hs,townhouse-dev}.ymlsource templates; HS template uses${TOON_*_DIGEST}placeholders substituted at build timepackages/townhouse/src/compose-loader.tswithloadComposeTemplate()+materializeComposeTemplate()API (writes~/.townhouse/compose/*.ymlat mode0o600)scripts/render-compose-template.mjsfor the CI step that renders the HS template afterdownload-artifactplaces the manifesttsup.config.tsonSuccesshook to producedist/compose/{hs,dev}.ymlduring buildDEFAULT_CONNECTOR_IMAGEfrom tag form (connector:3.4.1) to digest form (connector@sha256:4a24ccb0...) perCONNECTOR_RELEASE_CONTRACT.md--dry-runfrompublish-townhouse-images.ymlnpm-publish step; adds render + tarball verification CI stepsTarball verification output (local smoke test)
connector-image-contract canary (digest form):
Test plan
pnpm --filter @toon-protocol/townhouse test— 12 new compose-loader unit tests pass; existing suite green minus pre-existingearnings.test.ts(7) +logs.test.ts(4) failures (confirmed pre-existing on baseline)pnpm --filter @toon-protocol/townhouse test:integration— 7 new compose-template-validity tests pass; 5 new tarball-contents tests pass; connector canary passes; dev-stack-smoke (3 failures) confirmed pre-existing (requires dev stack running)pnpm --filter @toon-protocol/townhouse test:canary— green at digest-formDEFAULT_CONNECTOR_IMAGEdocker compose configexits 0materializeComposeTemplate('hs')produces files with mode600on WSL2🤖 Generated with Claude Code