feat: Phase 7 — CI, release automation and container images - #31
Merged
Conversation
Phase 7 covers CI, release automation and packaging the three components that had no container image. Phase 8 covers the Kustomize base, metrics and the k3s end-to-end run. Phase 9 works through the five points the design spec itself lists as open in §15. The identity broker task carries the requirement that per-tenant roles must look identical whether a user signs in through the tenant's own federated IdP or through a local broker account, and that roles are never taken from a federated assertion.
Multi-stage build compiles the SPA with `pnpm generate` (Node version taken from ui/.nvmrc, currently 24) and serves the static output via nginx-unprivileged (uid 101, port 8080), with try_files SPA fallback and a cache policy that never caches index.html while treating hashed _nuxt/ assets as immutable. Also adds a root .dockerignore: without it, `COPY ui/ ./` pulls the host's gitignored ui/node_modules over the image's freshly installed one and the build fails (or, worse, could ship stale local build output).
…er Maven repository
runner/Dockerfile copied runner/vendor/BlueMapS3Storage.jar out of the build context, but runner/vendor/ is gitignored and therefore does not exist in a fresh clone -- the publish-runner job could never have built the image in CI. Download the addon in the existing fetch stage instead, pinned by BLUEMAP_S3_STORAGE_VERSION, exactly the way hosting/Dockerfile already fetches the same jar from the same release. The addon still lands at /work/config/packs/bluemap-s3-storage.jar. Verified by building the image with runner/vendor/ moved out of the way.
… bootstrap sha extra-files paths are resolved relative to the package path, so "telemetry-addon/build.gradle.kts" resolved to telemetry-addon/telemetry-addon/build.gradle.kts. Generic updaters use createIfMissing: false, so the miss was silent: tag, release and manifest were bumped while the version marker in the build file stayed at 0.1.0 and the publish job republished 0.1.0 over the previous release. Write both paths package-relative, matching the root package's entry. bootstrap-sha is compared against the full 40-character SHA, so the abbreviated value never matched and the first release PR would have swept in all of phases 1-6.
docker-publish.yml@v2.4.0 declares a concurrency group of
docker-publish-${{ github.workflow }}-${{ github.ref }} with
cancel-in-progress: false. Both expressions are identical for all six of our
calls, so all six shared one group and the queued jobs cancelled each other. A
caller cannot override a callee's concurrency, so chain the six publish jobs
with needs: to keep at most one of them queued. Each job keeps a standalone
!cancelled() gate, so the edges convey ordering only: nothing publishes without
a release, and publish-hosting/publish-ui still take their content from the
checkout rather than from the Gradle artifact. The proper long-term fix is a
concurrency-suffix input on the central docker-publish.yml.
context-path: "." shipped the whole working tree, including .git and every
build directory, to five jobs that had already checked the repository out.
Narrow it to the four shadow jars. docker-publish checks out first and only then
downloads the artifact over the context, and upload-artifact roots a **-prefixed
glob at the workspace, so the <module>/build/libs/ prefix the Dockerfiles COPY
by name is preserved.
Give the two Maven publish jobs contents: read instead of inheriting the
workflow-level contents: write, matching the six Docker jobs.
pnpm/action-setup reads ${GITHUB_WORKSPACE}/package.json by default; this repo
has no root package.json and defaults.run.working-directory does not apply to
action inputs, so the ui job failed on its first step on every pull request with
"No pnpm version is specified." Point it at ui/package.json.
The central markdown-lint workflow defaults config-file to .markdownlint.json,
which does not exist here, so markdownlint-cli2 threw ENOENT on every docs PR.
Pass .markdownlint-cli2.jsonc.
Pin the Gradle PR build to ubuntu-latest: the central default is a three-OS
matrix, this project targets Linux only and several tests are path-separator and
line-ending sensitive.
Restore .github/workflows/** in the code path filter so a CI-only change still
runs the build, and add gradlew.bat.
The MD041 exemption cited SDD briefs and reports under .superpowers/sdd/, but those files are not tracked; the tracked tree lints clean with the rule on. The telemetry-addon publication comment claimed the thin jar would leave consumers resolving relocated dependencies. Every dependency of that module is compileOnly, so its shadow jar relocates nothing. State the actual reason it is published: it is the same file the runner image ships, so no second jar can drift from it. What is published is unchanged.
Contributor
Test results496 tests 496 ✅ 40s ⏱️ Results for commit af528b5. |
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.
Builds out everything around delivery: this repository had no CI at all before, and three of its six components had no container image.
What lands
build-pr.ymlbuilds and tests the Gradle modules and the Nuxt dashboard on every pull request;markdown-lint.ymllints the (substantial) documentation;close-invalid-prs.ymlguards against fork-default-branch PRs.:paperflavour forpaper-worldpush's version scheme.telemetry-addonandpaper-worldpush, the latter two because they hang off foreign version tracks (design spec §4). The version marker moved out ofgradle.propertiesintobuild.gradle.kts.operator,apiandui— the three that had none. The first two follow the conventionsingest/runneralready set; the dashboard is a static SPA build served by unprivileged nginx.telemetry-addonandpaper-worldpushto the OneLiteFeather Maven repository.Notes for review
The whole-branch review found four defects that would have failed only in CI, never locally, and they are worth knowing about:
runner/Dockerfilecopiedrunner/vendor/BlueMapS3Storage.jar, which is gitignored — it only existed on developer machines from phase-1 setup. It is now fetched in a builder stage with a pinned version, the same wayhosting/Dockerfilealready did it.extra-filespaths relative to the package path, sotelemetry-addon/build.gradle.ktsbecametelemetry-addon/telemetry-addon/build.gradle.ktsand was silently skipped. Every release would have republished0.1.0over the previous one.docker-publishcalls share one concurrency group in the central workflow, so they would have cancelled each other. They are serialised with aneeds:chain until the central workflow grows aconcurrency-suffixinput.pnpm/action-setuphad no version source: there is no rootpackage.json,packageManagerlives inui/package.json.Two values in the plan turned out to be wrong and were corrected against the real sources: the Maven repository is
OneLiteFeatherRepositorywithONELITEFEATHER_MAVEN_USERNAME/_PASSWORD, andhostingbuilds fromhosting/as its Docker context rather than the repository root.Still open, recorded rather than fixed
No module carries an SLF4J provider, so no container writes application logs to stdout — which §13.1 (Alloy → Loki) and §11.1 (log streaming to the UI) both depend on. Recorded as a new open point in the design spec §15; it belongs to the Phase 8 observability work.
🤖 Generated with Claude Code