ci(ceremony): build, test and publish the CCDP image (#28 follow-ups) - #33
Conversation
The `/ccdp/assets/**` header rule also matched error responses: SWS matches `[[advanced.headers]]` sources against the raw request path when no file was resolved, so every 404 beneath the namespace carried `Cache-Control: public, max-age=31536000, immutable`. Drop the wildcard; each file already gets its own exact rule with the immutable headers. Emit both rule forms per file, the physical path and the path with its file name appended, with identical headers. The pinned SWS 3.0.0-beta.1 appends the resolved file name before matching when trailing-slash redirects are off; a future SWS that stops doing so keeps every declared policy. A binary-gated canary test pins the current matching (appended name, post-rewrite path, raw path on errors) so a change fails loudly instead of silently dropping headers. Enable `health = true`: `GET /health` answers 200 for readiness and liveness probes. Compose healthchecks use it. The real-server tests assert the probe and that 404s carry no Cache-Control, Last-Modified or Expires; the static test asserts exact rule sources in both forms for every emitted file. Signed-off-by: xgreenx <xgreenx9999@gmail.com>
GitHub records a sha256 digest for every release asset and returns it in the release document. For `https://github.com/<owner>/<repo>/releases/download/...` sources, `download()` reads that document (authenticated with GH_TOKEN or GITHUB_TOKEN when set) and requires the bytes, fresh or cached, to hash to the recorded digest; a mismatch fails the build. A release without a digest for the asset warns and continues; an unreachable document uses a cached download with a warning and fails without one. Unit tests inject fetch and a temporary cache directory; nothing hits the network. Signed-off-by: xgreenx <xgreenx9999@gmail.com>
Copy it to /home/sws/, outside the served root, so the next build can read the previous publication's retention state back out of the image. Signed-off-by: xgreenx <xgreenx9999@gmail.com>
New `ccdp-image` job: install and build the ceremony workspace, seed the build output from the previously published ghcr.io/libid-org/ccdp:main image (its /home/sws/public and /home/sws/distribution-graph.json) so immutable assets are retained through the compatibility window, build the artifact, assemble the linux/amd64 image, run it read-only with all capabilities dropped, wait for /health, and run the distribution tests against the container and against the pinned native SWS binary (downloaded from the GitHub release and checked against the asset digest from the release API). Nothing may be skipped. Pull requests build and test only. Pushes to main also push `:sha-<short sha>` and `:main` and print the digest in the step summary. A missing previous image (first publication) is a logged skip, with a warning when publishing. Signed-off-by: xgreenx <xgreenx9999@gmail.com>
Describe the CI image publication and how it seeds retention from the previous image, the pinned SWS header-matching quirk and the other native behaviors the configuration accounts for (config.toml precedence, security-headers off with HSTS at the ingress, HEAD without Content-Length, /health and EXPOSE 8787), the release-digest verification, and the tests that cover them. Signed-off-by: xgreenx <xgreenx9999@gmail.com>
The SWS base image ships a placeholder /home/sws/public/index.html, and `COPY public/ /home/sws/public/` merged into that directory, so the image answered `GET /` with the server's default page (200, with validators) instead of a 404. Remove the base tree before copying so the served tree is exactly the build output. The real-server test now asserts that `/` is an uncacheable 404 alongside the other unknown routes. Signed-off-by: xgreenx <xgreenx9999@gmail.com>
The build, test and publish steps of the `ccdp-image` job now live in .github/workflows/ccdp-image.yml (`workflow_call`, plus `workflow_dispatch` for a dry run with `push` left false) so release.yml can run the same path. Inputs: `push` (boolean) and `tags` (image references, one per line, added to the always-tagged `ghcr.io/libid-org/ccdp:sha-<short sha>`; workflow expressions cannot shorten a sha, so the workflow derives that tag itself). ci.yml calls it with push = pushes to main and the `:main` tag; the check now reads "CCDP image / Build, test and push". Signed-off-by: xgreenx <xgreenx9999@gmail.com>
A published release `v<version>` now publishes ghcr.io/libid-org/ccdp:<version>, plus `:latest` when the version has no prerelease `-` (the npm dist-tag rule). `publish-ccdp-image` promotes the image ci.yml pushed as `sha-<short sha>` when the released commit landed on main: a registry-side retag by digest (`imagetools create --prefer-index=false`, which copies a single manifest as-is instead of wrapping it in a new index), verified to resolve to the same digest and printed in the step summary. When that image does not exist (not found), `build-ccdp-image` builds, tests and pushes from source through the reusable workflow under the version tags; any other inspect failure fails the job instead of silently rebuilding. Signed-off-by: xgreenx <xgreenx9999@gmail.com>
"Publication and upgrades" now describes the reusable workflow, the release tags (`:<version>`, `:latest` for stable versions), promotion as a same-digest retag of the tested main image (no retention seed needed), the from-source fallback seeded from `:main`, and pinning deployments by digest. Signed-off-by: xgreenx <xgreenx9999@gmail.com>
There was a problem hiding this comment.
🟡 Changes recommended
Address the two critical CI permission/retention issues and the short-SHA promotion collision risk.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Adds CCDP image build, test, retention, publication workflows, SWS hardening, release digest verification, and deployment documentation.
Changes:
- Adds reusable CI and release image workflows.
- Fixes served-tree, health-check, header, and immutable-asset behavior.
- Adds release digest verification, tests, compose probes, and documentation.
File summaries
| File | Reviewed changes and findings |
|---|---|
ts/packages/ceremony/e2e/compose.yaml |
Uses /health for container health checks. |
ts/packages/ceremony/docs/traceability.md |
Updates CCDP qualification traceability. |
ts/packages/ceremony/docs/testing.md |
Documents expanded distribution testing. |
ts/packages/ceremony/docs/distribution.md |
Documents runtime behavior, retention, publication, and deployment. |
ts/packages/ceremony/ccdp.Dockerfile |
Replaces the base served tree and embeds distribution metadata. |
ts/packages/ceremony/build/sws.ts |
Adds health support and exact header rules. |
ts/packages/ceremony/build/sws.test.ts |
Tests SWS headers, health behavior, and matching quirks. |
ts/packages/ceremony/build/release.ts |
Verifies GitHub release asset digests. |
ts/packages/ceremony/build/release.test.ts |
Tests release digest verification and caching. |
ts/packages/ceremony/build/distribution.test.ts |
Tests generated artifacts and served HTTP behavior. |
ts/apps/dev/compose.yaml |
Uses /health for readiness checks. |
.github/workflows/release.yml |
Adds release image promotion and fallback publishing. |
.github/workflows/ci.yml |
Invokes the reusable CCDP workflow. Critical (1 vote): PR execution grants write-capable package permissions and exposes GH_TOKEN to PR-controlled build code. |
.github/workflows/ccdp-image.yml |
Builds, tests, retains, and publishes CCDP images. Critical (2 votes): Treating all pull failures as missing images can bypass retention safeguards. Moderate (1 vote): Seven-character SHA promotion references can collide and promote the wrong image. |
Review details
Suppressed comments (1)
.github/workflows/ccdp-image.yml:80
- The release promotion identifies the source only by the first seven SHA characters. If two commits eventually share that prefix, the later
mainpublication overwrites this tag and a release can promote a different commit's image while the post-promotion check still passes because it only compares the new tags with that already-wrong source digest. Use a collision-resistant/full commit reference for the promotion lookup, or verify an exact commit identity embedded in the image before promoting.
sha_ref="$IMAGE:sha-${GITHUB_SHA:0:7}"
- Files reviewed: 14/14 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| if ! docker pull --platform linux/amd64 "$IMAGE:main"; then | ||
| echo "No previous image at $IMAGE:main; building without retained assets." | ||
| if [ "$PUSH" = "true" ]; then | ||
| echo "::warning::Retention seed unavailable; this publication retains no previous immutable assets." | ||
| fi | ||
| exit 0 | ||
| fi |
There was a problem hiding this comment.
Fixed in e5388a2: the pull's stderr is captured and the seed is skipped only when the registry says the image does not exist (manifest unknown|not found|name unknown, which is also what the read-only PR token gets before the first publication). Any other pull error (denied, network, registry) fails the run with ::error so the publication can be re-run.
| permissions: | ||
| contents: read | ||
| packages: write |
There was a problem hiding this comment.
Fixed in 5eb0d0b: the reusable workflow declares no permissions and inherits its caller's, and ci.yml now has a read-only ccdp-image job for pull requests (packages: read, never pushes) plus a ccdp-publish job that runs only on pushes to main with packages: write. GH_TOKEN is still exported for the release-asset digest lookups, but on PR runs it is the read-only token.
The reusable workflow declared packages: write and ci.yml granted it on every event, so PR-controlled build code ran with a write-capable token although pull requests never push. The called workflow now declares no permissions and inherits its caller's; ci.yml splits the caller into a read-only pull-request job and a main-only publish job. A manual dispatch is a dry run on the repository's read-only default token. Signed-off-by: xgreenx <xgreenx9999@gmail.com>
Every failed pull of :main counted as "no previous image", so a registry outage or an auth regression on a main publication would build without distribution-graph.json and drop retained immutable assets past the compatibility-window guard. Only the registry's answer that the image does not exist (manifest unknown, not found, name unknown) skips the seed now; any other error fails the run so it can be re-run. Signed-off-by: xgreenx <xgreenx9999@gmail.com>
The promotion source was sha-<7 hex>; two commits sharing that prefix would let a later main publication overwrite the tag and a release promote another commit's image, unnoticed because the post-promotion check only compares the new tags with that source. The sha tag is now the full 40-hex sha, the image carries OCI revision/source/version labels, and release.yml refuses to promote unless the source image's revision label equals the released commit. Signed-off-by: xgreenx <xgreenx9999@gmail.com>
|
Short-SHA promotion collision (Copilot's suppressed finding on ccdp-image.yml:80), fixed in 01fea8b: the image tag is |
Wondertan
left a comment
There was a problem hiding this comment.
The wildcard removal is useful. Two remaining findings and one simplification below.
Querying GitHub's release API for an asset digest at build time checks what GitHub currently serves, not a reviewed value, so it adds little over pinning checksums in the declarations (review on #33). Restore the plain cached download and drop its tests and docs. Checksum pinning is tracked in #34. Signed-off-by: xgreenx <xgreenx9999@gmail.com>
The CCDP image job checked the pinned static-web-server tarball against the digest GitHub's release API reports, which is what GitHub serves today rather than a reviewed value. Pin the sha256 literal next to SWS_VERSION and check the download against it. The build step no longer needs GH_TOKEN. Signed-off-by: xgreenx <xgreenx9999@gmail.com>
A fallback build pushed `:<version>` (and `:latest`) but never `:main`, while every later build seeds retention from `:main` alone, so assets a fallback release introduced could vanish from the next main deployment. Without a `sha-<commit sha>` image the release now fails and names the fix: merge to main, let ccdp-publish run, then re-publish the release. Signed-off-by: xgreenx <xgreenx9999@gmail.com>
Absent cache headers leave a 404 heuristically cacheable (RFC 9110 §15.5.5), and the per-file rule keyed on the appended-name form (`<file>/<name>`) equalled the raw path of the 404 beneath the file, so `/ccdp/assets/a.js/a.js` answered 404 as immutable. Rules are now keyed on the plain physical path only, which the pinned SWS matches after rewrites with redirect-trailing-slash on, behind a `/**` catch-all that carries the error policy; the canary pins that model on the real binary. Signed-off-by: xgreenx <xgreenx9999@gmail.com>
Keep KIT-001A aligned with the permitted same-origin directory redirect and clarify that its slash destination returns 404. Actual protocol and asset resources remain redirect-free. Assisted-by: GPT-6 Signed-off-by: Wondertan <hlibwondertan@gmail.com>
Querying GitHub's release API for an asset digest at build time checks what GitHub currently serves, not a reviewed value, so it adds little over pinning checksums in the declarations (review on #33). Restore the plain cached download and drop its tests and docs. Checksum pinning is tracked in #34. Signed-off-by: xgreenx <xgreenx9999@gmail.com>
Querying GitHub's release API for an asset digest at build time checks what GitHub currently serves, not a reviewed value, so it adds little over pinning checksums in the declarations (review on #33). Restore the plain cached download and drop its tests and docs. Checksum pinning is tracked in #34. Signed-off-by: xgreenx <xgreenx9999@gmail.com>
Querying GitHub's release API for an asset digest at build time checks what GitHub currently serves, not a reviewed value, so it adds little over pinning checksums in the declarations (review on #33). Restore the plain cached download and drop its tests and docs. Checksum pinning is tracked in #34. Signed-off-by: xgreenx <xgreenx9999@gmail.com>
Querying GitHub's release API for an asset digest at build time checks what GitHub currently serves, not a reviewed value, so it adds little over pinning checksums in the declarations (review on #33). Restore the plain cached download and drop its tests and docs. Checksum pinning is tracked in #34. Signed-off-by: xgreenx <xgreenx9999@gmail.com>
Follow-ups from #28's review: CI builds, tests and publishes the CCDP image.
Changes
ccdp-imagejob (ccdp-image.yml): builds the artifact and linux/amd64 image, tests it in the container and against the pinned SWS binary; PRs never push.ccdp-publishjob (mainonly): the onlypackages: writejob; pushes:sha-<full sha>and:mainwith OCI labels, seeded from:mainfor retention.:sha-<full sha>→:<version>(:latestif stable) by digest, revision label checked; no image, no release ("merge to main first"): every deployable image is in the:mainretention history./**catch-all, emitted first, gives every 404Cache-Control: no-store,nosniff, CORPsame-origin, CSPdefault-src 'none';/404.htmldeclares the same.redirect-trailing-slashon); the appended form, the raw path of the 404 under a file (/ccdp/assets/a.js/a.jswas immutable), is gone. Binary canary pins it./healthon; SWS test binary sha256 pinned; base imagepublic/cleared (/is a 404).Verified
CCDP image / Build, test and pushgreen.Not verified / known issues
/ccdp/assets(a directory) 308s to/ccdp/assets/, then 404.Integration smoke: flaky on every branch (live Google JWKS).