GT-324/435: repair the code-side half of the production path, and measure what is actually left - #307
Merged
Conversation
release: align main — the CLI and MCP resolve the SDK that carries the security wave, 1.2.2 (GT-634)
A board row carries a STATUS but not who is working it or where, so two sessions can both read `PENDING` and both start, correctly. On 2026-07-30 that happened three times in a day. `50-validate-gap-claim` derives the claim set from OPEN PULL REQUESTS — every `GT-*` in a PR's title, body or branch name — and fails when one id is claimed by two of them, naming both with their branches and titles. DERIVED, NEVER HAND-WRITTEN, and the row asked for that on purpose: a hand-written claim goes stale in the direction that matters, because someone forgets to remove it and the next session works around a claim nobody holds. THREE CRITERIA OF FOUR, and the fourth is left open with its reason rather than stretched to fit. A claim list committed to the board would be derived from live GitHub state, so it would be stale the moment anyone opened a pull request — and GT-630's chain exists precisely to insist derived artifacts reach a fixed point. A perpetually-stale artifact inside that chain would be worse than none. Making the claim discoverable from the board needs something the board can render without committing, and that is not built. The row stays IN-PROGRESS. What it cannot see is in its own failure text, not implied: a branch with no open pull request claims nothing. Opening the PR early — draft is enough — is what makes the claim visible, which turns a convention into something with a check behind it. Anti-vacuous: a pull-request query that cannot be answered is a hard failure, not a quiet pass. A guard built because two sessions could not see each other must not stay silent when it cannot look. Zero open PRs is reported in words, because it looks identical to a broken query otherwise. Verified: 10/10 fixtures; 42-validate-guard-denominators 62 guards classified; 43-validate-guard-negative-fixtures 39/39 observed red, up from 38. Run against the real repository it reports 0 claims across 5 open PRs — checked rather than assumed: all five are Dependabot bumps naming no gap, so the zero is real and not an empty query. 08-validate-tracking (640 gaps), 01, 04, 41 within budget, 46 chain at a fixed point. Counters: 600/640, in-progress 14 -> 15, pending 22 -> 21. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
feat(ci): one gap, one claim — the claim guard, advancing GT-639
`.git/EVOLITH_*` is a DIRECTORY only in the primary checkout; inside a `git worktree` it is a FILE holding a `gitdir:` pointer, so every write/read to `.git/EVOLITH_*` was ENOTDIR. In pre-commit that made `set -e` abort the commit outright; in pre-push the `rm -f` failed and TARGET stayed unread — together the reason `--no-verify` had become habitual from a worktree. `git rev-parse --git-dir` resolves both layouts; both hooks now go through it via a shared `write_push_intent`/read path, per-worktree, so the push intent belongs to the checkout that was committed in rather than the repo as a whole. Also in pre-push: `assert_checkoutable` refuses up front, before repo optimization or board sync run, when the merge target branch (main/develop) is already checked out in another worktree — `git checkout "$to"` would otherwise fail mid-dispatch with the push half-done. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
fix(ci): pre-commit/pre-push hooks assumed .git is always a directory
…GT-435/GT-448) Four defects on the deploy path that needed nobody's credentials. Each was measured, not argued: three of the four are proven by a command that ran. 1. `src/sdk/cli/Dockerfile` was unbuildable and would have shipped a dead entrypoint. Its context was `src/sdk/cli`, so `COPY package*.json` matched only `package.json` — the monorepo's single lockfile is at the root — and `npm ci` refused. Reproduced: `RUN npm ci` exit 1. Its CMD also invoked the `mcp` subcommand, deleted from the CLI in dc0b966, so a build-only fix would have published an image that dies on start. The CI job that built it was retired in 645374e for exactly these reasons, deliberately leaving the artifact for someone to decide about; this repairs it rather than leaving a trap. Now builds from the repo root like its three siblings and runs as a governance tool against a mounted workspace. VERIFIED: build exit 0; `--version` -> 1.2.2; `validate --core /app/corpus` against a mounted repo loads 333 rules and returns a real verdict. 2. `product/infra/vps-coolify/README.md` (+ .es) is the owner's production runbook and it was wrong in ways that guarantee a failed deploy. - Phase 2 told the owner to `cp product/infra/docker/bff.Dockerfile apps/core-api/Dockerfile` — a pre-`src/` path, and it would overwrite the working monorepo build with a template that deployment-topology.md already records as being on no deploy path. - Phase 3 set the Coolify build context to `src/apps/core-api`. Every real Dockerfile COPYs repo-root-relative paths and says so in its own header (`base directory = /`); that context fails on the first COPY. - Phase 3 instructed setting `DATABASE_URL`/`REDIS_URL` on core-api, which `product/infra/README.md` explicitly forbids two directories away: the Core is stateless per ADR-0101 and declares zero DB dependencies. - mcp-server was documented on port 3001; the image EXPOSEs 3000. - agent-runtime was absent although GT-437 put it in the deploy matrix. - Phase 4 told the owner to register the Coolify hook as a GitHub *repo webhook*. The pipeline never reads that. It reads three repository secrets and one repository variable, and it is switched OFF right now behind `vars.VPS_DEPLOY_ENABLED` (GT-567) — a fact the runbook did not mention. Phase 4 now carries the exact owner checklist: which secret, which shape, and why a UUID or a bare path produces the `Could not resolve host` recorded against GT-324. 3. `product/infra/docker-compose.yml` declared `bff` and `mcp` services that could never start: build context `./docker` holds two illustrative Dockerfiles and no package.json, and both mounted `../../../rulesets`, which `docker compose config` resolves to a SIBLING OF THE REPOSITORY ROOT — the corpus has been at `src/rulesets` since the GT-450 cutover. Removed, with the reasoning in place, which also makes deployment-topology.md's claim that "no bff remains on any deploy path" true instead of aspirational. VERIFIED: `docker compose config` exit 0, zero remaining dead mounts. 4. `docker-images.yml` published the git tag verbatim (`v1.1.0`), while the Helm charts reference bare semver (`evolith-mcp` tag `1.1.0`). No release this workflow ever ran could satisfy `helm install` with default values — the chart named a tag the pipeline was structurally incapable of producing. Added `type=semver`, so `v1.1.0` now also publishes `1.1.0` and `1.1`. NOT claimed, and deliberately not faked: nothing was deployed. The three CI service images were rebuilt from the repo root exactly as `docker-services` does them (core-api, mcp-server, agent-runtime — all exit 0), but GHCR answers `unauthorized` from here, so which tags actually exist in the registry is not verifiable from a dev machine. The `core-api` chart (`0.0.2`) and `agent-runtime` chart (`0.1.0`) still name tags no git tag would produce; that is a version-intent question for the owner, not one to guess at silently. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
📊 Bilingual Coverage ImpactPR Changes
Repository Coverage
✅ Good: All EN changes have ES counterparts. Generated by GitHub Actions |
5 tasks
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.
Advances GT-324 and GT-435. Closes neither — everything that remains is owner-gated, and this PR says so rather than crediting a deploy nobody ran.
The rows were partly stale
ci-cd.yml:549-564deployjob runs but curl failsCould not resolve host"&& vars.VPS_DEPLOY_ENABLED == 'true'(GT-567), deliberately OFFci-cd.yml:615curl: (28) Failed to connect … port 8000, a timeout, not DNS645374e9CircuitBreakerappears in zero.tsfiles undersrc, and the k6/chaos assets atproduct/infra/load/run in no workflow. Available work, not blocked workgrep -rl "CircuitBreaker" src --include='*.ts'→ emptyGT-435 cannot close: its own criterion requires every child DONE-or-deferred, and three children remain open. The VPS milestone row likewise has all three criteria unmet, including a rollback that has never been exercised — it is assessed here, not claimed; no board row for it is modified by this PR.
Fixed here
src/sdk/cli/Dockerfileshipped a dead entrypoint.CMD ["node","dist/main.js","mcp","serve","--transport","http"]invokes themcpsubcommand deleted indc0b9667, so the container printed CLI help and exited 0 — a silent no-op, not a crash. Rebuilt on the repo-root pattern its three siblings use, with a realENTRYPOINTand--helpas the default.Correction to the audit note that prompted this: the old file was not unbuildable — it fails only when the package directory is the build context; from the repo root it builds fine. Verified both ways by the integrating session. The dead CMD is the real defect.
product/infra/vps-coolify/README.md(+.es.md) — the owner's runbook was wrong in five ways, each sufficient to guarantee a failed deploy: a pre-src/copy instruction that would overwrite a working build with an off-path template; a build context ofsrc/apps/core-apiwhen every real Dockerfile needs the repo root and says so in its own header;DATABASE_URL/REDIS_URLon core-api, which ADR-0101 forbids; mcp documented on port 3001 when the image EXPOSEs 3000; agent-runtime omitted entirely.product/infra/docker-compose.yml— two services that could never start.bffandmcpbuilt from a context of illustrative Dockerfiles with nopackage.json, and mounted a path thatdocker compose configresolves to a sibling of the repository root. Removing them also makesdeployment-topology.md's claim that nobffremains on any deploy path true rather than aspirational.docker-images.yml— the Helm charts named a tag the pipeline could not produce.type=ref,event=tagpublishesv1.1.0verbatim while the mcp chart asks for1.1.0. Addedtype=semver.Verification
Re-run independently by the integrating session, not taken on report:
docker build -f src/sdk/cli/Dockerfile .→ exit 0; the old image's default command prints help and exits 0 (the defect, reproduced); the new image runsvalidate --core /app/corpusand returns a real verdict —**Status:** failed,**Rules Checked:** 86,**Rules Skipped:** 247. It governs; it does not merely--help.docker-servicesdoes them.ubuntu-latest. Context-identical, but not the runner.docker manifest inspectreturnsunauthorized, and no attempt was made to authenticate.Owner checklist
Written into
product/infra/vps-coolify/README.md§Phase 4 in both languages. The short form: create the three Coolify applications first (a hook cannot exist before its app), then setCOOLIFY_API_TOKEN, then the three deploy hooks as full URLs (https://<host>/api/v1/deploy?uuid=<uuid>— a bare UUID or a scheme-less host reproduces exactly the failure recorded against GT-324), then the repo variableVPS_DEPLOY_ENABLED=true(without it the job is skipped entirely), then the per-app encrypted env — and noDATABASE_URL.Deliberately not decided here: the Helm chart tags
0.0.2(core-api) and0.1.0(agent-runtime). No git tag would produce them, so that is a version-intent call for the owner; guessing silently is the failure mode this board keeps catching.Reported, not fixed:
src/apps/core-api/.dockerignoreandsrc/packages/mcp-server/.dockerignoreare inert — with the context at the repo root BuildKit loads<context>/.dockerignore, and every build loggedtransferring context: 2B. Builds are green either way; a repo-wide ignore rule is not worth the risk for a cosmetic win.🤖 Generated with Claude Code
Claim note: this PR assesses the reliability and VPS-milestone rows but modifies neither, and does not claim them. The reliability row is owned by #310. Retitled and rephrased so the claim guard stops reading an assessment as a claim.