diff --git a/qualification/README.md b/qualification/README.md index 72ddf9a..9469c85 100644 --- a/qualification/README.md +++ b/qualification/README.md @@ -248,8 +248,10 @@ part of the Server hook's combined qualification. ### Composer through a reviewed PHP launcher -The catalog currently admits no PHP CI launcher in `runtime_setups`. The earlier -Sample App, CLI and Waterline records were withdrawn after the +The catalog admits only Waterline's corrected PHP 8.2 build launcher in +`runtime_setups`, with read-only controls, twelve exact source bindings and +canonical Git modes. Its final hosted caller qualification remains separate. +The earlier writable Sample App, CLI and Waterline records were withdrawn after the [writable control-path finding](https://github.com/durable-workflow/.github/issues/297#issuecomment-6854260334). Their repository/state mounts allowed container code to replace a host launcher or wrapper before a later nominal verifier invocation. Historical source or TLS diff --git a/qualification/composer-container-trust.json b/qualification/composer-container-trust.json index beaeea2..26304e6 100644 --- a/qualification/composer-container-trust.json +++ b/qualification/composer-container-trust.json @@ -60,5 +60,72 @@ ] } }, - "runtime_setups": {} + "runtime_setups": { + "waterline-php82-build": { + "target": "waterline", + "workflows": { + "php.yml": [ + "build" + ] + }, + "commands": [ + "bash \"$GITHUB_WORKSPACE/scripts/ci/setup-php-container.sh\" \"8.2\"" + ], + "environment": [ + "WATERLINE_PHP_CI_IMAGE", + "WATERLINE_PHP_CI_ROOT", + "WATERLINE_PHP_CI_STATE" + ], + "files": { + "docker/php-ci/Dockerfile": { + "sha256": "81d23fbbee7e4ee97ca14df9d17982aa664335b94499c5019662259fe0387eaa", + "mode": 420 + }, + "docker/php-ci/Dockerfile.dockerignore": { + "sha256": "a26d7f1f5ae2cac900e5e32bfaa0f08bd7a8435847095990c9bfe09d0edbf264", + "mode": 420 + }, + "docker/php-ci/verify-php-trust.php": { + "sha256": "529c282e7422202bf25565431a6391de388f86dfe5bfd6c75cf21daa56ffa75e", + "mode": 420 + }, + "docker/brotli/install-vendor.sh": { + "sha256": "d7eb8b2a3e0af1c3072a56bcb81632b22f061f211d8fe5e57ee901c8bf94c92f", + "mode": 420 + }, + "docker/brotli/verify-runtime.php": { + "sha256": "580cc465e349f3872eca592355294e7320fe7f1a4560ff5d4c83d5900921c00e", + "mode": 420 + }, + "docker/brotli/verify-system-brotli.c": { + "sha256": "3353927d206aaf5e63157e46be715c6cbee46bac777e29f8f9e25c4c1f656667", + "mode": 420 + }, + "docker/brotli/version": { + "sha256": "e064953e075ddb99fb7b48ad29e215f2dfa075812c127eb1d6ab680e9920fba2", + "mode": 420 + }, + "scripts/ci/php-container-images.json": { + "sha256": "3314ecfd6c32641af1d5475f8e57f4421979f67e05db1f98c79ef7b08d41b944", + "mode": 420 + }, + "scripts/ci/run-php-container.sh": { + "sha256": "27b2601a4907737efa9ce157524e36ef0932df97d2d23b515cf65997e4f3725b", + "mode": 493 + }, + "scripts/ci/setup-php-container.sh": { + "sha256": "db0a276dea94aef70c3873ffc14e0ebf88e5baf4a6145e1a61a281d5835f59e4", + "mode": 493 + }, + "tests/ci/test_php_container_tls.py": { + "sha256": "9b229ac8400a047bfc6cf60ede16db899298e01b3543b840ab692656c2ef28fb", + "mode": 420 + }, + "tests/ci/test_php_container_isolation.py": { + "sha256": "821e1c8db3da47ec502efd704044bffb55b744ce7fe1d42c71c367ba0c9e9924", + "mode": 420 + } + } + } + } } diff --git a/tests/fixtures/waterline-php82-build.yml b/tests/fixtures/waterline-php82-build.yml index 04f0d03..d1ab354 100644 --- a/tests/fixtures/waterline-php82-build.yml +++ b/tests/fixtures/waterline-php82-build.yml @@ -2,6 +2,7 @@ jobs: build: needs: + - action-policy - classify - frontend if: ${{ needs.classify.outputs.qualification-class == 'complete' }} diff --git a/tests/test_composer_trust_contract.py b/tests/test_composer_trust_contract.py index c4c329e..2ea441e 100644 --- a/tests/test_composer_trust_contract.py +++ b/tests/test_composer_trust_contract.py @@ -374,13 +374,12 @@ def setUp(self): repository = Path(__file__).resolve().parents[1] self.policy = json.loads((repository / "qualification/policy.json").read_text()) self.document = yaml.safe_load((repository / "tests/fixtures/waterline-php82-build.yml").read_text()) - retired = json.loads((repository / "tests/fixtures/retired-writable-runtime-setups.json").read_text()) - self.catalog = {"requests": {}, "runtime_setups": {"waterline-php82-build": retired["waterline-php82-build"]}} + self.catalog = json.loads((repository / "qualification/composer-container-trust.json").read_text()) temporary = tempfile.TemporaryDirectory() self.addCleanup(temporary.cleanup) self.root = Path(temporary.name) - # Exercise mandatory enumeration using inert historical recipe data. - # This fixture grants no admission in the current production catalog. + # Bind inert bytes to the corrected recipe to exercise the actual target + # policy offline. Retired sources have separate refusal controls. recipe = self.catalog["runtime_setups"]["waterline-php82-build"] for name, expected in recipe["files"].items(): path = self.root / name @@ -400,6 +399,12 @@ def check(self, document): def test_proposed_build_enumerates_all_guarded_requests(self): self.assertEqual(4, len(self.check(self.document))) + def test_proposed_build_accepts_canonical_git_checkout_permissions(self): + recipe = self.catalog["runtime_setups"]["waterline-php82-build"] + for name, expected in recipe["files"].items(): + (self.root / name).chmod(0o755 if expected["mode"] & 0o100 else 0o644) + self.assertEqual(4, len(self.check(self.document))) + def test_deleting_every_live_verifier_cannot_disable_enumeration(self): document = deepcopy(self.document) steps = document["jobs"]["build"]["steps"]