Skip to content

feat(townhouse): embed compose templates + image-manifest in npm tarball (Story 45.2)#43

Merged
ALLiDoizCode merged 5 commits intomainfrom
feat/45-2-embed-compose-templates
May 9, 2026
Merged

feat(townhouse): embed compose templates + image-manifest in npm tarball (Story 45.2)#43
ALLiDoizCode merged 5 commits intomainfrom
feat/45-2-embed-compose-templates

Conversation

@ALLiDoizCode
Copy link
Copy Markdown
Collaborator

Summary

  • Story 45.2 — Critical path for Epic 45 (One-Command Apex Install). Unblocks Story 45.4 (townhouse hs up subcommand).
  • Adds packages/townhouse/compose/{townhouse-hs,townhouse-dev}.yml source templates; HS template uses ${TOON_*_DIGEST} placeholders substituted at build time
  • Adds packages/townhouse/src/compose-loader.ts with loadComposeTemplate() + materializeComposeTemplate() API (writes ~/.townhouse/compose/*.yml at mode 0o600)
  • Adds scripts/render-compose-template.mjs for the CI step that renders the HS template after download-artifact places the manifest
  • Updates tsup.config.ts onSuccess hook to produce dist/compose/{hs,dev}.yml during build
  • Flips DEFAULT_CONNECTOR_IMAGE from tag form (connector:3.4.1) to digest form (connector@sha256:4a24ccb0...) per CONNECTOR_RELEASE_CONTRACT.md
  • Removes --dry-run from publish-townhouse-images.yml npm-publish step; adds render + tarball verification CI steps

Tarball verification output (local smoke test)

=== Tarball contents (compose + manifest):
package/dist/image-manifest.json
package/dist/compose/townhouse-dev.yml
package/dist/compose/townhouse-hs.yml
=== sha256 count (from image: lines + comment header): 10
=== Unsubstituted placeholders: 0

=== Image lines (all 5 in digest form):
  image: ghcr.io/toon-protocol/connector@sha256:4a24ccb0997d7b025997e670546032f6a84cd18a77c490509016b85e181a344e
  image: ghcr.io/toon-protocol/townhouse-api@sha256:44b1ce0fcfc385667c64fe88b33e85889ac8010b4ba09b02733225fd6ea7ac59
  image: ghcr.io/toon-protocol/town@sha256:b55bc30ea2e5ca28286a2fa55ee9c08d07931a8ed8c4f511ee020837c25da5d6
  image: ghcr.io/toon-protocol/mill@sha256:681b2e2059c30a5d300f23233f3ddfde014f623db93a100d0dbfaae694cad95f
  image: ghcr.io/toon-protocol/dvm@sha256:3b64c12eb8ab5b7f57e04073ed85e6ebd2a20d83d382b47d704d911dd39f1296

docker compose config: ✅ exit 0

connector-image-contract canary (digest form):

✓ src/__integration__/connector-image-contract.test.ts  (4 tests | 1 skipped) ~8s

Test plan

  • pnpm --filter @toon-protocol/townhouse test — 12 new compose-loader unit tests pass; existing suite green minus pre-existing earnings.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-form DEFAULT_CONNECTOR_IMAGE
  • Local smoke test: tarball contains all 3 artifacts, 0 unsubstituted placeholders, 5 digest-form image lines, docker compose config exits 0
  • materializeComposeTemplate('hs') produces files with mode 600 on WSL2

🤖 Generated with Claude Code

ALLiDoizCode and others added 3 commits May 9, 2026 17:44
…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>
@ALLiDoizCode ALLiDoizCode force-pushed the feat/45-2-embed-compose-templates branch from 3922927 to d5129fe Compare May 9, 2026 21:44
ALLiDoizCode and others added 2 commits May 9, 2026 17:50
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>
@ALLiDoizCode ALLiDoizCode merged commit 4fe421e into main May 9, 2026
7 checks passed
@ALLiDoizCode ALLiDoizCode deleted the feat/45-2-embed-compose-templates branch May 9, 2026 22:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant