feat(ci): deploy the Core from GHCR so the Tracker↔Core seam is tested by CI - #144
Merged
Conversation
…d by CI `core-integration` asserts the SIX interfaces of the pair and had never run in this pipeline: it needs the Core, another product, which nothing here deployed. So the seam was verified by hand or not at all. The first time it ran against a real stack it found three defects invisible to every other suite, two of them product defects — one in each repository. The Core already publishes all three service images to GHCR on every merge to its main, tagged with the commit SHA as well as `latest`. That SHA tag is what makes this safe to wire. PINNED, not `latest`, and the file says why: following the Core's tip would turn this repository's pull requests red for a defect merged minutes earlier by someone who has never seen this pipeline. A pinned SHA moves that failure into a diff with a reviewer. The accepted cost is that `core-image.sha` goes stale on purpose and somebody has to raise it — the same discipline the charts already use for their own tags. The Core is deployed by a MINIMAL manifest of this repository's own, not by vendoring its charts: those live in the other repo, would drift here, and would drag in OPA sidecars, HPAs and IngressRoutes this test neither needs nor can maintain. It is a fixture, and it says so. It pins uid 1001 because pinning 1000 is what made the deployed MCP unable to read its own policy.wasm (evolith_arch32#425). `core-integration` enters the robot list only when the Core is actually present, and when it is absent the run SAYS so instead of quietly shrinking — a suite that silently drops its only cross-product robot reads exactly like one that passed it. The gateway also joins the local install: steps 1-4 of that robot go through it, which is part of why the robot was excluded. Verified on a live kind cluster, not argued: the Core rolls out from the pinned refs, the gateway reaches it by in-cluster DNS, and the full suite runs 11 robots — 242 passed, 0 failed, `core-integration` 13/13. Both branches of the inclusion predicate were exercised. NOT verified here, and stated rather than implied: the `docker pull` itself. The packages are PRIVATE, which the first local attempt proved by answering `unauthorized` — and that failure is what exposed the two things missing from this wiring, now fixed: `packages: read` on the workflow and a GHCR login step before the pull. The local run used the same image refs loaded by hand, so everything downstream of the pull is proven and the pull is exercised the first time this lands. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CI ran the seam for the first time and failed on exactly one check: `POST /assistant/converse` → 502. The cause is a three-way mismatch that existed before this change and could not be seen until now. The same local deployment carried three different credentials: this script wrote `local-only-not-a-real-key-core` and `local-only-not-a-real-key-runtime`, while the gateway chart defaults to `local-dev-key`. With the Core outside the cluster nobody ever authenticated against it, so nothing disagreed. The moment the Core came in, every step that goes THROUGH the gateway passed — both ends were `local-dev-key` — and the one step that goes straight from tracker-api to the agent-runtime got 401. What made it expensive to read: that 401 reaches the caller as 502 (LV-26), so a credential problem arrives dressed as an availability problem. It cost time on a laptop two days ago and it cost this run. One key everywhere now. It is a local test credential and its name says so; the value does not matter as long as all three agree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Step 6 of `core-integration` kept returning 502 and the previous commit blamed the wrong thing. Aligning the local keys was correct on its own — three credentials for one deployment is a defect — but it was not this. `values-local.yaml` points the tracker-api at `evolith-runtime-evolith-agent-runtime.evolith-local`: the service names the CORE's Helm chart generates, in the Core's own namespace. This pipeline has neither. It deploys a minimal fixture as `core-agent-runtime` in `evolith-core-local`, so the tracker-api was calling a host that does not resolve — and an unresolvable host surfaces as 502, which reads as "the runtime is down" rather than "the URL points nowhere". I set these by hand on a laptop two days ago and never wired them into `install()`. CI ran the path I had only ever run manually, and that is precisely what it is for. Set unconditionally, including when the Core is absent: it is configuration, not a boot dependency, and a nonexistent host fails exactly as the previous one did. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The robots pass — 242, 0 failed, `core-integration` included — and the step AFTER them broke: the production redeploy timed out with `context deadline exceeded`. Not resource pressure, which is what I assumed first. The log names it: the tracker-api's readiness probe runs a `core-capabilities` health check that calls the Core, and under the PRODUCTION profile `networkPolicy` is enabled, blocking egress to the Core's namespace. The call hangs until it cancels, the pod never reaches Ready, and helm --wait gives up after five minutes. Without a Core the same URL simply does not resolve and fails fast, which is the condition that step was written under and why it has been green on develop for months. So the fixture is retired as soon as the test that needs it is done. It was never part of the Tracker's deployment; it is scaffolding for one step, and leaving it standing changed the conditions of every step after. `if: always()` so a failing robot run still cleans up and the production steps are judged on their own terms. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
core-integrationasserts the six interfaces of the Tracker↔Core pair and had never run in this pipeline: it needs the Core, another product, which nothing here deployed. The seam was verified by hand or not at all.The first time it ran against a real stack it found three defects invisible to every other suite — two of them product defects, one in each repository (beyondnetcode/evolith_arch32#425 and
LV-26).Pinned, not
latestThe Core already publishes all three service images to GHCR on every merge to its
main, tagged with the commit SHA as well aslatest. That SHA tag is what makes this safe to wire.Following the Core's tip would turn this repository's pull requests red for a defect merged minutes earlier by someone who has never seen this pipeline. A pinned SHA moves that failure into a diff with a reviewer. The accepted cost is that
product/infra/core-image.shagoes stale on purpose and somebody has to raise it — the same discipline the charts already use for their own tags.A fixture, not a deployment reference
The Core is deployed by a minimal manifest of this repository's own, not by vendoring its charts: those live in the other repo, would drift here, and would drag in OPA sidecars, HPAs and IngressRoutes this test neither needs nor can maintain. It pins uid 1001 because pinning 1000 is exactly what made the deployed MCP unable to read its own
policy.wasm.The robot enters only when the Core is there
And when it is absent the run says so instead of quietly shrinking — a suite that silently drops its only cross-product robot reads exactly like one that passed it.
The gateway also joins the local install: steps 1–4 of that robot go through it, which is part of why it was excluded.
Verified on a live cluster
core-integration13/13Not verified here, stated rather than implied
The
docker pullitself. The packages are private, which the first local attempt proved by answeringunauthorized— and that failure is what exposed the two things missing from this wiring, now fixed:permissions: packages: readon the workflow (it had onlycontents: read);docker/login-actionstep before the pull.Without both, this would have failed in CI exactly as it failed on my machine. The local run used the same image refs loaded by hand, so everything downstream of the pull is proven; the pull is exercised the first time this lands.
🤖 Generated with Claude Code