Repository navigation
1.9.79: the container image installs composer.lock's set, not a fresh resolution - #312
Merged
Merged
Conversation
…se the web
The app stage of the Dockerfile copies the checkout into the web root, and
.dockerignore did not leave tests/ out, so every built image (the one the
Coolify and production compose stacks deploy) served the test suite. About
thirty scripts there are the child processes of integration tests and the
live passes' drivers: they act on the database their environment or their
defaults name, with no sign-in. Most stop over HTTP before acting only
because $argv is a CLI-only global; measured on php -S against a scratch
database, a GET of the schema installer's runner created 164 tables and a
GET of the ladder runner ran the upgrade.
- .dockerignore leaves tests/ out. Docker's own context transfer (a FROM
scratch stage with the app stage's COPY, exported) carried 611 PHP files
under tests/ before and none after, the app files still there. The Docker
Image workflow now checks the built image has no tests/ and that a request
for a runner is a 404.
- Every runnable script under tests/ (26 of them; tests/live/goals-ingest.php
already was) refuses any SAPI but the CLI first:
`if (PHP_SAPI !== 'cli') { http_response_code(404); exit; }`, the shape
#292's runner uses. Under the CLI nothing changes.
- TestScriptsAreNotServedTest reads every PHP file under tests/ by its
top-level statements: declarations, imports and a load of a file the
repository holds are not acts; the first other statement must be the
guard. The agent-eval router, which runs under php -S on purpose, is
listed with why. It also holds the release zip to shipping none of these
scripts, refuses another extension a host may run as PHP, requests every
guarded script over php -S (each an empty 404, a function library a 200
as the control), and reads .dockerignore as BuildKit does (checked file
for file against BuildKit on this tree and on 14 pattern shapes) to hold
tests/ out of the image's context while the files the Dockerfile copies
by name stay in.
Backlog: CR7W
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
… resolution The Dockerfile's app stage copied composer.json alone and ran composer install, so every image build resolved whatever matched composer.json that day while the release zip ships the locked set. Two images of one release could run different dependencies, an upstream release could change an image with no commit, and no image could be rebuilt as it was. Measured on 10/28: the old shape resolves composer/ca-bundle, symfony/console, symfony/string and three polyfills to newer versions than the lock's. Its comment said no lock was committed; one is. - The app stage copies composer.json and composer.lock (named, so a lock left out of the context fails the build where a glob would skip it in silence; measured through BuildKit) and runs composer validate before composer install --no-dev in one layer keyed on both files. install only warns on a lock that has drifted from composer.json (measured: a changed constraint the lock still satisfies, a changed platform and an added require-dev all install the old set with exit 0); validate exits 2 on each. - The entrypoint's install at startup (the base image with a checkout bind-mounted) refuses a missing lock and runs the same validate first. - The Docker Image workflow checks the built image's vendor/ against the checkout's composer.lock with the release zip's own check, exposed as release-tree.php vendor <app dir> <lock>, and plants a vendor/ in the build context to show .dockerignore keeps a builder's own out. - ImageInstallsTheLockedSetTest reads the Dockerfile by its instructions and holds the install RUN to directly following that COPY into the WORKDIR, as a validate && install --no-dev chain of plain words; what it does not read near composer is refused by name. 29 planted shapes (the 1.9.78 COPY among them) each fail with their own message, 3 equivalent spellings pass. It runs the entrypoint itself against the real composer validate. - build/README.md, RELEASING.md and the Coolify guide say so; changelog lines. The image build itself runs only in CI (the base stage's apt, pecl and composer steps cannot reach their hosts from the sandbox this was written in). Backlog: E016 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Contributor
Contributor
Brings in 1.9.79's new commits. The conflicts were in 202-config/changelogs.json, changelogs.txt, where both sides added lines: the incoming lines are kept as they are and this branch's follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
Brings in tests-not-served (this branch's stacked base), and through it 1.9.79's new commits. The conflicts were in 202-config/changelogs.json, where both sides added lines: the incoming lines are kept as they are and this branch's follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr
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.
Stacked on #306 (
1.9.79-tests-not-served): it edits the same Docker Image workflow and.dockerignore's neighbourhood, so this branch starts from #306's head (c26911b) and must merge after #306. Until then the diff against1.9.79also shows #306's commit; this PR's own change is 2e147be.Backlog: E016
The defect
The Dockerfile's
appstage ranCOPY composer.json /var/www/html/composer.jsonand thencomposer install --no-dev …, under a comment saying nocomposer.lockis committed. One is (and.dockerignoredoes not leave it out). So every image build resolved whatever matchedcomposer.jsonthat day, while the release zip ships the locked set: two images of one release could run different dependencies, an upstream release could change a production image with no commit, and no image could be rebuilt as it was. That matters now that #145 publishes the image on release.Measured on 10/28, the old shape (
composer install --no-devwithcomposer.jsonalone, dry run) resolves six packages away from the lock: composer/ca-bundle 1.6.0 (lock 1.5.14), symfony/console v6.4.48 (v6.4.46), symfony/string v7.4.21 (v7.4.19), and the intl-grapheme, intl-normalizer and mbstring polyfills at 1.43.x (lock 1.41.0, 1.42.0, 1.38.2).What changed
appstage.COPY composer.json composer.lock /var/www/html/, then one RUN:composer validate --no-check-publish --no-check-all && composer install --no-dev …. The vendor layer is cached on both files. The comment is rewritten.composer installonly warns on a lock that has drifted fromcomposer.json. Measured with Composer 2.8.12 on five stale variants:config.platform, and an addedrequire-deveach install the old set with exit 0.requireand an unsatisfiable constraint fail install with exit 4.composer validateexits 2 on all five.pr-checks.ymlandpackage-release.shalready use.composer.lockplanted in.dockerignore, BuildKit fails the named COPY ("/composer.lock": not found).COPY composer.* …copiescomposer.jsonalone and carries on without a word.build/scripts/docker-entrypoint.sh. Its install at startup runs whenvendor/is missing, which means the base image with a checkout bind-mounted. It now:composer.lock;composer validatefirst, and underset -ea stale lock stops the container.composer.lock:build/scripts/release-tree.php vendor <app dir> <lock>, verify'svendorMatchesLock()exposed as a subcommand. One implementation of "vendor/ is exactly the locked runtime set, no dev package" ([Setup] Include ISP preference in rotator #24).vendor/p202-context-markerin the build context. The check then asserts the marker is absent from the image, so a builder's ownvendor/(with the dev set) cannot reach the image.release-tree.phpandReleaseTree.phpare added to the workflow'spaths.tests/Release/ImageInstallsTheLockedSetTest, the machine check:WORKDIRfollowed throughFROM base AS app. The install RUN must directly followCOPY composer.json composer.lock <WORKDIR>/and be avalidate && install --no-devchain of plain words.;/|/quotes/$, RUN flags, heredocs, JSON forms, an escape directive,SHELL, unknown instructions,ADD/COPY --fromof composer's files orvendor/,-d/--working-dir,update/require, a flag before the subcommand,ENV/ARG COMPOSER*other thanCOMPOSER_ALLOW_SUPERUSER, and"config": {"lock": false}in composer.json. I measured that last one: with it set, composer ignores the lock and bothvalidate --check-lockandinstallpass with a fresh resolution.cd /var/www/htmlis redirected by an exported bash function.composeris a stub that execs the real composer forvalidateand stands in forinstall.vendor/already installed.TestScriptsAreNotServedTestreads the Dockerfile's COPY sources, so it now also holds.dockerignoreto sendingcomposer.lock. With the lock planted in.dockerignoreit fails (.dockerignore leaves out composer.lock).build/README.md(Dockerfile and entrypoint rows, boot chain, the "lock file is not up to date" boot failure),RELEASING.md,documentation/deploying-on-coolify.md. A changelog line in both files.Sweep: every place that installs the app's dependencies
appstagebasestagecomposer:2, a moving tag. That decides no package version (the lock does), but it does write the autoloader. Not pinned here: out of this item, raised for a backlog item.docker-entrypoint.shdocker-compose.yamltarget: basewith the checkout bind-mounted, so the entrypoint path above.docker-compose.coolify.yamlappstage, so the fix applies.appstage, so it inherits the fix, and its template pulls that image.build/scripts/package-release.shcomposer validateand then installs from the lock; itsverifyholdsvendor/to the lock.install.sh(host and docker paths)composer installin a checkout or the release zip, both of which carrycomposer.lock, so it installs the locked set. It does not validate. Left unchanged, see "For the reviewer".php-unit,php-lint, …)composer installfrom the tracked lock;pr-checks.ymlvalidates it on every PR.How it was proved here
composer install --no-devfrom the lock installed the 22 locked packages (dist downloads were refused by the proxy, so it fell back to git source). The stale-lock measurements are listed above.dockerd(data under my scratch dir, now stopped and removed):FROM scratchbuild whose COPY is the Dockerfile's line character for character, with the worktree as context, exported with--output type=local.composer.jsonandcomposer.lock, byte-identical to the checkout's.vendor/(7,162 files) is not in the context.validate && installwith the Dockerfile's flags, then the full context copied over, thendump-autoload --optimize --no-dev;release-tree.php vendoragainst the checkout's lock answeredok;'dev' => falseplanted totrueininstalled.php, it refused;php -Son the tree answered/health/with 200.cmp):testTheDefaultStageInstallsTheLockedSet. So does the old COPY alone.actionlint1.7.7 (with shellcheck) on the workflow: clean.shellcheckon the entrypoint: clean.The image build itself ran only in CI, in this PR's Docker Image workflow run. The base stage's apt, pecl and composer steps cannot reach their hosts through this sandbox's proxy, and the Dockerfile was not changed to make it build here.
Verification scope report
.claude/skills/p202-verify/scripts/verify.sh --since origin/1.9.79 --changed, with actionlint 1.7.7 on PATH (unit:Tests: 6194, Assertions: 133456, Skipped: 4., the 4 documented DB-singleton skips):What could not run:
For the reviewer
composer.lock, or one that has drifted fromcomposer.json, where before it installed with a warning. The README's boot section says what to run.&&chain. A cache mount (RUN --mount=type=cache) orSHELL ["/bin/bash", "-o", "pipefail", "-c"]will be refused until the reader is taught them. That is the loud direction, and the message says so.ReleaseTree::verifyVendor()is split. The lock-existence message for the release tree is unchanged, andvendorMatchesLock(root, lockFile)is public.main()'s problem printing moved intoreport()so thevendorcommand can run without loading the manifest.install.shwas not given a validate step. It installs from the lock it ships with. A step there would need a decision about hosts with an older system Composer. Its non-verbose mode also hides composer's stale-lock warning in a log it shows only on failure; that is raised for a backlog item.🤖 Generated with Claude Code
https://claude.ai/code/session_01VRoS2SViuxaucFaKWsFnLr