feat(deploy): reusable build workflow, strict mode and the deployment contract - #84
Merged
oto-macenauer-absa merged 1 commit intoSep 4, 2026
Conversation
… contract Deployment is deliberately not part of this repository, but what a deployment repo needs *from* it was written down nowhere. Worse, the pieces that make the public build work — a vendored fixture, `prebuilt` paths, `optional` entries — are exactly the ones that would let a production build ship a half-empty knowledge base and still exit 0. Three things close that. `--strict` / KB_STRICT rejects everything a deployment must not rely on: `prebuilt` and `localPath` (a path on someone's disk is not reproducible), `optional` (permission to ship without an app nobody noticed was missing), an empty registry, and any entry that produced no apps. iframe entries survive — they are a documented stopgap with no artifact to pin, so the rules about released artifacts cannot apply to them. build-image.yml is a workflow_call the deployment repo invokes. It checks out the caller's repo for the registry and this one for the build, runs the build against KB_REGISTRY, and either pushes an image or, with no image-name, uploads dist/ and pushes nothing — which is the right shape for a PR check on a registry. KB_REGISTRY now resolves absolute paths, so the registry is read where it lives rather than copied into this checkout. Every build writes dist/kb-build.json: which source, at which version, produced which slugs. An image is opaque once pushed, and "which release produced this page" is not answerable from the registry alone once `latest` has moved. The workflow keeps it as a 90-day artifact and renders it into the job summary. contract/DEPLOYMENT.md is the definition the private repo is built from — what lives where, the registry rules, the GitHub App that reads the docs repos (`contents: read`, installed per repo, short-lived tokens, no personal access token), the narrower token repository_dispatch needs, the triggers, and rollback. examples/deployment-repo/ is the skeleton to copy. CI now calls build-image.yml as a dry run, pinned to the commit under review rather than the default branch — otherwise the check would build master and pass while the change under review was broken. tests/deployment.spec.js covers what strict refuses and what the provenance records. Every case there is rejected during registry validation, before anything is staged or written, so the spec cannot clobber the dist/ the rest of the suite reads. Suites green: 127 embedded, 22 standalone. Part of #73. Closes #78. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PYtmxSeSXFg4wEXRcKCmAi
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.
Closes #78. Part of #73. Top of the stack: #82 → #81 → #80 → #79.
The gap
Deployment is deliberately not part of this repository, but what a deployment repo needs from it was written down nowhere. Worse, the pieces that make the public build work — a vendored fixture,
prebuiltpaths,optionalentries — are exactly the ones that would let a production build ship a half-empty knowledge base and still exit 0.--strict/KB_STRICTRejects everything a deployment must not rely on:
prebuilt,localPathoptionaliframeentries survive: a documented stopgap (#10) with no artifact to pin, so the rules about released artifacts cannot apply to them.This repo's own registry fails strict mode by design — it is a fixture, and saying so out loud is the point.
build-image.ymlA
workflow_callthe deployment repo invokes. It checks out the caller's repo for the registry and this one for the build, runs againstKB_REGISTRY, and either pushes an image or — withimage-nameempty — uploadsdist/and pushes nothing. That empty-name mode is the right shape for a PR check on a registry, and the skeleton uses it exactly that way.KB_REGISTRYnow resolves absolute paths, so the registry is read where it lives rather than copied into this checkout. Both the orchestrator and Astro resolve it identically, so they cannot end up reading two different files.Provenance
Every build writes
dist/kb-build.json:{ "builtAt": "2026-09-04T13:44:49.043Z", "registry": "apps.json", "strict": true, "sources": [ { "source": "AbsaOSS/my-service-docs", "version": "v1.4.0", "slugs": ["my-service"] } ] }An image is opaque once pushed, and "which release produced this page" is not answerable from the registry alone once
latesthas moved. The workflow keeps it as a 90-day artifact and renders it into the job summary.The contract and the skeleton
contract/DEPLOYMENT.mdis the definition the private repo is built from: what lives where, the registry rules, the GitHub App that reads the docs repos (contents: read, installed per repo, hour-long tokens, no personal access token), the narrower tokenrepository_dispatchneeds and why it must be scoped to the deployment repo alone, the trigger set, and rollback.examples/deployment-repo/is the skeleton to copy:apps.json, abuild.ymlthat mints an App token and calls the reusable workflow, and aregistry-check.ymldry run on PRs touching the registry.It deliberately stops at "an image is pushed". Getting one running is the cloud's business, and the environments and approvals around it are where the real policy lives.
Two review notes
The CI dry run is pinned to the commit under review (
kb-ref: ${{ github.sha }}). Without that the job would check out the default branch, build master, and pass green while the change under review was broken.The new spec cannot clobber
dist/. Every strict case is rejected during registry validation, before anything is staged or written, and the provenance assertions read the manifest once at module load. An earlier draft ran full builds against the shareddist/and brokebuild-integrity— worth knowing if you extend it.Verification
npm test(embedded, +9 new)playwright.config.ci.js(standalone)After this lands
The epic is complete except for the example repo migration (AbsaOSS/knowledge-base-docs-example#3) and cutting
v1.0.0, which turns the@v1references throughout the contract docs into a real tag.🤖 Generated with Claude Code
https://claude.ai/code/session_01PYtmxSeSXFg4wEXRcKCmAi