Skip to content

1.9.79: the container image ships no test suite, and its runners refuse the web - #306

Merged
tracking202 merged 2 commits into
1.9.79from
1.9.79-tests-not-served
Oct 28, 2026
Merged

tracking202 merged 2 commits into
1.9.79from
1.9.79-tests-not-served

Conversation

@tracking202

Copy link
Copy Markdown
Owner

Backlog: CR7W

What and why

The Dockerfile's app stage runs COPY . /var/www/html, and .dockerignore did not exclude tests/. So every built image served the test suite from the web root, including the image the Coolify and production compose stacks deploy. About thirty scripts under tests/ are integration-test child processes or live-pass drivers. Each acts on whatever database its environment or its defaults name (root with no password, database prosper202), with no sign-in. The same is true of any checkout served as the web root, such as the dev compose stack's bind mount or a clone on a web server.

Most of these scripts stop over HTTP before they act, but only by accident: $argv is a CLI-only global. Under a web SAPI only $_SERVER['argv'] gets the query string (measured). The schema installer runner and the ladder runner read no argument, so they run. Before, measured on php -S against a scratch database:

  • GET /tests/Install/fixtures/schema-installer-runner.php answered SUCCESS and created 164 tables.
  • GET /tests/Upgrade/fixtures/ladder-runner.php ran the upgrade ladder.
  • Every other runner answered 200, with a PHP error naming the server's paths.

Changes

  • .dockerignore now leaves tests out. The new lines are separate from 1.9.79: the cron job holds an flock() for the whole run #299's cron-lock line, and the trial merge applies cleanly.
  • Docker Image workflow: two new checks. The built image's docroot must have index.php and no tests/, and after boot a request for a runner must answer 404.
  • Every runnable script under tests/ (26 of them) now refuses any SAPI but the CLI as its first statement after declare/namespace/use. tests/live/goals-ingest.php already did. The guard has the same shape and answer as 1.9.79: find and move back the traffic sources the rename took before 1.9.79 #292's runner, if (PHP_SAPI !== 'cli') { http_response_code(404); exit; }. Under the CLI nothing changes.
  • tests/Release/TestScriptsAreNotServedTest.php (new structural test):
    • How "runnable" is decided: it reads every .php file under tests/ by its top-level statements, in order. These do not count as acts:

      • declare, namespace and use statements;
      • class-like and named-function declarations;
      • a require/include whose path is __DIR__ or dirname(__DIR__[, n]) plus a plain string, and stays in the repository: outside tests/ (served on its own anyway) or a .php file inside it (held by this same test).

      The first other statement must be the guard. A construct it cannot read counts as an act (the loud direction): a load from a variable, a top-level const, return, inline HTML or a shebang, a block declare. In a namespaced file, or one with a function or constant import, the guard must use \PHP_SAPI and \http_response_code.

    • The agent-eval router is the one listed exception, with its reason: it is php -S's router, so it runs under cli-server. A second test removes it from the list once it is guarded or gone.

    • It holds the release zip to shipping none of these scripts (ReleaseTree::ships()).

    • It refuses .phtml, .pht, .phar and .php5-style files under tests/, since a host may run them as PHP and the reading does not see them.

    • It starts php -S on the repository and requests every guarded script. Each must answer an empty 404. A function library answering 200 is the control.

    • It reads .dockerignore the way BuildKit does and holds every file under tests/ out of the context. The files the Dockerfile copies by name, and the app's entry points, must stay in.

    • It plants 46 file shapes through a data provider.

  • Changelog lines in both files; build/README.md updated.

How the release zip leaves tests/ out

build/release-manifest.php does it with keep_only: tests keeps only fixtures, and tests/fixtures keeps only agent-eval and app-sdk-contract. It also uses exclude_nested for tests/fixtures/agent-eval/ci, where the router lives. So the zip ships no PHP from tests/. The new test now holds that.

Dockerfile

No stage uses tests/. The only COPY sources from the context are composer.json, build/scripts/docker-entrypoint.sh and .. Composer's autoload-dev (Tests\ → tests/) is dropped by dump-autoload --no-dev, and no runtime code reads tests/.

Verification

Ladder (verify.sh --since origin/1.9.79 --changed, with actionlint 1.7.12, CI's pinned version, on PATH):

Scope report
------------
  actionlint PASS
  patterns   PASS
  phpcs      PASS
  phpstan    PASS
  syntax     PASS
  unit       PASS

Not covered by this script:
  live      stand up an instance, drive the path a user would take
  agent     add and run a case under tests/fixtures/agent-eval/cases/
  ci        a workflow still in_progress has not passed; read the run

The unit tier ran 6,156 tests with 4 skipped, the documented "User class requires DB singleton" skips. PHP is 8.3.6, as in CI.

  • Live, before and after (php -S on port 8151, serving the worktree, with P202_TEST_DB_* naming a scratch MariaDB 10.11 database):
    • Before: all 28 runnable scripts answered 200. The schema installer created 164 tables and the ladder ran.
    • After, on a fresh empty database: all 27 guarded scripts answered an empty 404, the listed router answered an empty 200 (it returns false), and the database still had 0 tables.
    • No 202-config.php was needed.
  • Build context, through Docker's own transfer. I ran a private dockerd and used a FROM scratch stage with the app stage's COPY . /var/www/html, exported with --output type=local:
    • With 1.9.79's .dockerignore, tests/ reached the docroot: 611 PHP files, schema-installer-runner.php among them, 2,621 files in all.
    • With this branch's, tests/ is absent and index.php, 202-config/connect.php and build/scripts/docker-entrypoint.sh are present, 1,892 files in all.
    • The test's .dockerignore reading matches BuildKit file for file on both: 0 differences either way. It also matches on 14 pattern shapes (negation, *, **, ?, a leading / or ./, comments and padding) on a synthetic context.
  • Plants: 18 defects, each planted in the real tree, then restored and checked with cmp. Each fails the test it targets:
    • a guard removed, moved below a load, missing exit, or using php_sapi_name();
    • a new unguarded runner, one under tests/live, and a PHPUnit case that acts at file scope;
    • a .phtml file under tests/, and the router guarded;
    • .dockerignore without tests, re-including tests/fixtures, naming only tests/*/fixtures, or leaving out everything, and a Dockerfile.dockerignore placed beside the Dockerfile;
    • the zip shipping tests/Attribution or agent-eval/ci;
    • the reading wrongly accepting a 200 guard. The HTTP check then fails on the runner that answers 200.
  • Integration suites that spawn the runners: tests/run-integration-suites.sh (CI's runner, one fresh database per suite, p202_cr7w_test) passed 145 of 146 suites, 1,365 tests. All 29 suites that spawn a runner I touched passed. The one failure is MysqlLookupRepositoriesIntegrationTest::testTheFactoryCachesThroughTheBootstrapHelpers, and it is environmental: this PHP has no memcached extension, and the test extends \Memcached.
  • Unit tests that spawn the guarded scripts all pass: ClickPathUncaughtTest, TrackerPlaceholdersReadTheClickOnlyWhenAskedTest, FallbackRedirectScriptsTest, IntegrationSuiteSelectionTest, SourceScanTest and ReleaseTreeTest.
  • tests/live/schema-diff.php from the CLI behaves as before: 0 differences on identical databases, refusing an empty one, and usage with exit 2.
  • 1.9.79: find and move back the traffic sources the rename took before 1.9.79 #292: on a trial merge of 1.9.79-ts-taken-before-fix, this test passes and reads 1.9.79: find and move back the traffic sources the rename took before 1.9.79 #292's traffic-source-repair-runner.php as guarded, unchanged. 1.9.79: find and move back the traffic sources the rename took before 1.9.79 #292's own TrafficSourceRepairRunnerIsNotServedTest also passes there.

Not run

  • The real image build: this sandbox's daemon cannot fetch php:8.3.36-apache or run its apt, pecl and composer steps through the TLS-intercepting proxy without changing the Dockerfile. The Docker Image workflow runs on this PR, because .dockerignore changed, with the two new checks.
  • upgrade-equals-install.sh, which drives schema-diff.php and the router. Both are covered above in isolation.

For the reviewer

  • The image now leaves out all of tests/, while the zip keeps two fixture trees: the SDK contract vectors and the eval cases the CLI help names. Nothing in the server reads them, so the image doesn't need them.
  • A top-level load of a file the repository holds counts as not an act. That keeps three PHPUnit cases inert without a guard: DollarFormatTest, MobileAppsRegistrationTest and HtmlReportFormatterTest, which require_once an app function file at file scope. A guard's exit in a file PHPUnit loads in-process would silently end a run under a SAPI other than the CLI, such as phpdbg.
  • The guard has one accepted spelling. A different but equivalent spelling is refused, and the failure message prints the exact text to use.

Generated by Claude Code

…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
Copilot AI balanced review requested due to automatic review settings October 28, 2026 10:12
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ⚠️ Failed 2026-10-28T10:12:37.282234Z
We couldn’t complete this review. Comment @codex review to try again.
c26911b PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

@github-actions

Copy link
Copy Markdown
Contributor

Code review: CLEAN

For c26911b: 0 issue(s). The review is encrypted for the maintainers in the review-findings artifact of this run.

@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Adversarial review: NON-BLOCKING

For 1821e28: 3 finding(s). The report is encrypted for the maintainers in the review-findings artifact of this run.

Rewritten on every push; the check fails on BLOCKING.

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
@tracking202
tracking202 merged commit 4e1ef16 into 1.9.79 Oct 28, 2026
24 checks passed
tracking202 added a commit that referenced this pull request Oct 28, 2026
…ike the others

#272's JVZooAdapter read 202_users_pref itself, which the branches that made
AccountSecrets::stored() the one read (a deleted account owns no key) did not
know of; StoredSecretsReadThroughAccountSecretsTest holds every adapter to it.
Also: #323's session runner refuses a web request first (#306).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants