Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 65 additions & 9 deletions .github/workflows/publish-townhouse-images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
# Trigger scenarios and expected behaviour:
# - Push of tag v* → builds 4 townhouse images (townhouse-api, town, mill, dvm),
# pushes versioned + :latest tags, signs, writes manifest,
# then npm-publishes @toon-protocol/townhouse.
# renders compose templates, then npm-publishes @toon-protocol/townhouse.
# - workflow_dispatch → builds + pushes versioned tag only (NO :latest), signs,
# writes manifest. Does NOT npm-publish (smoke-test path).
# writes manifest, renders templates (dry-run only; NO npm-publish).
#
# Connector image is NOT built or pushed here — it is published upstream by
# toon-protocol/connector's own workflow (Stories 44.2/44.3). Townhouse consumes
Expand All @@ -23,9 +23,13 @@
# --certificate-oidc-issuer 'https://token.actions.githubusercontent.com'
# done
#
# NOTE: npm-publish step runs in --dry-run mode pending Story 45.2
# (dist/compose/townhouse-hs.yml must ship in the tarball before live publish).
# Flip to live publish when Story 45.2 lands.
# Staged delivery history:
# v0.1.0 (Story 45.1): images + signatures + manifest published; npm publish in --dry-run
# pending Story 45.2 (tarball must ship compose templates + manifest before live publish).
# v0.1.0+ (Story 45.2): live npm publish flipped on; tarball ships
# dist/compose/{townhouse-hs,townhouse-dev}.yml + dist/image-manifest.json.
# Build sequence in npm-publish job: pnpm build → download-artifact (manifest)
# → render-compose-template → verify tarball → pnpm publish (live).

name: Publish Townhouse Images

Expand All @@ -40,7 +44,7 @@ on:
required: true
type: string
connector_version:
description: 'Connector image tag to record in image-manifest.json (default: 3.4.1)'
description: 'Connector image tag to record in image-manifest.json (default: 3.4.1 = digest pinned in DEFAULT_CONNECTOR_IMAGE)'
required: false
type: string
default: '3.4.1'
Expand Down Expand Up @@ -251,17 +255,69 @@ jobs:
run: pnpm install --frozen-lockfile

- name: Build @toon-protocol/townhouse
# clean: true in tsup config wipes dist/ at build start.
# The manifest must be placed AFTER this build step, not before.
run: pnpm --filter @toon-protocol/townhouse build

- name: Download image-manifest.json
# Placed into dist/ AFTER pnpm build so tsup's clean: true doesn't wipe it.
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: image-manifest
path: packages/townhouse/dist

- name: Render HS compose template against pinned digests
# Re-runs the placeholder substitution now that dist/image-manifest.json
# is present. tsup's onSuccess hook ran without the manifest (clean build),
# so this step produces the authoritative digest-substituted HS template.
run: node scripts/render-compose-template.mjs

- name: Verify tarball contents
# Gate: ensure the three required artifacts are present in the tarball
# and the HS YAML has no unsubstituted placeholders before publishing.
run: |
set -euo pipefail
PACK_OUT=$(mktemp -d)
PACK_EXTRACT=$(mktemp -d)
pnpm --filter @toon-protocol/townhouse pack --pack-destination "$PACK_OUT"
# Globbing for the tgz is fine because $PACK_OUT is fresh from mktemp.
TGZ=$(ls "$PACK_OUT"/toon-protocol-townhouse-*.tgz | head -1)
[ -f "$TGZ" ] || { echo "FAIL: pnpm pack did not produce a tgz in $PACK_OUT"; exit 1; }
tar -tzf "$TGZ" > "$PACK_OUT/listing.txt"
for path in \
package/dist/compose/townhouse-hs.yml \
package/dist/compose/townhouse-dev.yml \
package/dist/image-manifest.json; do
# Anchor the match — a `*.bak` or partial-name file would otherwise
# satisfy a substring grep without the actual file existing.
grep -qE "^${path}\$" "$PACK_OUT/listing.txt" \
|| { echo "MISSING from tarball: $path"; exit 1; }
done
tar -xzf "$TGZ" -C "$PACK_EXTRACT"
HS_YAML="$PACK_EXTRACT/package/dist/compose/townhouse-hs.yml"
# Negative: no unsubstituted placeholders.
if grep -E '\$\{TOON_[A-Z_]+_DIGEST\}' "$HS_YAML"; then
echo "FAIL: unsubstituted placeholders in tarball HS YAML"; exit 1
fi
# Positive: every services.<name>.image must use full digest form.
# Single regex enforces YAML shape (' image: <ref>'), digest form
# (@sha256:<64hex>), and tolerates trailing CR / whitespace. Catches
# orphaned '@' (empty-digest substitution) and tag-form refs because
# neither matches the strict 64-hex tail. The IMAGE_LINES floor stops
# a structural refactor that drops all 'image:' keys from passing the
# gate via 0===0.
IMAGE_LINES=$(grep -cE '^[[:space:]]+image:[[:space:]]+' "$HS_YAML" || true)
DIGEST_LINES=$(grep -cE '^[[:space:]]+image:[[:space:]]+[^[:space:]]+@sha256:[a-f0-9]{64}[[:space:]]*$' "$HS_YAML" || true)
if [ "$IMAGE_LINES" -lt 1 ]; then
echo "FAIL: no image: lines found in $HS_YAML — structural defect in template"; exit 1
fi
if [ "$IMAGE_LINES" -ne "$DIGEST_LINES" ]; then
echo "FAIL: $IMAGE_LINES image: lines but only $DIGEST_LINES use @sha256:<64hex> digest form"
exit 1
fi
echo "✅ Tarball contains all required artifacts with no unsubstituted placeholders ($DIGEST_LINES digest-pinned images)"

- name: Publish to npm
# NOTE: --dry-run is set pending Story 45.2 (dist/compose/townhouse-hs.yml
# must ship in the tarball before live publish). Flip to live when 45.2 lands.
run: pnpm --filter @toon-protocol/townhouse publish --access public --no-git-checks --dry-run
run: pnpm --filter @toon-protocol/townhouse publish --access public --no-git-checks
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
3 changes: 3 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,9 @@ All bindings on `127.0.0.1:` only. Script: `scripts/townhouse-dev-infra.sh`. Con
| Townhouse dev stack fixtures | `docker/dev-fixtures/` (Mill JSON configs + README) |
| **Townhouse real-CLI E2E** | `scripts/townhouse-test-infra.sh` (Story 21.16) |
| Townhouse real-CLI E2E docs | `packages/townhouse/README.md` § "Running E2E Tests" |
| **Townhouse npm-tarball compose templates** | `packages/townhouse/compose/` (source) → `dist/compose/` (built output) |
| Compose loader + materializer API | `packages/townhouse/src/compose-loader.ts` |
| Image-manifest digest registry (per release) | `packages/townhouse/dist/image-manifest.json` (CI-produced; not committed) |

## Browser Verification

Expand Down
Loading
Loading