ci(publish): gate kaniko image builds + publish on changed paths
The bottleneck. Every main merge runs publish.yml, which unconditionally rebuilds the three kaniko images (gateway/appservice/web, ~25 min); each Dockerfile's COPY . . busts kaniko's cache on any change. But none of those apps depend on @mosaicstack/mosaic — so the entire constitution + fleet PR stream (all packages/mosaic/** + docs/**) rebuilds all three images for nothing, saturating the runners.
Change (minimal — publish.ymlwhen:path only)
build-gateway / build-appservice / build-web — step-level when (shared anchor): build on tag always; on a main push, build unless the changed files are onlypackages/mosaic/**, docs/**, **/*.md, .woodpecker/**.
publish-npm — run only when packages/** changed (or on tag); a pure-docs merge runs no publish.
install/build unchanged.
Why exclude-list (not per-image include-lists)
My proposal floated per-image include-lists, but those risk under-including an app's transitive package closure → a needed image silently skipped → stale deploy. An exclude-list is correctness-safe: the default is to build; only the known npm-only/docs change classes skip. Same waste elimination, no staleness risk.
when list entries are OR'd ("if at least one of the conditions … evaluates true the step is executed"); sub-conditions within an entry are AND'd.
path conditions apply to push/pull_request only — not tag events — which is exactly why each gated step has a separate event: tag entry so releases always build.
Step-level when governs the step independently of the file-level when (the file-level when gates whether the workflow runs; ungated steps still inherit it).
Validation & safety
The real skip-validation happens on the next merge after this lands: a docs-only merge (e.g. #613) should show 0 image builds; an apps/gateway/** change should still build gateway.
Skipping is safe — prior :latest / :sha-* images remain; nothing is deleted, deploys still pull the last good image.
Deferred (follow-up PRs, per Lead)
Dockerfile COPY . . tightening (better cache reuse for builds that do run); scoping publish-npm's build dependency to --filter "@mosaicstack/*". Not adopting tag-gate-everything (publish-on-merge → publish-on-tag is a deploy-workflow change = Jason's call).
## ci(publish): gate kaniko image builds + publish on changed paths
**The bottleneck.** Every `main` merge runs `publish.yml`, which unconditionally rebuilds the three kaniko images (gateway/appservice/web, **~25 min**); each Dockerfile's `COPY . .` busts kaniko's cache on any change. But **none of those apps depend on `@mosaicstack/mosaic`** — so the entire constitution + fleet PR stream (all `packages/mosaic/**` + `docs/**`) rebuilds all three images **for nothing**, saturating the runners.
### Change (minimal — `publish.yml` `when:path` only)
- **`build-gateway` / `build-appservice` / `build-web`** — step-level `when` (shared anchor): build on **tag** always; on a `main` push, build **unless** the changed files are *only* `packages/mosaic/**`, `docs/**`, `**/*.md`, `.woodpecker/**`.
- **`publish-npm`** — run only when `packages/**` changed (or on tag); a pure-docs merge runs no publish.
- `install`/`build` unchanged.
### Why exclude-list (not per-image include-lists)
My proposal floated per-image include-lists, but those risk **under-including** an app's transitive package closure → a needed image silently skipped → stale deploy. An **exclude-list is correctness-safe**: the default is to build; only the known npm-only/docs change classes skip. Same waste elimination, no staleness risk.
### Woodpecker semantics (docs-confirmed)
Per the [Woodpecker workflow-syntax docs](https://woodpecker-ci.org/docs/usage/workflow-syntax):
- **`when` list entries are OR'd** ("if at least one of the conditions … evaluates true the step is executed"); sub-conditions within an entry are AND'd.
- **`path` conditions apply to `push`/`pull_request` only** — *not* tag events — which is exactly why each gated step has a **separate `event: tag`** entry so releases always build.
- **Step-level `when` governs the step independently** of the file-level `when` (the file-level `when` gates whether the workflow runs; ungated steps still inherit it).
### Validation & safety
- The real skip-validation happens on the **next merge after this lands**: a docs-only merge (e.g. #613) should show **0 image builds**; an `apps/gateway/**` change should still build gateway.
- **Skipping is safe** — prior `:latest` / `:sha-*` images remain; nothing is deleted, deploys still pull the last good image.
### Deferred (follow-up PRs, per Lead)
Dockerfile `COPY . .` tightening (better cache reuse for builds that *do* run); scoping `publish-npm`'s build dependency to `--filter "@mosaicstack/*"`. **Not** adopting tag-gate-everything (publish-on-merge → publish-on-tag is a deploy-workflow change = Jason's call).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Every main merge runs publish.yml, which unconditionally rebuilds the three
kaniko images (gateway/appservice/web, ~25 min) — and each Dockerfile's
`COPY . .` busts kaniko's cache on any change. But none of those apps depend
on @mosaicstack/mosaic, so the entire constitution + fleet PR stream (all
packages/mosaic/** + docs/**) rebuilds all three images for nothing, saturating
the runners.
Gate the heavy steps with step-level `when: path`:
- build-gateway/appservice/web: skip when a main push touches ONLY non-image
paths (packages/mosaic/**, docs/**, **/*.md, .woodpecker/**); always build on
tag. Exclude-list keeps the default SAFE — any non-excluded change still
builds, so no transitive dep can silently go stale (chosen over per-image
include-lists, which risked under-including an app's transitive closure).
- publish-npm: run only when packages/** changed (or on tag) — a pure-docs
merge now runs no publish.
Woodpecker semantics (docs-confirmed): `when` entries are OR'd; `path` applies
to push/PR only (hence the separate `event: tag` entry); step-level `when`
governs the step independently of the file-level `when`.
install/build remain ungated (deferred: scoping the build + tightening the
Dockerfile COPY are follow-ups). Skip-validation lands on the next real merge
(a docs-only merge should show 0 image builds); skipping is safe — prior
:latest/:sha images remain.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EsgTQzV5YUGk1JtCLP4B83
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
ci(publish): gate kaniko image builds + publish on changed paths
The bottleneck. Every
mainmerge runspublish.yml, which unconditionally rebuilds the three kaniko images (gateway/appservice/web, ~25 min); each Dockerfile'sCOPY . .busts kaniko's cache on any change. But none of those apps depend on@mosaicstack/mosaic— so the entire constitution + fleet PR stream (allpackages/mosaic/**+docs/**) rebuilds all three images for nothing, saturating the runners.Change (minimal —
publish.ymlwhen:pathonly)build-gateway/build-appservice/build-web— step-levelwhen(shared anchor): build on tag always; on amainpush, build unless the changed files are onlypackages/mosaic/**,docs/**,**/*.md,.woodpecker/**.publish-npm— run only whenpackages/**changed (or on tag); a pure-docs merge runs no publish.install/buildunchanged.Why exclude-list (not per-image include-lists)
My proposal floated per-image include-lists, but those risk under-including an app's transitive package closure → a needed image silently skipped → stale deploy. An exclude-list is correctness-safe: the default is to build; only the known npm-only/docs change classes skip. Same waste elimination, no staleness risk.
Woodpecker semantics (docs-confirmed)
Per the Woodpecker workflow-syntax docs:
whenlist entries are OR'd ("if at least one of the conditions … evaluates true the step is executed"); sub-conditions within an entry are AND'd.pathconditions apply topush/pull_requestonly — not tag events — which is exactly why each gated step has a separateevent: tagentry so releases always build.whengoverns the step independently of the file-levelwhen(the file-levelwhengates whether the workflow runs; ungated steps still inherit it).Validation & safety
apps/gateway/**change should still build gateway.:latest/:sha-*images remain; nothing is deleted, deploys still pull the last good image.Deferred (follow-up PRs, per Lead)
Dockerfile
COPY . .tightening (better cache reuse for builds that do run); scopingpublish-npm's build dependency to--filter "@mosaicstack/*". Not adopting tag-gate-everything (publish-on-merge → publish-on-tag is a deploy-workflow change = Jason's call).🤖 Generated with Claude Code