diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..250ba38
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,3 @@
+include MANIFEST.in PACKAGE.md pyproject.toml
+recursive-include src/password_policy_lab *.css *.html *.py *.typed
+prune tests
diff --git a/Makefile b/Makefile
index 99665e0..b8eaf2d 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,6 @@
PYTHON ?= python3
EVIDENCE_WORK ?= $(CURDIR)/.evidence-work
+DISTRIBUTION_WORK ?= .evidence-work/distribution
PLAYWRIGHT_BROWSERS_PATH ?= $(CURDIR)/.playwright-browsers
EVIDENCE_ENV = \
PLAYWRIGHT_BROWSERS_PATH="$(PLAYWRIGHT_BROWSERS_PATH)" \
@@ -15,8 +16,8 @@ EVIDENCE_ENV = \
TEMP="$(EVIDENCE_WORK)/tmp"
.PHONY: \
- build check dependencies evidence evidence-browser evidence-check lint test \
- typecheck
+ build check dependencies distribution-check evidence evidence-browser \
+ evidence-check lint test typecheck
lint:
@$(PYTHON) -m ruff check app.py scripts src tests
@@ -38,6 +39,11 @@ build:
dependencies:
@$(PYTHON) -m pip check
+distribution-check:
+ @PYTHONDONTWRITEBYTECODE=1 $(PYTHON) scripts/attest_distribution.py \
+ --root "$(CURDIR)" \
+ --work-root "$(DISTRIBUTION_WORK)"
+
evidence-browser:
@mkdir -p "$(EVIDENCE_WORK)/tmp" "$(EVIDENCE_WORK)/xdg"
@$(EVIDENCE_ENV) $(PYTHON) -m playwright install chromium
@@ -52,4 +58,4 @@ evidence-check:
"$(EVIDENCE_WORK)/xdg"
@$(EVIDENCE_ENV) PYTHONPATH=src $(PYTHON) scripts/check_evidence.py
-check: lint typecheck test dependencies evidence-check
+check: lint typecheck test dependencies distribution-check evidence-check
diff --git a/PACKAGE.md b/PACKAGE.md
new file mode 100644
index 0000000..fc53dde
--- /dev/null
+++ b/PACKAGE.md
@@ -0,0 +1,24 @@
+# Password Policy State-Space
+
+`password-policy-state-space` counts constrained visible-ASCII password spaces
+exactly and exposes deterministic inspection, rank, and unrank operations. Its
+production sampler selects one uniform integer rank with `secrets.randbelow`
+and maps that rank to a candidate without retrying invalid strings.
+
+The installed command can inspect a policy without sampling:
+
+```bash
+password-policy-lab inspect --length 20 --format json
+```
+
+`rank` and `unrank` are reversible and are intended only for explicitly public
+test vectors. Both require `--acknowledge-reversible-output`; never use them
+with a credential.
+
+The wheel contains the typed Python package, server-rendered template and
+stylesheet, and the `password-policy-lab` entry point. Repository-only tests,
+portfolio evidence, and browser captures are deliberately excluded from the
+installable distribution and remain available in the source repository.
+
+No project license is currently declared. Public source availability alone
+does not grant permission to copy, modify, or redistribute the package.
diff --git a/README.md b/README.md
index ddee87a..d5c0914 100644
--- a/README.md
+++ b/README.md
@@ -39,8 +39,8 @@ make check
The package supports Python 3.11 or newer. `make check` runs Ruff, formatting,
strict mypy, exhaustive and independent mathematical oracles, real Flask
-request tests, 100% combined line/branch coverage, and the committed-evidence
-integrity check.
+request tests, 100% combined line/branch coverage, the distribution
+attestation, and the committed-evidence integrity check.
Start the pinned production WSGI server on loopback:
@@ -56,6 +56,48 @@ waitress-serve \
Then open `http://127.0.0.1:5000`.
+## Reproducible distribution contract
+
+
+
+`make distribution-check` does not trust the migrated worktree's file modes or
+an implicit setuptools file list. It snapshots exactly 15 stage-zero Git blobs,
+materializes two normalized source trees, builds both with the pinned
+`setuptools==83.0.0`, and validates exact archive inventories:
+
+- the wheel has 17 regular members with fixed ZIP metadata and a complete,
+ canonical `RECORD`;
+- the sdist has 23 regular files and six directories, with fixed modes, epoch,
+ owner fields, member order, and gzip header;
+- a wheel rebuilt from the safely materialized canonical sdist is byte-for-byte
+ equal to the canonicalized wheel produced by each primary build;
+- an offline `pip --target` install from an external working directory imports
+ package code and metadata from that target, finds both package resources, and
+ runs deterministic `inspect --length 20 --format json` twice without sampling.
+
+
+
+Run the attested path with:
+
+```bash
+make distribution-check
+```
+
+The raw [`distribution-check.txt`](docs/evidence/distribution-check.txt) and
+canonical
+[`distribution-attestation.json`](docs/evidence/distribution-attestation.json)
+record the input digest, complete member-level SHA-256 inventory, canonical
+archive hashes, rebuild equality, toolchain, smoke result, and negative claim
+boundaries. `make build` remains a conventional backend build for local
+inspection; its raw sdist contains environment-dependent metadata and is not
+presented as the canonical release artifact.
+
+The attestation is deliberately unofficial. It does not claim a license,
+artifact signature, dependency integrity, cross-platform reproducibility, or
+safety for arbitrary archives. The install smoke uses the current pinned
+checker dependencies without resolving them; it is not a fresh, hash-locked
+dependency environment.
+
## Architecture

@@ -170,6 +212,11 @@ temporary files under this repository. On a minimal Linux image, Chromium's OS
runtime libraries still need to be supplied by that environment; the target
never invokes a privileged system-package install.
+The capture contract waits for fonts and settled layout, preserves the declared
+viewport during full-page screenshots, and pins Chromium to one raster thread.
+That removes subpixel shadow races without replacing the real server-rendered
+interface with a mockup; the exact launch argument is recorded in the manifest.
+

Evidence provenance stays next to the visuals:
diff --git a/docs/assets/distribution-check.png b/docs/assets/distribution-check.png
new file mode 100644
index 0000000..eb46a5c
Binary files /dev/null and b/docs/assets/distribution-check.png differ
diff --git a/docs/assets/distribution-contract.svg b/docs/assets/distribution-contract.svg
new file mode 100644
index 0000000..977f2e9
--- /dev/null
+++ b/docs/assets/distribution-contract.svg
@@ -0,0 +1,111 @@
+
diff --git a/docs/assets/quality-gate.png b/docs/assets/quality-gate.png
index 2347692..66e3e9e 100644
Binary files a/docs/assets/quality-gate.png and b/docs/assets/quality-gate.png differ
diff --git a/docs/assets/setup-workflow.svg b/docs/assets/setup-workflow.svg
index b536961..af5289d 100644
--- a/docs/assets/setup-workflow.svg
+++ b/docs/assets/setup-workflow.svg
@@ -57,6 +57,7 @@
make check
lint · types · tests
+ distribution · evidence
diff --git a/docs/assets/web-home.png b/docs/assets/web-home.png
index 61ee5b7..fcce7fc 100644
Binary files a/docs/assets/web-home.png and b/docs/assets/web-home.png differ
diff --git a/docs/evidence/distribution-attestation.json b/docs/evidence/distribution-attestation.json
new file mode 100644
index 0000000..be66658
--- /dev/null
+++ b/docs/evidence/distribution-attestation.json
@@ -0,0 +1,441 @@
+{
+ "artifacts": {
+ "sdist": {
+ "canonical_builds_byte_equal": true,
+ "filename": "password_policy_state_space-0.1.0.tar.gz",
+ "inventory": [
+ {
+ "compressed_size": null,
+ "mode": "0755",
+ "mtime": 1704067200,
+ "name": "password_policy_state_space-0.1.0",
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "size": 0
+ },
+ {
+ "compressed_size": null,
+ "mode": "0644",
+ "mtime": 1704067200,
+ "name": "password_policy_state_space-0.1.0/MANIFEST.in",
+ "sha256": "90a343738ccd474b81735cc3fd3e3a1cec947e47bc2c8cb5c8a9ff30c9eca679",
+ "size": 126
+ },
+ {
+ "compressed_size": null,
+ "mode": "0644",
+ "mtime": 1704067200,
+ "name": "password_policy_state_space-0.1.0/PACKAGE.md",
+ "sha256": "0c58f616bdd464d40315197b7f5679cbd63f709a31925c3b887f12c5e1ae4dad",
+ "size": 1088
+ },
+ {
+ "compressed_size": null,
+ "mode": "0644",
+ "mtime": 1704067200,
+ "name": "password_policy_state_space-0.1.0/PKG-INFO",
+ "sha256": "f04478b56bacfad7878c29668019c33bbc3bbff2f39a6cc753f5aaa07148bab2",
+ "size": 2023
+ },
+ {
+ "compressed_size": null,
+ "mode": "0644",
+ "mtime": 1704067200,
+ "name": "password_policy_state_space-0.1.0/pyproject.toml",
+ "sha256": "1ca9cca3822404be961fae73a1cbecf5e9a7b2c112878d808b79b7c97466e7c9",
+ "size": 1554
+ },
+ {
+ "compressed_size": null,
+ "mode": "0644",
+ "mtime": 1704067200,
+ "name": "password_policy_state_space-0.1.0/setup.cfg",
+ "sha256": "1c473cbaee8da5fc46e7f0158794af5cea4414c34a3cf3f180c2001f5e38bd3e",
+ "size": 38
+ },
+ {
+ "compressed_size": null,
+ "mode": "0755",
+ "mtime": 1704067200,
+ "name": "password_policy_state_space-0.1.0/src",
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "size": 0
+ },
+ {
+ "compressed_size": null,
+ "mode": "0755",
+ "mtime": 1704067200,
+ "name": "password_policy_state_space-0.1.0/src/password_policy_lab",
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "size": 0
+ },
+ {
+ "compressed_size": null,
+ "mode": "0644",
+ "mtime": 1704067200,
+ "name": "password_policy_state_space-0.1.0/src/password_policy_lab/__init__.py",
+ "sha256": "23cb122fab42f49e6843c7ae6ea7bbcba7d9c0971e8f93e451fb9b6d0dc73efa",
+ "size": 1079
+ },
+ {
+ "compressed_size": null,
+ "mode": "0644",
+ "mtime": 1704067200,
+ "name": "password_policy_state_space-0.1.0/src/password_policy_lab/__main__.py",
+ "sha256": "529f004dc17fc33f0df9a0fd37338afa2269b073975c4e2d0d8df03b7c7e1067",
+ "size": 257
+ },
+ {
+ "compressed_size": null,
+ "mode": "0644",
+ "mtime": 1704067200,
+ "name": "password_policy_state_space-0.1.0/src/password_policy_lab/cli.py",
+ "sha256": "2c417d5a5f941b8e1898e3d14626dcd9fc2277712367841f709a194ebd7e04fd",
+ "size": 16641
+ },
+ {
+ "compressed_size": null,
+ "mode": "0644",
+ "mtime": 1704067200,
+ "name": "password_policy_state_space-0.1.0/src/password_policy_lab/errors.py",
+ "sha256": "21613d041edaea44c38503eb5b543ecf1d406c8c92bab6fbcb40f5a06a6e1dcc",
+ "size": 645
+ },
+ {
+ "compressed_size": null,
+ "mode": "0644",
+ "mtime": 1704067200,
+ "name": "password_policy_state_space-0.1.0/src/password_policy_lab/inspection.py",
+ "sha256": "8af1f3703184acaf0524f2f57a8494128fb56ba8a6c48835f240565a5ce785b0",
+ "size": 5388
+ },
+ {
+ "compressed_size": null,
+ "mode": "0644",
+ "mtime": 1704067200,
+ "name": "password_policy_state_space-0.1.0/src/password_policy_lab/policy.py",
+ "sha256": "636112fe3d636c2e87529774ade6c30f9c488ff51f705a6d8663711749ef98be",
+ "size": 4160
+ },
+ {
+ "compressed_size": null,
+ "mode": "0644",
+ "mtime": 1704067200,
+ "name": "password_policy_state_space-0.1.0/src/password_policy_lab/profiles.py",
+ "sha256": "1a75d0972e8c93ee2c6a353ed8c328425aa3e6ff33b4bce17097f91809380f6f",
+ "size": 1190
+ },
+ {
+ "compressed_size": null,
+ "mode": "0644",
+ "mtime": 1704067200,
+ "name": "password_policy_state_space-0.1.0/src/password_policy_lab/py.typed",
+ "sha256": "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b",
+ "size": 1
+ },
+ {
+ "compressed_size": null,
+ "mode": "0644",
+ "mtime": 1704067200,
+ "name": "password_policy_state_space-0.1.0/src/password_policy_lab/space.py",
+ "sha256": "76832b2870aced4d9d2fe8aac67bf21226ab12a9d396d7bc7ec04628ee1c8783",
+ "size": 7189
+ },
+ {
+ "compressed_size": null,
+ "mode": "0755",
+ "mtime": 1704067200,
+ "name": "password_policy_state_space-0.1.0/src/password_policy_lab/static",
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "size": 0
+ },
+ {
+ "compressed_size": null,
+ "mode": "0644",
+ "mtime": 1704067200,
+ "name": "password_policy_state_space-0.1.0/src/password_policy_lab/static/styles.css",
+ "sha256": "e97db9cbbbbfd1483b0a5db254667ec56675f2e28e945d0a067f0ac3a6e0dc08",
+ "size": 12963
+ },
+ {
+ "compressed_size": null,
+ "mode": "0755",
+ "mtime": 1704067200,
+ "name": "password_policy_state_space-0.1.0/src/password_policy_lab/templates",
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "size": 0
+ },
+ {
+ "compressed_size": null,
+ "mode": "0644",
+ "mtime": 1704067200,
+ "name": "password_policy_state_space-0.1.0/src/password_policy_lab/templates/index.html",
+ "sha256": "7dae09a227330eb774810a9d861ea925110e95ac1d683fe38170a17052d594c5",
+ "size": 9039
+ },
+ {
+ "compressed_size": null,
+ "mode": "0644",
+ "mtime": 1704067200,
+ "name": "password_policy_state_space-0.1.0/src/password_policy_lab/web.py",
+ "sha256": "36d08dee59f7f7d361f7d80b0a3ef7c172bccf8032f827b95d8ef89a2289fba0",
+ "size": 6485
+ },
+ {
+ "compressed_size": null,
+ "mode": "0755",
+ "mtime": 1704067200,
+ "name": "password_policy_state_space-0.1.0/src/password_policy_state_space.egg-info",
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "size": 0
+ },
+ {
+ "compressed_size": null,
+ "mode": "0644",
+ "mtime": 1704067200,
+ "name": "password_policy_state_space-0.1.0/src/password_policy_state_space.egg-info/PKG-INFO",
+ "sha256": "f04478b56bacfad7878c29668019c33bbc3bbff2f39a6cc753f5aaa07148bab2",
+ "size": 2023
+ },
+ {
+ "compressed_size": null,
+ "mode": "0644",
+ "mtime": 1704067200,
+ "name": "password_policy_state_space-0.1.0/src/password_policy_state_space.egg-info/SOURCES.txt",
+ "sha256": "1f0d1244441fcb70010b44d4cddcd3636b8ebedff618d6e3b1fe7c102c67b27e",
+ "size": 798
+ },
+ {
+ "compressed_size": null,
+ "mode": "0644",
+ "mtime": 1704067200,
+ "name": "password_policy_state_space-0.1.0/src/password_policy_state_space.egg-info/dependency_links.txt",
+ "sha256": "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b",
+ "size": 1
+ },
+ {
+ "compressed_size": null,
+ "mode": "0644",
+ "mtime": 1704067200,
+ "name": "password_policy_state_space-0.1.0/src/password_policy_state_space.egg-info/entry_points.txt",
+ "sha256": "3eb99b6f09e5d0707a4d5852651147b116e34c325bf327559505700d762bee0d",
+ "size": 69
+ },
+ {
+ "compressed_size": null,
+ "mode": "0644",
+ "mtime": 1704067200,
+ "name": "password_policy_state_space-0.1.0/src/password_policy_state_space.egg-info/requires.txt",
+ "sha256": "ef709edf5a884e07c9caa7a4ca33e883b5d800b6d4b4700a3be6eae726a0b4ae",
+ "size": 191
+ },
+ {
+ "compressed_size": null,
+ "mode": "0644",
+ "mtime": 1704067200,
+ "name": "password_policy_state_space-0.1.0/src/password_policy_state_space.egg-info/top_level.txt",
+ "sha256": "8feadb3d9d3c69d031c67aaf43f6933b0a8a78461c47d8ab7d186c707644d86a",
+ "size": 20
+ }
+ ],
+ "member_count": 29,
+ "raw_build_count": 2,
+ "raw_builds_byte_equality_claimed": false,
+ "sha256": "ac510b1ed90ec2ac12d0cbe9b7d8da4b3369e00b25a43dc2a7a65a4b6462ba39",
+ "size": 19463
+ },
+ "wheel": {
+ "canonical_builds_byte_equal": true,
+ "filename": "password_policy_state_space-0.1.0-py3-none-any.whl",
+ "inventory": [
+ {
+ "compressed_size": 369,
+ "mode": "0644",
+ "mtime": 1704067200,
+ "name": "password_policy_lab/__init__.py",
+ "sha256": "23cb122fab42f49e6843c7ae6ea7bbcba7d9c0971e8f93e451fb9b6d0dc73efa",
+ "size": 1079
+ },
+ {
+ "compressed_size": 171,
+ "mode": "0644",
+ "mtime": 1704067200,
+ "name": "password_policy_lab/__main__.py",
+ "sha256": "529f004dc17fc33f0df9a0fd37338afa2269b073975c4e2d0d8df03b7c7e1067",
+ "size": 257
+ },
+ {
+ "compressed_size": 4247,
+ "mode": "0644",
+ "mtime": 1704067200,
+ "name": "password_policy_lab/cli.py",
+ "sha256": "2c417d5a5f941b8e1898e3d14626dcd9fc2277712367841f709a194ebd7e04fd",
+ "size": 16641
+ },
+ {
+ "compressed_size": 274,
+ "mode": "0644",
+ "mtime": 1704067200,
+ "name": "password_policy_lab/errors.py",
+ "sha256": "21613d041edaea44c38503eb5b543ecf1d406c8c92bab6fbcb40f5a06a6e1dcc",
+ "size": 645
+ },
+ {
+ "compressed_size": 1567,
+ "mode": "0644",
+ "mtime": 1704067200,
+ "name": "password_policy_lab/inspection.py",
+ "sha256": "8af1f3703184acaf0524f2f57a8494128fb56ba8a6c48835f240565a5ce785b0",
+ "size": 5388
+ },
+ {
+ "compressed_size": 1134,
+ "mode": "0644",
+ "mtime": 1704067200,
+ "name": "password_policy_lab/policy.py",
+ "sha256": "636112fe3d636c2e87529774ade6c30f9c488ff51f705a6d8663711749ef98be",
+ "size": 4160
+ },
+ {
+ "compressed_size": 528,
+ "mode": "0644",
+ "mtime": 1704067200,
+ "name": "password_policy_lab/profiles.py",
+ "sha256": "1a75d0972e8c93ee2c6a353ed8c328425aa3e6ff33b4bce17097f91809380f6f",
+ "size": 1190
+ },
+ {
+ "compressed_size": 3,
+ "mode": "0644",
+ "mtime": 1704067200,
+ "name": "password_policy_lab/py.typed",
+ "sha256": "01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b",
+ "size": 1
+ },
+ {
+ "compressed_size": 2101,
+ "mode": "0644",
+ "mtime": 1704067200,
+ "name": "password_policy_lab/space.py",
+ "sha256": "76832b2870aced4d9d2fe8aac67bf21226ab12a9d396d7bc7ec04628ee1c8783",
+ "size": 7189
+ },
+ {
+ "compressed_size": 2099,
+ "mode": "0644",
+ "mtime": 1704067200,
+ "name": "password_policy_lab/web.py",
+ "sha256": "36d08dee59f7f7d361f7d80b0a3ef7c172bccf8032f827b95d8ef89a2289fba0",
+ "size": 6485
+ },
+ {
+ "compressed_size": 3177,
+ "mode": "0644",
+ "mtime": 1704067200,
+ "name": "password_policy_lab/static/styles.css",
+ "sha256": "e97db9cbbbbfd1483b0a5db254667ec56675f2e28e945d0a067f0ac3a6e0dc08",
+ "size": 12963
+ },
+ {
+ "compressed_size": 2638,
+ "mode": "0644",
+ "mtime": 1704067200,
+ "name": "password_policy_lab/templates/index.html",
+ "sha256": "7dae09a227330eb774810a9d861ea925110e95ac1d683fe38170a17052d594c5",
+ "size": 9039
+ },
+ {
+ "compressed_size": 958,
+ "mode": "0644",
+ "mtime": 1704067200,
+ "name": "password_policy_state_space-0.1.0.dist-info/METADATA",
+ "sha256": "f04478b56bacfad7878c29668019c33bbc3bbff2f39a6cc753f5aaa07148bab2",
+ "size": 2023
+ },
+ {
+ "compressed_size": 91,
+ "mode": "0644",
+ "mtime": 1704067200,
+ "name": "password_policy_state_space-0.1.0.dist-info/WHEEL",
+ "sha256": "2b6eb4118ce7cd7b09601406aa623c553c4476265836f0d9c16f5c061f7efcc0",
+ "size": 91
+ },
+ {
+ "compressed_size": 59,
+ "mode": "0644",
+ "mtime": 1704067200,
+ "name": "password_policy_state_space-0.1.0.dist-info/entry_points.txt",
+ "sha256": "3eb99b6f09e5d0707a4d5852651147b116e34c325bf327559505700d762bee0d",
+ "size": 69
+ },
+ {
+ "compressed_size": 22,
+ "mode": "0644",
+ "mtime": 1704067200,
+ "name": "password_policy_state_space-0.1.0.dist-info/top_level.txt",
+ "sha256": "8feadb3d9d3c69d031c67aaf43f6933b0a8a78461c47d8ab7d186c707644d86a",
+ "size": 20
+ },
+ {
+ "compressed_size": 836,
+ "mode": "0644",
+ "mtime": 1704067200,
+ "name": "password_policy_state_space-0.1.0.dist-info/RECORD",
+ "sha256": "2685b61285c89ae42afd2c7a17a6a11ea14ccbd87a9a5872851b9d57e7ab58ff",
+ "size": 1543
+ }
+ ],
+ "member_count": 17,
+ "raw_build_count": 2,
+ "raw_builds_byte_equal": true,
+ "sdist_rebuild_byte_equal": true,
+ "sha256": "2cd15168c3c93b9969b32e28be07802709b071548bd2666f47ac30efe418b484",
+ "size": 22862
+ }
+ },
+ "build": {
+ "build_count": 2,
+ "build_isolation": false,
+ "fixed_source_date_epoch": 1704067200,
+ "locale": "C.UTF-8",
+ "network_package_index_enabled": false,
+ "timezone": "UTC",
+ "umask": "0022"
+ },
+ "claim_boundaries": {
+ "arbitrary_archive_safety": false,
+ "artifact_signature_verified": false,
+ "cross_platform_reproducibility": false,
+ "dependency_integrity_verified": false,
+ "fresh_dependency_environment": false,
+ "license_declared": false
+ },
+ "official": false,
+ "schema_version": 1,
+ "smoke": {
+ "command": "inspect --length 20 --format json",
+ "current_checker_dependencies": {
+ "Flask": "3.1.3",
+ "waitress": "3.0.2"
+ },
+ "dependency_install_mode": "current-pinned-checker-environment",
+ "deterministic": true,
+ "inspect_sha256": "68965b4cf54d1ee9d2b8a52f77193d767d963693fa37a06ee6789de9f7dcc078",
+ "metadata_origin_in_target": true,
+ "package_origin_in_target": true,
+ "pip_compile_bytecode": false,
+ "pip_dependency_resolution": false,
+ "pip_index_enabled": false,
+ "resources_present": true,
+ "sampled_password": false
+ },
+ "source": {
+ "distribution_input_count": 15,
+ "distribution_input_sha256": "82281d790d4736759f776f0a43e0b6207731fafee4be89c9df14cc61596860cd",
+ "git_index_stage": 0
+ },
+ "toolchain": {
+ "build": "1.5.0",
+ "python": "3.12.3",
+ "setuptools": "83.0.0"
+ }
+}
diff --git a/docs/evidence/distribution-check.txt b/docs/evidence/distribution-check.txt
new file mode 100644
index 0000000..023d6b0
--- /dev/null
+++ b/docs/evidence/distribution-check.txt
@@ -0,0 +1,7 @@
+$ python scripts/attest_distribution.py
+distribution attestation: PASS (unofficial)
+source: 15 indexed inputs; sha256=82281d790d4736759f776f0a43e0b6207731fafee4be89c9df14cc61596860cd
+wheel: sha256=2cd15168c3c93b9969b32e28be07802709b071548bd2666f47ac30efe418b484; two builds and sdist rebuild match
+sdist: sha256=ac510b1ed90ec2ac12d0cbe9b7d8da4b3369e00b25a43dc2a7a65a4b6462ba39; two canonical builds match (raw equality unclaimed)
+smoke: deterministic inspect passed; no password sampled
+boundaries: no license, signature, dependency-integrity, cross-platform, or arbitrary-archive claim
diff --git a/docs/evidence/manifest.json b/docs/evidence/manifest.json
index 98c45ba..bedea62 100644
--- a/docs/evidence/manifest.json
+++ b/docs/evidence/manifest.json
@@ -24,16 +24,40 @@
"sha256": "9d0aadb8cc7338e9adaf9b6796167d9c0862ab2631a8fd274cecd4e2cd727aad",
"width": 1600
},
+ {
+ "assertions": [
+ "rendered from the real distribution attestation transcript",
+ "canonical wheel, sdist rebuild, and installed smoke passed"
+ ],
+ "bytes": 64519,
+ "height": 466,
+ "media_type": "image/png",
+ "path": "docs/assets/distribution-check.png",
+ "sha256": "df09560f9baa4b2da030597859d174d5f297a4b79afe1032392434c72e9a0ebb",
+ "width": 1600
+ },
+ {
+ "assertions": [
+ "rendered from measured distribution hashes and member counts",
+ "claim boundaries remain explicit"
+ ],
+ "bytes": 6991,
+ "height": 830,
+ "media_type": "image/svg+xml",
+ "path": "docs/assets/distribution-contract.svg",
+ "sha256": "37eeac7f88fd0d3dd844c1984a8d69cdaf707f7ae5f3229889cb6dafe0d6656d",
+ "width": 1960
+ },
{
"assertions": [
"rendered from normalized real gate transcript",
"all commands exited zero"
],
- "bytes": 91467,
- "height": 976,
+ "bytes": 146672,
+ "height": 1276,
"media_type": "image/png",
"path": "docs/assets/quality-gate.png",
- "sha256": "66e179b7c8b06cf579ec5e29a168ec1c1422c9cc8724523666506003d12f68a5",
+ "sha256": "9c9e04e71175d5d90aaa8ffd242a02611b9f44a20dea3884f0756656a9080985",
"width": 1600
},
{
@@ -41,11 +65,11 @@
"commands verified against package metadata",
"repository-local evidence workflow"
],
- "bytes": 6122,
+ "bytes": 6185,
"height": 650,
"media_type": "image/svg+xml",
"path": "docs/assets/setup-workflow.svg",
- "sha256": "88693e52fb683a5263c70342245d779f9be31f8ab7aa504878c35bb91c6cbb82",
+ "sha256": "ac10bb0b52f6351e3165bd8a54ff3922e311fe3cda853d86f57d03643ae90301",
"width": 1600
},
{
@@ -89,11 +113,11 @@
"real Waitress GET response",
"exact policy details expanded"
],
- "bytes": 470160,
- "height": 2419,
+ "bytes": 474461,
+ "height": 2417,
"media_type": "image/png",
"path": "docs/assets/web-home.png",
- "sha256": "fffa52b478ba62665c346bda982a8576ee67364e3b882e1e29ebe4a05a445019",
+ "sha256": "3043a36c8fe548a2ff9da4bb3a814988b263f021e8f97c00485ff7aba939843d",
"width": 1440
},
{
@@ -133,15 +157,35 @@
"path": "docs/evidence/cli-inspect.txt",
"sha256": "4cdabfef6d4fd10c9a41867fbfaea943be095b0c77652f9ff1aa68d03554bb15"
},
+ {
+ "assertions": [
+ "canonical path-free report from two real builds",
+ "exact archive inventories and honest claim boundaries"
+ ],
+ "bytes": 16006,
+ "media_type": "application/json",
+ "path": "docs/evidence/distribution-attestation.json",
+ "sha256": "611ed7883b93a62d8c977342271519be13614263aa343b495f340e126b2e5b18"
+ },
+ {
+ "assertions": [
+ "real normalized attestation output",
+ "no password sampled"
+ ],
+ "bytes": 587,
+ "media_type": "text/plain",
+ "path": "docs/evidence/distribution-check.txt",
+ "sha256": "94b61d45db43b334efe8f574c94d69931bdb35aa709bec1205e112913285a92e"
+ },
{
"assertions": [
"real normalized command output",
"timing and absolute path absent"
],
- "bytes": 1166,
+ "bytes": 1874,
"media_type": "text/plain",
"path": "docs/evidence/quality-gate.txt",
- "sha256": "18eaf060d51fbaeb8cedec36c18bf2196290d36b66bef1ad2926603cd255e5d7"
+ "sha256": "aec144f46e7385f6dc217ad9b6ac4ac877bb688cf0fcce7c557b74a3847b98b3"
},
{
"assertions": [
@@ -168,6 +212,9 @@
}
],
"capture": {
+ "chromium_launch_args": [
+ "--num-raster-threads=1"
+ ],
"requests": [
{
"artifact": "docs/assets/web-home.png",
@@ -265,6 +312,7 @@
"python -m ruff format --check app.py scripts src tests",
"MYPYPATH=src python -m mypy --strict app.py scripts src tests",
"PYTHONPATH=src python -m pytest --cov=password_policy_lab --cov-branch --cov-report=term-missing -q",
+ "python scripts/attest_distribution.py",
"python -m pip check"
]
},
@@ -298,13 +346,21 @@
},
"schema_version": 1,
"source_files": [
+ {
+ "path": "MANIFEST.in",
+ "sha256": "90a343738ccd474b81735cc3fd3e3a1cec947e47bc2c8cb5c8a9ff30c9eca679"
+ },
{
"path": "Makefile",
- "sha256": "dcafae5783472f29ecea4f3029db4f647ef7793a2b4f58a3d0eb9766f937ddd6"
+ "sha256": "70f4fe061b50a4548ddd13f47247c2110fff7eccec33252d47dfde69468be2ea"
+ },
+ {
+ "path": "PACKAGE.md",
+ "sha256": "0c58f616bdd464d40315197b7f5679cbd63f709a31925c3b887f12c5e1ae4dad"
},
{
"path": "README.md",
- "sha256": "29de5e6ee9f4f4f869202381d1c62f84dbe706d50503ea05ac65dfe962b13bdc"
+ "sha256": "405ede3a7f253df40b64180f804e4a228872132aaaf6ad5c282fcb65f4a2c42b"
},
{
"path": "app.py",
@@ -312,19 +368,27 @@
},
{
"path": "pyproject.toml",
- "sha256": "f528181eae25e424dd82fc5dbf7c9065c886ef1ab979c97ecf716d9c8e9ca052"
+ "sha256": "1ca9cca3822404be961fae73a1cbecf5e9a7b2c112878d808b79b7c97466e7c9"
+ },
+ {
+ "path": "scripts/attest_distribution.py",
+ "sha256": "ffac30dec76abdded0320d6106942ab0096a30caa05ae05a201f23d814962e5a"
},
{
"path": "scripts/check_evidence.py",
- "sha256": "96770a1376ee5e2494abbb0b86ecc028392fd771548f12da302c7385474a648f"
+ "sha256": "eb72f02fd3fc50536aee670f0b83badffd1d9b13df5fa16d87c38a78be8cf881"
+ },
+ {
+ "path": "scripts/distribution_contract.py",
+ "sha256": "68c68447528202bc83303ce593b6d2a1c46090aadccfd16a5b1f8e469d3b6a97"
},
{
"path": "scripts/evidence_rendering.py",
- "sha256": "2ee59d600f2880682d9a7763172a16e4f48bd3327cebd2c615c89b4b17644a43"
+ "sha256": "b313270e49c26a9eb8b966805df21b5c46ed30823794cb5272014efb9f100a92"
},
{
"path": "scripts/generate_evidence.py",
- "sha256": "4574b4a3dc00ce142e677134368962340c3a981f9549736d3892a3d74609915c"
+ "sha256": "96e00dd2f4e48c6ffb20d7543ef02b687d3f94df2d259bc064d2b6f48ef3c534"
},
{
"path": "src/password_policy_lab/__init__.py",
@@ -386,9 +450,17 @@
"path": "tests/test_counting.py",
"sha256": "5f72e9acf6c22d35317549fd13eaf87a6a864d5ad88c1441bfa50850eefa6718"
},
+ {
+ "path": "tests/test_distribution_attestation.py",
+ "sha256": "222d913d2ccc1afb65992acc29c8b0586699d0234fa483f1cd877ef4911e5c77"
+ },
+ {
+ "path": "tests/test_distribution_contract.py",
+ "sha256": "d6f8761a03c6f48962aaef97e4a9955ffc8967488deb9ec11f76bc5263773854"
+ },
{
"path": "tests/test_evidence.py",
- "sha256": "db99a5c7678e0d2ec2e2bf8c9c3cab539987e1fdc36161b77f44440a35104049"
+ "sha256": "8bc201dce3424e6456090e803fa0da4e83f625cb0ce2fc186f33ed255f12121c"
},
{
"path": "tests/test_inspection.py",
diff --git a/docs/evidence/quality-gate.txt b/docs/evidence/quality-gate.txt
index a299603..99f8a56 100644
--- a/docs/evidence/quality-gate.txt
+++ b/docs/evidence/quality-gate.txt
@@ -2,14 +2,15 @@ $ python -m ruff check app.py scripts src tests
All checks passed!
$ python -m ruff format --check app.py scripts src tests
-22 files already formatted
+26 files already formatted
$ MYPYPATH=src python -m mypy --strict app.py scripts src tests
-Success: no issues found in 22 source files
+Success: no issues found in 26 source files
$ PYTHONPATH=src python -m pytest --cov=password_policy_lab --cov-branch --cov-report=term-missing -q
-................................................................................................................ [ 69%]
-................................................. [100%]
+................................................................................................................ [ 42%]
+................................................................................................................ [ 84%]
+........................................ [100%]
==================================================== tests coverage ====================================================
___________________________________ coverage: platform linux, python 3.12.3-final-0 ____________________________________
@@ -19,7 +20,15 @@ TOTAL 526 0 130 0 100%
9 files skipped due to complete coverage.
Required test coverage of 100.0% reached. Total coverage: 100.00%
-161 passed
+264 passed
+
+$ python scripts/attest_distribution.py
+distribution attestation: PASS (unofficial)
+source: 15 indexed inputs; sha256=82281d790d4736759f776f0a43e0b6207731fafee4be89c9df14cc61596860cd
+wheel: sha256=2cd15168c3c93b9969b32e28be07802709b071548bd2666f47ac30efe418b484; two builds and sdist rebuild match
+sdist: sha256=ac510b1ed90ec2ac12d0cbe9b7d8da4b3369e00b25a43dc2a7a65a4b6462ba39; two canonical builds match (raw equality unclaimed)
+smoke: deterministic inspect passed; no password sampled
+boundaries: no license, signature, dependency-integrity, cross-platform, or arbitrary-archive claim
$ python -m pip check
No broken requirements found.
diff --git a/pyproject.toml b/pyproject.toml
index 82abc01..ec9263c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,5 +1,5 @@
[build-system]
-requires = ["setuptools>=83.0.0"]
+requires = ["setuptools==83.0.0"]
build-backend = "setuptools.build_meta"
[project]
@@ -7,7 +7,7 @@ name = "password-policy-state-space"
version = "0.1.0"
description = "Exact counting and uniform sampling for constrained password policies"
authors = [{ name = "Omar Ibrahim" }]
-readme = "README.md"
+readme = { file = "PACKAGE.md", content-type = "text/markdown" }
requires-python = ">=3.11"
dependencies = ["Flask==3.1.3", "waitress==3.0.2"]
@@ -29,6 +29,7 @@ dev = [
"pytest==9.1.1",
"pytest-cov==7.1.0",
"ruff==0.16.0",
+ "setuptools==83.0.0",
]
[tool.setuptools.packages.find]
diff --git a/scripts/attest_distribution.py b/scripts/attest_distribution.py
new file mode 100644
index 0000000..1a38b9c
--- /dev/null
+++ b/scripts/attest_distribution.py
@@ -0,0 +1,1471 @@
+#!/usr/bin/env python3
+"""Produce a bounded, source-bound distribution attestation.
+
+The attestation is intentionally project-specific. It builds only the exact
+release inputs stored in Git's stage-zero index, normalizes archive container
+metadata, proves the two normalized builds equal, rebuilds the wheel from the
+canonical source distribution, and performs an offline target install. It is
+not a package signature or a general-purpose archive scanner.
+"""
+
+from __future__ import annotations
+
+import argparse
+import hashlib
+import importlib.metadata
+import json
+import os
+import re
+import selectors
+import signal
+import stat
+import subprocess
+import sys
+import tempfile
+import time
+import tomllib
+from collections.abc import Mapping, Sequence
+from contextlib import suppress
+from dataclasses import dataclass
+from pathlib import Path, PurePosixPath
+from typing import Literal, Never, NoReturn, TextIO, cast
+
+from distribution_contract import (
+ DIST_INFO,
+ FIXED_MTIME,
+ SDIST_ROOT,
+ ArtifactRecord,
+ DistributionContractError,
+ MemberRecord,
+ ProjectPayloads,
+ build_expected_metadata,
+ canonicalize_sdist,
+ canonicalize_wheel,
+ inspect_sdist,
+ inspect_wheel,
+ materialize_canonical_sdist,
+)
+
+SCHEMA_VERSION = 1
+PROJECT_NAME = "password-policy-state-space"
+PROJECT_VERSION = "0.1.0"
+NORMALIZED_NAME = "password_policy_state_space"
+WHEEL_FILENAME = f"{NORMALIZED_NAME}-{PROJECT_VERSION}-py3-none-any.whl"
+SDIST_FILENAME = f"{NORMALIZED_NAME}-{PROJECT_VERSION}.tar.gz"
+DEFAULT_WORK_ROOT = ".evidence-work/distribution"
+
+BUILD_VERSION = "1.5.0"
+SETUPTOOLS_VERSION = "83.0.0"
+FLASK_VERSION = "3.1.3"
+WAITRESS_VERSION = "3.0.2"
+
+COMMAND_TIMEOUT_SECONDS = 240.0
+MAX_COMMAND_OUTPUT = 2 * 1024 * 1024
+MAX_SOURCE_FILE = 2 * 1024 * 1024
+MAX_PACKAGE_WORKTREE_ENTRIES = 256
+_READ_CHUNK = 64 * 1024
+_OBJECT_ID = re.compile(r"[0-9a-f]{40}(?:[0-9a-f]{24})?\Z")
+
+PACKAGE_INPUTS = (
+ "src/password_policy_lab/__init__.py",
+ "src/password_policy_lab/__main__.py",
+ "src/password_policy_lab/cli.py",
+ "src/password_policy_lab/errors.py",
+ "src/password_policy_lab/inspection.py",
+ "src/password_policy_lab/policy.py",
+ "src/password_policy_lab/profiles.py",
+ "src/password_policy_lab/py.typed",
+ "src/password_policy_lab/space.py",
+ "src/password_policy_lab/static/styles.css",
+ "src/password_policy_lab/templates/index.html",
+ "src/password_policy_lab/web.py",
+)
+DISTRIBUTION_INPUTS = tuple(
+ sorted(("MANIFEST.in", "PACKAGE.md", "pyproject.toml", *PACKAGE_INPUTS))
+)
+
+# Setuptools emits Python modules before package-data files in wheels.
+WHEEL_MEMBERS = (
+ "password_policy_lab/__init__.py",
+ "password_policy_lab/__main__.py",
+ "password_policy_lab/cli.py",
+ "password_policy_lab/errors.py",
+ "password_policy_lab/inspection.py",
+ "password_policy_lab/policy.py",
+ "password_policy_lab/profiles.py",
+ "password_policy_lab/py.typed",
+ "password_policy_lab/space.py",
+ "password_policy_lab/web.py",
+ "password_policy_lab/static/styles.css",
+ "password_policy_lab/templates/index.html",
+ f"{DIST_INFO}/METADATA",
+ f"{DIST_INFO}/WHEEL",
+ f"{DIST_INFO}/entry_points.txt",
+ f"{DIST_INFO}/top_level.txt",
+ f"{DIST_INFO}/RECORD",
+)
+
+_EGG_INFO = "src/password_policy_state_space.egg-info"
+SDIST_FILES = tuple(
+ sorted(
+ {
+ "MANIFEST.in",
+ "PACKAGE.md",
+ "PKG-INFO",
+ "pyproject.toml",
+ "setup.cfg",
+ *PACKAGE_INPUTS,
+ f"{_EGG_INFO}/PKG-INFO",
+ f"{_EGG_INFO}/SOURCES.txt",
+ f"{_EGG_INFO}/dependency_links.txt",
+ f"{_EGG_INFO}/entry_points.txt",
+ f"{_EGG_INFO}/requires.txt",
+ f"{_EGG_INFO}/top_level.txt",
+ }
+ )
+)
+
+_DEV_DEPENDENCIES = (
+ "build==1.5.0",
+ "matplotlib==3.11.1",
+ "mypy==2.3.0",
+ "numpy==2.3.5",
+ "Pillow==12.3.0",
+ "playwright==1.61.0",
+ "pytest==9.1.1",
+ "pytest-cov==7.1.0",
+ "ruff==0.16.0",
+ "setuptools==83.0.0",
+)
+_SETUP_CFG = b"[egg_info]\ntag_build = \ntag_date = 0\n\n"
+_DEPENDENCY_LINKS = b"\n"
+_REQUIRES = (
+ b"Flask==3.1.3\n"
+ b"waitress==3.0.2\n"
+ b"\n"
+ b"[dev]\n" + "\n".join(_DEV_DEPENDENCIES).encode("ascii") + b"\n"
+)
+_SOURCES = "\n".join(
+ (
+ "MANIFEST.in",
+ "PACKAGE.md",
+ "pyproject.toml",
+ "src/password_policy_lab/__init__.py",
+ "src/password_policy_lab/__main__.py",
+ "src/password_policy_lab/cli.py",
+ "src/password_policy_lab/errors.py",
+ "src/password_policy_lab/inspection.py",
+ "src/password_policy_lab/policy.py",
+ "src/password_policy_lab/profiles.py",
+ "src/password_policy_lab/py.typed",
+ "src/password_policy_lab/space.py",
+ "src/password_policy_lab/web.py",
+ "src/password_policy_lab/static/styles.css",
+ "src/password_policy_lab/templates/index.html",
+ f"{_EGG_INFO}/PKG-INFO",
+ f"{_EGG_INFO}/SOURCES.txt",
+ f"{_EGG_INFO}/dependency_links.txt",
+ f"{_EGG_INFO}/entry_points.txt",
+ f"{_EGG_INFO}/requires.txt",
+ f"{_EGG_INFO}/top_level.txt",
+ )
+).encode("ascii")
+
+AttestationCode = Literal[
+ "arguments-invalid",
+ "archive-contract",
+ "artifact-mismatch",
+ "build-failed",
+ "build-output-invalid",
+ "index-invalid",
+ "internal-io",
+ "repository-invalid",
+ "smoke-failed",
+ "source-dirty",
+ "source-payload-mismatch",
+ "subprocess-output",
+ "subprocess-timeout",
+ "toolchain-invalid",
+ "work-root-invalid",
+]
+
+
+class AttestationError(ValueError):
+ """A value-free, stable attestation failure."""
+
+ __slots__ = ("code",)
+
+ def __init__(self, code: AttestationCode) -> None:
+ self.code = code
+ super().__init__(f"distribution attestation rejected: {code}")
+
+
+@dataclass(frozen=True, slots=True)
+class IndexEntry:
+ """One regular stage-zero Git index entry."""
+
+ path: str
+ object_id: str
+
+
+@dataclass(frozen=True, slots=True)
+class SourceFile:
+ """Trusted content read from a stage-zero Git blob."""
+
+ path: str
+ data: bytes
+ sha256: str
+
+
+@dataclass(frozen=True, slots=True)
+class SourceState:
+ """The exact source inputs and their Git provenance."""
+
+ files: tuple[SourceFile, ...]
+ input_sha256: str
+ tree: str
+
+ def payload(self, path: str) -> bytes:
+ for source_file in self.files:
+ if source_file.path == path:
+ return source_file.data
+ raise AttestationError("index-invalid")
+
+
+@dataclass(frozen=True, slots=True)
+class CommandResult:
+ """Bounded subprocess output."""
+
+ stdout: bytes
+ stderr: bytes
+
+
+@dataclass(frozen=True, slots=True)
+class BuiltArtifacts:
+ """The exact two artifacts accepted from one build invocation."""
+
+ wheel: Path
+ sdist: Path
+
+
+@dataclass(frozen=True, slots=True)
+class Toolchain:
+ """Versions explicitly required by the attestation."""
+
+ python: str
+ build: str
+ setuptools: str
+ flask: str
+ waitress: str
+
+
+class _SafeArgumentParser(argparse.ArgumentParser):
+ def error(self, message: str) -> Never:
+ del message
+ raise AttestationError("arguments-invalid")
+
+
+class _StoreOnce(argparse.Action):
+ def __call__(
+ self,
+ parser: argparse.ArgumentParser,
+ namespace: argparse.Namespace,
+ values: object,
+ option_string: str | None = None,
+ ) -> None:
+ marker = f"_seen_{self.dest}"
+ if getattr(namespace, marker, False):
+ parser.error(f"{option_string} may be specified only once")
+ setattr(namespace, marker, True)
+ setattr(namespace, self.dest, values)
+
+
+def _reject(code: AttestationCode) -> NoReturn:
+ raise AttestationError(code)
+
+
+def _sha256(data: bytes) -> str:
+ return hashlib.sha256(data).hexdigest()
+
+
+def _parse_index_entries(raw: bytes) -> tuple[IndexEntry, ...]:
+ """Parse ``git ls-files --stage -z`` without accepting quoted paths."""
+
+ if not raw:
+ return ()
+ records = raw.split(b"\0")
+ if records[-1] != b"":
+ _reject("index-invalid")
+ result: list[IndexEntry] = []
+ observed: set[str] = set()
+ for raw_record in records[:-1]:
+ try:
+ header, raw_path = raw_record.split(b"\t", 1)
+ mode, object_id, stage = header.decode("ascii").split(" ")
+ path = raw_path.decode("ascii")
+ except (UnicodeError, ValueError):
+ _reject("index-invalid")
+ if (
+ mode != "100644"
+ or stage != "0"
+ or _OBJECT_ID.fullmatch(object_id) is None
+ or path in observed
+ or path != PurePosixPath(path).as_posix()
+ or PurePosixPath(path).is_absolute()
+ or any(part in {"", ".", ".."} for part in PurePosixPath(path).parts)
+ ):
+ _reject("index-invalid")
+ observed.add(path)
+ result.append(IndexEntry(path, object_id))
+ return tuple(result)
+
+
+def _parse_tree_entries(raw: bytes) -> tuple[IndexEntry, ...]:
+ """Parse regular blobs from ``git ls-tree -r -z``."""
+
+ if not raw:
+ return ()
+ records = raw.split(b"\0")
+ if records[-1] != b"":
+ _reject("repository-invalid")
+ result: list[IndexEntry] = []
+ observed: set[str] = set()
+ for raw_record in records[:-1]:
+ try:
+ header, raw_path = raw_record.split(b"\t", 1)
+ mode, object_type, object_id = header.decode("ascii").split(" ")
+ path = raw_path.decode("ascii")
+ except (UnicodeError, ValueError):
+ _reject("repository-invalid")
+ if (
+ mode != "100644"
+ or object_type != "blob"
+ or _OBJECT_ID.fullmatch(object_id) is None
+ or path in observed
+ or path != PurePosixPath(path).as_posix()
+ or PurePosixPath(path).is_absolute()
+ or any(part in {"", ".", ".."} for part in PurePosixPath(path).parts)
+ ):
+ _reject("repository-invalid")
+ observed.add(path)
+ result.append(IndexEntry(path, object_id))
+ return tuple(result)
+
+
+def _canonical_input_digest(files: Sequence[tuple[str, bytes]]) -> str:
+ """Hash a length-framed, lexicographically ordered source payload."""
+
+ digest = hashlib.sha256()
+ previous: str | None = None
+ for path, data in sorted(files):
+ if previous == path or path not in DISTRIBUTION_INPUTS:
+ _reject("index-invalid")
+ encoded_path = path.encode("ascii")
+ digest.update(len(encoded_path).to_bytes(4, "big"))
+ digest.update(encoded_path)
+ digest.update(len(data).to_bytes(8, "big"))
+ digest.update(data)
+ previous = path
+ if previous is None or len(files) != len(DISTRIBUTION_INPUTS):
+ _reject("index-invalid")
+ return digest.hexdigest()
+
+
+def _process_environment(
+ temporary_root: Path,
+ *,
+ python_path: Path | None = None,
+) -> dict[str, str]:
+ """Return a fixed allowlisted environment, never an inherited copy."""
+
+ executable_directory = str(Path(sys.executable).resolve().parent)
+ environment = {
+ "HOME": str(temporary_root / "home"),
+ "LANG": "C.UTF-8",
+ "LC_ALL": "C.UTF-8",
+ "PATH": os.pathsep.join((executable_directory, "/usr/bin", "/bin")),
+ "PIP_CONFIG_FILE": os.devnull,
+ "PIP_DISABLE_PIP_VERSION_CHECK": "1",
+ "PIP_NO_INDEX": "1",
+ "PYTHONDONTWRITEBYTECODE": "1",
+ "PYTHONHASHSEED": "0",
+ "PYTHONNOUSERSITE": "1",
+ "SOURCE_DATE_EPOCH": str(FIXED_MTIME),
+ "TMPDIR": str(temporary_root / "tmp"),
+ "TZ": "UTC",
+ }
+ if python_path is not None:
+ environment["PYTHONPATH"] = str(python_path)
+ return environment
+
+
+def _prepare_environment_directories(environment: Mapping[str, str]) -> None:
+ for key in ("HOME", "TMPDIR"):
+ directory = Path(environment[key])
+ directory.mkdir(parents=True, mode=0o700, exist_ok=False)
+
+
+def _child_setup() -> None:
+ os.umask(0o022)
+ os.setsid()
+
+
+def _terminate(process: subprocess.Popen[bytes]) -> None:
+ with suppress(ProcessLookupError):
+ os.killpg(process.pid, signal.SIGKILL)
+ if process.poll() is None:
+ process.wait()
+
+
+def _run_command(
+ command: Sequence[str],
+ *,
+ cwd: Path,
+ environment: Mapping[str, str],
+ failure_code: AttestationCode,
+ timeout: float = COMMAND_TIMEOUT_SECONDS,
+ maximum_output: int = MAX_COMMAND_OUTPUT,
+) -> CommandResult:
+ """Run one process with bounded time, combined output, and a fixed umask."""
+
+ if not command or timeout <= 0 or maximum_output <= 0:
+ _reject(failure_code)
+ try:
+ process = subprocess.Popen(
+ tuple(command),
+ cwd=cwd,
+ env=dict(environment),
+ stdin=subprocess.DEVNULL,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ preexec_fn=_child_setup,
+ )
+ except OSError:
+ _reject(failure_code)
+
+ if process.stdout is None or process.stderr is None:
+ _terminate(process)
+ _reject(failure_code)
+ output = {"stdout": bytearray(), "stderr": bytearray()}
+ selector = selectors.DefaultSelector()
+ selector.register(process.stdout, selectors.EVENT_READ, "stdout")
+ selector.register(process.stderr, selectors.EVENT_READ, "stderr")
+ deadline = time.monotonic() + timeout
+ try:
+ while selector.get_map():
+ remaining = deadline - time.monotonic()
+ if remaining <= 0:
+ _terminate(process)
+ _reject("subprocess-timeout")
+ for key, _ in selector.select(min(remaining, 0.1)):
+ chunk = os.read(key.fd, _READ_CHUNK)
+ if not chunk:
+ selector.unregister(key.fileobj)
+ continue
+ channel = cast(Literal["stdout", "stderr"], key.data)
+ output[channel].extend(chunk)
+ if len(output["stdout"]) + len(output["stderr"]) > maximum_output:
+ _terminate(process)
+ _reject("subprocess-output")
+ return_code = process.wait(timeout=max(0.0, deadline - time.monotonic()))
+ except subprocess.TimeoutExpired:
+ _terminate(process)
+ _reject("subprocess-timeout")
+ finally:
+ selector.close()
+ process.stdout.close()
+ process.stderr.close()
+ if return_code != 0:
+ _reject(failure_code)
+ return CommandResult(bytes(output["stdout"]), bytes(output["stderr"]))
+
+
+def _git(
+ repository: Path,
+ arguments: Sequence[str],
+ *,
+ maximum_output: int = MAX_COMMAND_OUTPUT,
+) -> bytes:
+ environment = {
+ "LANG": "C.UTF-8",
+ "LC_ALL": "C.UTF-8",
+ "PATH": "/usr/bin:/bin",
+ "TZ": "UTC",
+ }
+ result = _run_command(
+ ("git", "-c", "core.quotepath=false", *arguments),
+ cwd=repository,
+ environment=environment,
+ failure_code="repository-invalid",
+ timeout=30.0,
+ maximum_output=maximum_output,
+ )
+ if result.stderr:
+ _reject("repository-invalid")
+ return result.stdout
+
+
+def _validate_project_configuration(raw: bytes) -> None:
+ try:
+ document = tomllib.loads(raw.decode("utf-8"))
+ except (UnicodeError, tomllib.TOMLDecodeError):
+ _reject("source-payload-mismatch")
+ build_system = document.get("build-system")
+ project = document.get("project")
+ if not isinstance(build_system, dict) or not isinstance(project, dict):
+ _reject("source-payload-mismatch")
+ if build_system != {
+ "requires": [f"setuptools=={SETUPTOOLS_VERSION}"],
+ "build-backend": "setuptools.build_meta",
+ }:
+ _reject("source-payload-mismatch")
+ if (
+ project.get("name") != PROJECT_NAME
+ or project.get("version") != PROJECT_VERSION
+ or project.get("requires-python") != ">=3.11"
+ or project.get("dependencies")
+ != [f"Flask=={FLASK_VERSION}", f"waitress=={WAITRESS_VERSION}"]
+ or project.get("readme")
+ != {"file": "PACKAGE.md", "content-type": "text/markdown"}
+ or project.get("license") is not None
+ or project.get("scripts")
+ != {"password-policy-lab": "password_policy_lab.cli:main"}
+ ):
+ _reject("source-payload-mismatch")
+ optional = project.get("optional-dependencies")
+ if not isinstance(optional, dict) or optional != {"dev": list(_DEV_DEPENDENCIES)}:
+ _reject("source-payload-mismatch")
+
+
+def _validate_working_package_inventory(repository: Path) -> None:
+ """Reject ignored as well as visible unexpected package-tree files."""
+
+ package_root = repository / "src/password_policy_lab"
+ expected_files = set(PACKAGE_INPUTS)
+ expected_directories = {
+ str(PurePosixPath(path).parent)
+ for path in PACKAGE_INPUTS
+ if str(PurePosixPath(path).parent) != "src/password_policy_lab"
+ }
+ entry_count = 0
+ try:
+ for item in package_root.rglob("*"):
+ entry_count += 1
+ if entry_count > MAX_PACKAGE_WORKTREE_ENTRIES:
+ _reject("index-invalid")
+ metadata = item.lstat()
+ relative = item.relative_to(repository).as_posix()
+ package_relative = item.relative_to(package_root)
+ cache_parts = package_relative.parts
+ if "__pycache__" in cache_parts:
+ if item.is_symlink() or (
+ stat.S_ISREG(metadata.st_mode) and item.suffix != ".pyc"
+ ):
+ _reject("index-invalid")
+ if not (
+ stat.S_ISDIR(metadata.st_mode) or stat.S_ISREG(metadata.st_mode)
+ ):
+ _reject("index-invalid")
+ continue
+ if item.is_symlink():
+ _reject("source-dirty")
+ if stat.S_ISDIR(metadata.st_mode):
+ if relative not in expected_directories:
+ _reject("index-invalid")
+ elif not stat.S_ISREG(metadata.st_mode) or relative not in expected_files:
+ _reject("index-invalid")
+ except OSError:
+ _reject("source-dirty")
+
+
+def _validate_worktree_payloads(
+ repository: Path,
+ source_files: Sequence[SourceFile],
+) -> None:
+ if {item.path for item in source_files} != set(DISTRIBUTION_INPUTS):
+ _reject("index-invalid")
+ untracked_package = _git(
+ repository,
+ (
+ "ls-files",
+ "--others",
+ "--exclude-standard",
+ "-z",
+ "--",
+ "src/password_policy_lab",
+ ),
+ )
+ if untracked_package:
+ _reject("index-invalid")
+ _validate_working_package_inventory(repository)
+ for source_file in source_files:
+ destination = repository.joinpath(*source_file.path.split("/"))
+ try:
+ metadata = destination.lstat()
+ if not stat.S_ISREG(metadata.st_mode):
+ _reject("source-dirty")
+ working_data = destination.read_bytes()
+ except OSError:
+ _reject("source-dirty")
+ if working_data != source_file.data:
+ _reject("source-dirty")
+
+
+def _collect_source(repository: Path) -> SourceState:
+ """Read the exact distribution payload from Git's stage-zero index."""
+
+ try:
+ root = repository.resolve(strict=True)
+ metadata = root.stat()
+ except OSError:
+ _reject("repository-invalid")
+ if not stat.S_ISDIR(metadata.st_mode) or (root / ".git").is_symlink():
+ _reject("repository-invalid")
+ top_level = _git(root, ("rev-parse", "--show-toplevel")).decode("utf-8").strip()
+ try:
+ if Path(top_level).resolve(strict=True) != root:
+ _reject("repository-invalid")
+ except OSError:
+ _reject("repository-invalid")
+
+ source_tree = _git(root, ("write-tree",)).decode("ascii").strip()
+ if _OBJECT_ID.fullmatch(source_tree) is None:
+ _reject("repository-invalid")
+ entries = _parse_tree_entries(
+ _git(
+ root,
+ (
+ "ls-tree",
+ "-r",
+ "-z",
+ source_tree,
+ "--",
+ "MANIFEST.in",
+ "PACKAGE.md",
+ "pyproject.toml",
+ "src/password_policy_lab",
+ ),
+ )
+ )
+ by_path = {entry.path: entry for entry in entries}
+ if set(by_path) != set(DISTRIBUTION_INPUTS):
+ _reject("index-invalid")
+
+ source_files: list[SourceFile] = []
+ for path in DISTRIBUTION_INPUTS:
+ entry = by_path[path]
+ indexed_data = _git(
+ root,
+ ("cat-file", "blob", entry.object_id),
+ maximum_output=MAX_SOURCE_FILE + 1,
+ )
+ if len(indexed_data) > MAX_SOURCE_FILE:
+ _reject("index-invalid")
+ source_files.append(SourceFile(path, indexed_data, _sha256(indexed_data)))
+
+ _validate_worktree_payloads(root, source_files)
+ _validate_project_configuration(
+ next(item.data for item in source_files if item.path == "pyproject.toml")
+ )
+ final_tree = _git(root, ("write-tree",)).decode("ascii").strip()
+ if final_tree != source_tree:
+ _reject("source-dirty")
+ _validate_worktree_payloads(root, source_files)
+ pairs = tuple((item.path, item.data) for item in source_files)
+ return SourceState(
+ tuple(source_files),
+ _canonical_input_digest(pairs),
+ source_tree,
+ )
+
+
+def _validate_work_root(repository: Path, raw_path: str) -> Path:
+ pure = PurePosixPath(raw_path)
+ if (
+ not raw_path
+ or raw_path != pure.as_posix()
+ or any(part in {"", ".", ".."} for part in pure.parts)
+ ):
+ _reject("work-root-invalid")
+ candidate = (
+ Path(raw_path) if pure.is_absolute() else repository.joinpath(*pure.parts)
+ )
+ try:
+ resolved_repository = repository.resolve(strict=True)
+ lexical_relative = candidate.relative_to(resolved_repository)
+ if not lexical_relative.parts:
+ _reject("work-root-invalid")
+ candidate.resolve(strict=False).relative_to(resolved_repository)
+ current = repository
+ for component in lexical_relative.parts:
+ current /= component
+ if current.exists() and current.is_symlink():
+ _reject("work-root-invalid")
+ candidate.mkdir(parents=True, mode=0o700, exist_ok=True)
+ if not candidate.is_dir() or candidate.is_symlink():
+ _reject("work-root-invalid")
+ except (OSError, ValueError):
+ _reject("work-root-invalid")
+ return candidate
+
+
+def _repository_argument(raw_path: str) -> Path:
+ pure = PurePosixPath(raw_path)
+ if (
+ not raw_path
+ or raw_path != pure.as_posix()
+ or not pure.is_absolute()
+ or any(part in {"", ".", ".."} for part in pure.parts)
+ ):
+ _reject("arguments-invalid")
+ candidate = Path(raw_path)
+ try:
+ if candidate.is_symlink() or candidate.resolve(strict=True) != candidate:
+ _reject("arguments-invalid")
+ except OSError:
+ _reject("arguments-invalid")
+ return candidate
+
+
+def _materialize_snapshot(destination: Path, source: SourceState) -> None:
+ try:
+ destination.mkdir(mode=0o755, exist_ok=False)
+ for source_file in source.files:
+ output = destination.joinpath(*source_file.path.split("/"))
+ output.parent.mkdir(parents=True, mode=0o755, exist_ok=True)
+ descriptor = os.open(
+ output,
+ os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, "O_NOFOLLOW", 0),
+ 0o644,
+ )
+ with os.fdopen(descriptor, "wb", closefd=True) as stream:
+ stream.write(source_file.data)
+ stream.flush()
+ os.fsync(stream.fileno())
+ os.chmod(output, 0o644, follow_symlinks=False)
+ os.utime(
+ output,
+ (FIXED_MTIME, FIXED_MTIME),
+ follow_symlinks=False,
+ )
+ directories = [
+ destination,
+ *[item for item in destination.rglob("*") if item.is_dir()],
+ ]
+ for directory in sorted(
+ directories,
+ key=lambda item: len(item.parts),
+ reverse=True,
+ ):
+ os.chmod(directory, 0o755, follow_symlinks=False)
+ os.utime(
+ directory,
+ (FIXED_MTIME, FIXED_MTIME),
+ follow_symlinks=False,
+ )
+ except OSError:
+ _reject("internal-io")
+
+
+def _check_toolchain() -> Toolchain:
+ running_version = (sys.version_info.major, sys.version_info.minor)
+ if running_version < (3, 11):
+ _reject("toolchain-invalid")
+ expected = {
+ "build": BUILD_VERSION,
+ "setuptools": SETUPTOOLS_VERSION,
+ "Flask": FLASK_VERSION,
+ "waitress": WAITRESS_VERSION,
+ }
+ observed: dict[str, str] = {}
+ try:
+ for package, version in expected.items():
+ observed[package] = importlib.metadata.version(package)
+ if observed[package] != version:
+ _reject("toolchain-invalid")
+ except importlib.metadata.PackageNotFoundError:
+ _reject("toolchain-invalid")
+ return Toolchain(
+ python=".".join(str(part) for part in sys.version_info[:3]),
+ build=observed["build"],
+ setuptools=observed["setuptools"],
+ flask=observed["Flask"],
+ waitress=observed["waitress"],
+ )
+
+
+def _build(
+ snapshot: Path,
+ output: Path,
+ environment_root: Path,
+ *,
+ wheel_only: bool = False,
+) -> BuiltArtifacts | Path:
+ output.mkdir(mode=0o755, exist_ok=False)
+ environment = _process_environment(environment_root)
+ _prepare_environment_directories(environment)
+ command = [
+ sys.executable,
+ "-m",
+ "build",
+ "--no-isolation",
+ ]
+ if wheel_only:
+ command.append("--wheel")
+ command.extend(("--outdir", str(output), "."))
+ _run_command(
+ command,
+ cwd=snapshot,
+ environment=environment,
+ failure_code="build-failed",
+ )
+ try:
+ observed = tuple(sorted(item.name for item in output.iterdir()))
+ if any(item.is_symlink() or not item.is_file() for item in output.iterdir()):
+ _reject("build-output-invalid")
+ except OSError:
+ _reject("build-output-invalid")
+ if wheel_only:
+ if observed != (WHEEL_FILENAME,):
+ _reject("build-output-invalid")
+ return output / WHEEL_FILENAME
+ if observed != tuple(sorted((WHEEL_FILENAME, SDIST_FILENAME))):
+ _reject("build-output-invalid")
+ return BuiltArtifacts(output / WHEEL_FILENAME, output / SDIST_FILENAME)
+
+
+def _files_equal(first: Path, second: Path) -> bool:
+ try:
+ if first.stat().st_size != second.stat().st_size:
+ return False
+ with first.open("rb") as left, second.open("rb") as right:
+ while True:
+ left_chunk = left.read(_READ_CHUNK)
+ right_chunk = right.read(_READ_CHUNK)
+ if left_chunk != right_chunk:
+ return False
+ if not left_chunk:
+ return True
+ except OSError:
+ _reject("internal-io")
+
+
+def _member_map(record: ArtifactRecord) -> dict[str, MemberRecord]:
+ result = {member.name: member for member in record.members}
+ if len(result) != len(record.members):
+ _reject("source-payload-mismatch")
+ return result
+
+
+def _require_member(
+ members: Mapping[str, MemberRecord],
+ name: str,
+ data: bytes,
+) -> None:
+ member = members.get(name)
+ if member is None or member.size != len(data) or member.sha256 != _sha256(data):
+ _reject("source-payload-mismatch")
+
+
+def _verify_wheel_payloads(
+ record: ArtifactRecord,
+ source: SourceState,
+ payloads: ProjectPayloads,
+) -> None:
+ members = _member_map(record)
+ for path in PACKAGE_INPUTS:
+ _require_member(members, path.removeprefix("src/"), source.payload(path))
+ _require_member(members, f"{DIST_INFO}/METADATA", payloads.metadata)
+ _require_member(members, f"{DIST_INFO}/WHEEL", payloads.wheel)
+ _require_member(
+ members,
+ f"{DIST_INFO}/entry_points.txt",
+ payloads.entry_points,
+ )
+ _require_member(members, f"{DIST_INFO}/top_level.txt", payloads.top_level)
+
+
+def _verify_sdist_payloads(
+ record: ArtifactRecord,
+ source: SourceState,
+ payloads: ProjectPayloads,
+) -> None:
+ members = _member_map(record)
+ for path in DISTRIBUTION_INPUTS:
+ _require_member(members, f"{SDIST_ROOT}/{path}", source.payload(path))
+ generated = {
+ "PKG-INFO": payloads.metadata,
+ "setup.cfg": _SETUP_CFG,
+ f"{_EGG_INFO}/PKG-INFO": payloads.metadata,
+ f"{_EGG_INFO}/SOURCES.txt": _SOURCES,
+ f"{_EGG_INFO}/dependency_links.txt": _DEPENDENCY_LINKS,
+ f"{_EGG_INFO}/entry_points.txt": payloads.entry_points,
+ f"{_EGG_INFO}/requires.txt": _REQUIRES,
+ f"{_EGG_INFO}/top_level.txt": payloads.top_level,
+ }
+ for path, data in generated.items():
+ _require_member(members, f"{SDIST_ROOT}/{path}", data)
+
+
+_SMOKE_PROGRAM = r"""
+import hashlib
+import importlib.metadata
+import io
+import json
+import os
+import sys
+from pathlib import Path
+
+target = Path(os.environ["ATTEST_TARGET"]).resolve(strict=True)
+sys.path.insert(0, str(target))
+import password_policy_lab
+from password_policy_lab.cli import run
+
+module_path = Path(password_policy_lab.__file__).resolve(strict=True)
+if not module_path.is_relative_to(target):
+ raise SystemExit(21)
+distribution = importlib.metadata.distribution("password-policy-state-space")
+metadata_root = Path(distribution.locate_file("")).resolve(strict=True)
+if not metadata_root.is_relative_to(target):
+ raise SystemExit(22)
+if distribution.version != "0.1.0":
+ raise SystemExit(23)
+if not (target / "password_policy_state_space-0.1.0.dist-info/METADATA").is_file():
+ raise SystemExit(29)
+entry_points = {
+ (item.group, item.name, item.value) for item in distribution.entry_points
+}
+if entry_points != {
+ ("console_scripts", "password-policy-lab", "password_policy_lab.cli:main")
+}:
+ raise SystemExit(24)
+for relative in (
+ "password_policy_lab/static/styles.css",
+ "password_policy_lab/templates/index.html",
+):
+ if not (target / relative).is_file():
+ raise SystemExit(25)
+
+def invoke():
+ stdout = io.StringIO()
+ stderr = io.StringIO()
+ status = run(
+ ["inspect", "--length", "20", "--format", "json"],
+ stdin=io.StringIO(""),
+ stdout=stdout,
+ stderr=stderr,
+ )
+ if status != 0 or stderr.getvalue():
+ raise SystemExit(26)
+ return stdout.getvalue()
+
+first = invoke()
+second = invoke()
+if first != second:
+ raise SystemExit(27)
+payload = json.loads(first)
+
+def object_keys(value):
+ if isinstance(value, dict):
+ for key, child in value.items():
+ yield key
+ yield from object_keys(child)
+ elif isinstance(value, list):
+ for child in value:
+ yield from object_keys(child)
+
+if not isinstance(payload, dict) or payload.get("operation") != "inspect":
+ raise SystemExit(28)
+forbidden_keys = {
+ "candidate",
+ "generated_password",
+ "password",
+ "sample",
+ "sampled_password",
+ "secret",
+}
+if forbidden_keys.intersection(object_keys(payload)):
+ raise SystemExit(29)
+print(json.dumps({
+ "inspect_sha256": hashlib.sha256(first.encode("utf-8")).hexdigest(),
+ "metadata_origin_in_target": True,
+ "package_origin_in_target": True,
+ "resources_present": True,
+}, ensure_ascii=True, sort_keys=True, separators=(",", ":")))
+"""
+
+
+def _smoke_install(
+ wheel: Path,
+ run_root: Path,
+) -> dict[str, object]:
+ target = run_root / "smoke-target"
+ cwd = run_root / "smoke-cwd"
+ cwd.mkdir(mode=0o755, exist_ok=False)
+ install_environment = _process_environment(run_root / "pip-environment")
+ _prepare_environment_directories(install_environment)
+ _run_command(
+ (
+ sys.executable,
+ "-m",
+ "pip",
+ "install",
+ "--isolated",
+ "--no-index",
+ "--no-deps",
+ "--no-compile",
+ "--target",
+ str(target),
+ str(wheel),
+ ),
+ cwd=cwd,
+ environment=install_environment,
+ failure_code="smoke-failed",
+ )
+ smoke_environment = _process_environment(
+ run_root / "smoke-environment",
+ python_path=target,
+ )
+ smoke_environment["ATTEST_TARGET"] = str(target)
+ _prepare_environment_directories(smoke_environment)
+ result = _run_command(
+ (sys.executable, "-I", "-c", _SMOKE_PROGRAM),
+ cwd=cwd,
+ environment=smoke_environment,
+ failure_code="smoke-failed",
+ timeout=60.0,
+ maximum_output=16 * 1024,
+ )
+ if result.stderr or not result.stdout.endswith(b"\n"):
+ _reject("smoke-failed")
+ try:
+ document = json.loads(result.stdout)
+ except (UnicodeError, json.JSONDecodeError):
+ _reject("smoke-failed")
+ if not isinstance(document, dict) or set(document) != {
+ "inspect_sha256",
+ "metadata_origin_in_target",
+ "package_origin_in_target",
+ "resources_present",
+ }:
+ _reject("smoke-failed")
+ digest = document.get("inspect_sha256")
+ if (
+ not isinstance(digest, str)
+ or re.fullmatch(r"[0-9a-f]{64}", digest) is None
+ or document.get("metadata_origin_in_target") is not True
+ or document.get("package_origin_in_target") is not True
+ or document.get("resources_present") is not True
+ ):
+ _reject("smoke-failed")
+ return cast(dict[str, object], document)
+
+
+def _member_document(member: MemberRecord) -> dict[str, object]:
+ return {
+ "compressed_size": member.compressed_size,
+ "mode": f"{member.mode & 0o7777:04o}",
+ "mtime": member.mtime,
+ "name": member.name,
+ "sha256": member.sha256,
+ "size": member.size,
+ }
+
+
+def _artifact_document(record: ArtifactRecord) -> dict[str, object]:
+ return {
+ "inventory": [_member_document(member) for member in record.members],
+ "member_count": len(record.members),
+ "sha256": record.sha256,
+ "size": record.size,
+ }
+
+
+def _validate_claim_guards(
+ *,
+ raw_wheels_equal: bool,
+ canonical_wheels_equal: bool,
+ canonical_sdists_equal: bool,
+ rebuilt_wheel_equal: bool,
+ smoke_passed: bool,
+) -> None:
+ if not (
+ raw_wheels_equal
+ and canonical_wheels_equal
+ and canonical_sdists_equal
+ and rebuilt_wheel_equal
+ ):
+ _reject("artifact-mismatch")
+ if not smoke_passed:
+ _reject("smoke-failed")
+
+
+def _build_report(
+ *,
+ source: SourceState,
+ toolchain: Toolchain,
+ raw_wheels: tuple[ArtifactRecord, ArtifactRecord],
+ raw_sdists: tuple[ArtifactRecord, ArtifactRecord],
+ wheel: ArtifactRecord,
+ sdist: ArtifactRecord,
+ smoke: Mapping[str, object],
+ raw_wheels_equal: bool,
+ canonical_wheels_equal: bool,
+ canonical_sdists_equal: bool,
+ rebuilt_wheel_equal: bool,
+ smoke_passed: bool,
+) -> dict[str, object]:
+ """Create the path-free claim document from already verified facts."""
+
+ _validate_claim_guards(
+ raw_wheels_equal=raw_wheels_equal,
+ canonical_wheels_equal=canonical_wheels_equal,
+ canonical_sdists_equal=canonical_sdists_equal,
+ rebuilt_wheel_equal=rebuilt_wheel_equal,
+ smoke_passed=smoke_passed,
+ )
+ wheel_document = _artifact_document(wheel)
+ wheel_document.update(
+ {
+ "canonical_builds_byte_equal": canonical_wheels_equal,
+ "filename": WHEEL_FILENAME,
+ "raw_build_count": len(raw_wheels),
+ "raw_builds_byte_equal": raw_wheels_equal,
+ "sdist_rebuild_byte_equal": rebuilt_wheel_equal,
+ }
+ )
+ sdist_document = _artifact_document(sdist)
+ sdist_document.update(
+ {
+ "canonical_builds_byte_equal": canonical_sdists_equal,
+ "filename": SDIST_FILENAME,
+ "raw_build_count": len(raw_sdists),
+ "raw_builds_byte_equality_claimed": False,
+ }
+ )
+ return {
+ "artifacts": {"sdist": sdist_document, "wheel": wheel_document},
+ "build": {
+ "build_count": 2,
+ "build_isolation": False,
+ "fixed_source_date_epoch": FIXED_MTIME,
+ "locale": "C.UTF-8",
+ "network_package_index_enabled": False,
+ "timezone": "UTC",
+ "umask": "0022",
+ },
+ "claim_boundaries": {
+ "arbitrary_archive_safety": False,
+ "artifact_signature_verified": False,
+ "cross_platform_reproducibility": False,
+ "dependency_integrity_verified": False,
+ "fresh_dependency_environment": False,
+ "license_declared": False,
+ },
+ "official": False,
+ "schema_version": SCHEMA_VERSION,
+ "smoke": {
+ "command": "inspect --length 20 --format json",
+ "current_checker_dependencies": {
+ "Flask": toolchain.flask,
+ "waitress": toolchain.waitress,
+ },
+ "dependency_install_mode": "current-pinned-checker-environment",
+ "deterministic": smoke_passed,
+ "inspect_sha256": smoke["inspect_sha256"],
+ "metadata_origin_in_target": True,
+ "package_origin_in_target": True,
+ "pip_compile_bytecode": False,
+ "pip_dependency_resolution": False,
+ "pip_index_enabled": False,
+ "resources_present": True,
+ "sampled_password": False,
+ },
+ "source": {
+ "distribution_input_count": len(source.files),
+ "distribution_input_sha256": source.input_sha256,
+ "git_index_stage": 0,
+ },
+ "toolchain": {
+ "build": toolchain.build,
+ "python": toolchain.python,
+ "setuptools": toolchain.setuptools,
+ },
+ }
+
+
+def _canonical_json(document: Mapping[str, object]) -> str:
+ return (
+ json.dumps(
+ document,
+ ensure_ascii=True,
+ indent=2,
+ sort_keys=True,
+ separators=(",", ": "),
+ )
+ + "\n"
+ )
+
+
+def _text_report(document: Mapping[str, object]) -> str:
+ artifacts = cast(dict[str, dict[str, object]], document["artifacts"])
+ source = cast(dict[str, object], document["source"])
+ return (
+ "distribution attestation: PASS (unofficial)\n"
+ f"source: {source['distribution_input_count']} indexed inputs; "
+ f"sha256={source['distribution_input_sha256']}\n"
+ f"wheel: sha256={artifacts['wheel']['sha256']}; "
+ "two builds and sdist rebuild match\n"
+ f"sdist: sha256={artifacts['sdist']['sha256']}; "
+ "two canonical builds match (raw equality unclaimed)\n"
+ "smoke: deterministic inspect passed; no password sampled\n"
+ "boundaries: no license, signature, dependency-integrity, "
+ "cross-platform, or arbitrary-archive claim\n"
+ )
+
+
+def attest(
+ repository: Path,
+ *,
+ work_root: str = DEFAULT_WORK_ROOT,
+) -> dict[str, object]:
+ """Run the complete attestation and return its deterministic document."""
+
+ toolchain = _check_toolchain()
+ source = _collect_source(repository)
+ trusted_payloads = ProjectPayloads(
+ metadata=build_expected_metadata(source.payload("PACKAGE.md"))
+ )
+ local_work_root = _validate_work_root(repository.resolve(), work_root)
+ try:
+ with tempfile.TemporaryDirectory(
+ prefix="attestation-",
+ dir=local_work_root,
+ ) as temporary_name:
+ run_root = Path(temporary_name)
+ snapshot_a = run_root / "source-a"
+ snapshot_b = run_root / "source-b"
+ _materialize_snapshot(snapshot_a, source)
+ _materialize_snapshot(snapshot_b, source)
+ built_a = cast(
+ BuiltArtifacts,
+ _build(
+ snapshot_a,
+ run_root / "dist-a",
+ run_root / "environment-a",
+ ),
+ )
+ built_b = cast(
+ BuiltArtifacts,
+ _build(
+ snapshot_b,
+ run_root / "dist-b",
+ run_root / "environment-b",
+ ),
+ )
+
+ raw_wheel_a = inspect_wheel(
+ built_a.wheel,
+ WHEEL_MEMBERS,
+ trusted_payloads,
+ require_canonical=False,
+ )
+ raw_wheel_b = inspect_wheel(
+ built_b.wheel,
+ WHEEL_MEMBERS,
+ trusted_payloads,
+ require_canonical=False,
+ )
+ raw_sdist_a = inspect_sdist(
+ built_a.sdist,
+ SDIST_FILES,
+ trusted_payloads,
+ require_canonical=False,
+ )
+ raw_sdist_b = inspect_sdist(
+ built_b.sdist,
+ SDIST_FILES,
+ trusted_payloads,
+ require_canonical=False,
+ )
+ for record in (raw_wheel_a, raw_wheel_b):
+ _verify_wheel_payloads(record, source, trusted_payloads)
+ for record in (raw_sdist_a, raw_sdist_b):
+ _verify_sdist_payloads(record, source, trusted_payloads)
+ raw_wheels_equal = _files_equal(built_a.wheel, built_b.wheel)
+ if not raw_wheels_equal:
+ _reject("artifact-mismatch")
+
+ canonical_wheel_a_path = run_root / "canonical-a" / WHEEL_FILENAME
+ canonical_wheel_b_path = run_root / "canonical-b" / WHEEL_FILENAME
+ canonical_sdist_a_path = run_root / "canonical-a" / SDIST_FILENAME
+ canonical_sdist_b_path = run_root / "canonical-b" / SDIST_FILENAME
+ canonical_wheel_a = canonicalize_wheel(
+ built_a.wheel,
+ canonical_wheel_a_path,
+ WHEEL_MEMBERS,
+ trusted_payloads,
+ )
+ canonical_wheel_b = canonicalize_wheel(
+ built_b.wheel,
+ canonical_wheel_b_path,
+ WHEEL_MEMBERS,
+ trusted_payloads,
+ )
+ canonical_sdist_a = canonicalize_sdist(
+ built_a.sdist,
+ canonical_sdist_a_path,
+ SDIST_FILES,
+ trusted_payloads,
+ )
+ canonical_sdist_b = canonicalize_sdist(
+ built_b.sdist,
+ canonical_sdist_b_path,
+ SDIST_FILES,
+ trusted_payloads,
+ )
+ canonical_wheels_equal = _files_equal(
+ canonical_wheel_a_path,
+ canonical_wheel_b_path,
+ )
+ if not canonical_wheels_equal:
+ _reject("artifact-mismatch")
+ canonical_sdists_equal = _files_equal(
+ canonical_sdist_a_path,
+ canonical_sdist_b_path,
+ )
+ if not canonical_sdists_equal:
+ _reject("artifact-mismatch")
+ _verify_wheel_payloads(canonical_wheel_a, source, trusted_payloads)
+ _verify_wheel_payloads(canonical_wheel_b, source, trusted_payloads)
+ _verify_sdist_payloads(canonical_sdist_a, source, trusted_payloads)
+ _verify_sdist_payloads(canonical_sdist_b, source, trusted_payloads)
+
+ rebuilt_source = run_root / "sdist-source"
+ materialize_canonical_sdist(
+ canonical_sdist_a_path,
+ rebuilt_source,
+ SDIST_FILES,
+ trusted_payloads,
+ )
+ rebuilt_raw_wheel = cast(
+ Path,
+ _build(
+ rebuilt_source,
+ run_root / "sdist-wheel",
+ run_root / "environment-sdist",
+ wheel_only=True,
+ ),
+ )
+ rebuilt_canonical_path = run_root / "sdist-canonical" / WHEEL_FILENAME
+ rebuilt_canonical = canonicalize_wheel(
+ rebuilt_raw_wheel,
+ rebuilt_canonical_path,
+ WHEEL_MEMBERS,
+ trusted_payloads,
+ )
+ _verify_wheel_payloads(rebuilt_canonical, source, trusted_payloads)
+ rebuilt_wheel_equal = _files_equal(
+ canonical_wheel_a_path,
+ rebuilt_canonical_path,
+ )
+ if not rebuilt_wheel_equal:
+ _reject("artifact-mismatch")
+
+ smoke = _smoke_install(canonical_wheel_a_path, run_root)
+ smoke_passed = True
+ return _build_report(
+ source=source,
+ toolchain=toolchain,
+ raw_wheels=(raw_wheel_a, raw_wheel_b),
+ raw_sdists=(raw_sdist_a, raw_sdist_b),
+ wheel=canonical_wheel_a,
+ sdist=canonical_sdist_a,
+ smoke=smoke,
+ raw_wheels_equal=raw_wheels_equal,
+ canonical_wheels_equal=canonical_wheels_equal,
+ canonical_sdists_equal=canonical_sdists_equal,
+ rebuilt_wheel_equal=rebuilt_wheel_equal,
+ smoke_passed=smoke_passed,
+ )
+ except DistributionContractError:
+ _reject("archive-contract")
+ except OSError:
+ _reject("internal-io")
+
+
+def _parser() -> argparse.ArgumentParser:
+ parser = _SafeArgumentParser(
+ prog="attest-distribution",
+ description="Build and verify the project distribution without sampling.",
+ allow_abbrev=False,
+ )
+ parser.add_argument(
+ "--format",
+ choices=("json", "text"),
+ default="text",
+ action=_StoreOnce,
+ )
+ parser.add_argument(
+ "--work-root",
+ default=DEFAULT_WORK_ROOT,
+ action=_StoreOnce,
+ )
+ parser.add_argument(
+ "--root",
+ default=None,
+ action=_StoreOnce,
+ )
+ return parser
+
+
+def run(
+ argv: Sequence[str],
+ *,
+ stdout: TextIO,
+ stderr: TextIO,
+ repository: Path | None = None,
+) -> int:
+ try:
+ arguments = _parser().parse_args(argv)
+ supplied_root = cast(str | None, arguments.root)
+ if repository is not None and supplied_root is not None:
+ _reject("arguments-invalid")
+ root = (
+ repository
+ if repository is not None
+ else (
+ Path(__file__).resolve().parents[1]
+ if supplied_root is None
+ else _repository_argument(supplied_root)
+ )
+ )
+ document = attest(root, work_root=cast(str, arguments.work_root))
+ output_format = cast(str, arguments.format)
+ stdout.write(
+ _canonical_json(document)
+ if output_format == "json"
+ else _text_report(document)
+ )
+ return 0
+ except AttestationError as error:
+ stderr.write(f"error: distribution attestation rejected: {error.code}\n")
+ return 1
+ except DistributionContractError:
+ stderr.write("error: distribution attestation rejected: archive-contract\n")
+ return 1
+ except OSError:
+ stderr.write("error: distribution attestation rejected: internal-io\n")
+ return 1
+
+
+def main(argv: Sequence[str] | None = None) -> int:
+ return run(
+ sys.argv[1:] if argv is None else argv,
+ stdout=sys.stdout,
+ stderr=sys.stderr,
+ )
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/scripts/check_evidence.py b/scripts/check_evidence.py
index bdebc42..a91d4c0 100644
--- a/scripts/check_evidence.py
+++ b/scripts/check_evidence.py
@@ -39,6 +39,8 @@
{
"docs/assets/architecture.svg",
"docs/assets/cli-inspect.png",
+ "docs/assets/distribution-check.png",
+ "docs/assets/distribution-contract.svg",
"docs/assets/quality-gate.png",
"docs/assets/setup-workflow.svg",
"docs/assets/state-space-sweep.png",
@@ -52,6 +54,8 @@
EXPECTED_RAW_EVIDENCE = frozenset(
{
"docs/evidence/cli-inspect.txt",
+ "docs/evidence/distribution-attestation.json",
+ "docs/evidence/distribution-check.txt",
"docs/evidence/quality-gate.txt",
"docs/evidence/state-space-sweep.csv",
}
@@ -78,6 +82,7 @@
MEDIA_TYPES = {
".csv": "text/csv",
".gif": "image/gif",
+ ".json": "application/json",
".png": "image/png",
".svg": "image/svg+xml",
".txt": "text/plain",
@@ -95,8 +100,67 @@
"PYTHONPATH=src python -m pytest --cov=password_policy_lab "
"--cov-branch --cov-report=term-missing -q"
),
+ "python scripts/attest_distribution.py",
"python -m pip check",
)
+DISTRIBUTION_INPUTS = (
+ "MANIFEST.in",
+ "PACKAGE.md",
+ "pyproject.toml",
+ "src/password_policy_lab/__init__.py",
+ "src/password_policy_lab/__main__.py",
+ "src/password_policy_lab/cli.py",
+ "src/password_policy_lab/errors.py",
+ "src/password_policy_lab/inspection.py",
+ "src/password_policy_lab/policy.py",
+ "src/password_policy_lab/profiles.py",
+ "src/password_policy_lab/py.typed",
+ "src/password_policy_lab/space.py",
+ "src/password_policy_lab/static/styles.css",
+ "src/password_policy_lab/templates/index.html",
+ "src/password_policy_lab/web.py",
+)
+_DISTRIBUTION_ROOT = "password_policy_state_space-0.1.0"
+_DIST_INFO = "password_policy_state_space-0.1.0.dist-info"
+_EGG_INFO = "src/password_policy_state_space.egg-info"
+_WHEEL_MEMBERS = (
+ "password_policy_lab/__init__.py",
+ "password_policy_lab/__main__.py",
+ "password_policy_lab/cli.py",
+ "password_policy_lab/errors.py",
+ "password_policy_lab/inspection.py",
+ "password_policy_lab/policy.py",
+ "password_policy_lab/profiles.py",
+ "password_policy_lab/py.typed",
+ "password_policy_lab/space.py",
+ "password_policy_lab/web.py",
+ "password_policy_lab/static/styles.css",
+ "password_policy_lab/templates/index.html",
+ f"{_DIST_INFO}/METADATA",
+ f"{_DIST_INFO}/WHEEL",
+ f"{_DIST_INFO}/entry_points.txt",
+ f"{_DIST_INFO}/top_level.txt",
+ f"{_DIST_INFO}/RECORD",
+)
+_SDIST_FILES = tuple(
+ sorted(
+ {
+ "MANIFEST.in",
+ "PACKAGE.md",
+ "PKG-INFO",
+ "pyproject.toml",
+ "setup.cfg",
+ *DISTRIBUTION_INPUTS[3:],
+ f"{_EGG_INFO}/PKG-INFO",
+ f"{_EGG_INFO}/SOURCES.txt",
+ f"{_EGG_INFO}/dependency_links.txt",
+ f"{_EGG_INFO}/entry_points.txt",
+ f"{_EGG_INFO}/requires.txt",
+ f"{_EGG_INFO}/top_level.txt",
+ }
+ )
+)
+_FIXED_DISTRIBUTION_MTIME = 1_704_067_200
SWEEP_COLUMNS = (
"length",
"policy_sha256",
@@ -221,29 +285,35 @@ def _safe_path(root: Path, raw_path: object, label: str) -> tuple[str, Path]:
return path, destination
-def _load_json(path: Path) -> tuple[dict[str, object], str]:
+def _load_json(
+ path: Path,
+ *,
+ label: str = "manifest",
+) -> tuple[dict[str, object], str]:
try:
+ if not 0 < path.stat().st_size <= 2_000_000:
+ _fail(f"{label} has an invalid byte size")
raw = path.read_bytes()
except OSError:
- _fail("manifest is missing or unreadable")
+ _fail(f"{label} is missing or unreadable")
try:
text = raw.decode("utf-8")
except UnicodeDecodeError:
- _fail("manifest must be UTF-8")
+ _fail(f"{label} must be UTF-8")
def reject_duplicates(pairs: list[tuple[str, object]]) -> dict[str, object]:
result: dict[str, object] = {}
for key, value in pairs:
if key in result:
- _fail("manifest contains a duplicate object key")
+ _fail(f"{label} contains a duplicate object key")
result[key] = value
return result
try:
value = json.loads(text, object_pairs_hook=reject_duplicates)
except (json.JSONDecodeError, RecursionError):
- _fail("manifest is not valid bounded JSON")
- document = _mapping(value, "manifest")
+ _fail(f"{label} is not valid bounded JSON")
+ document = _mapping(value, label)
canonical = (
json.dumps(
document,
@@ -254,7 +324,7 @@ def reject_duplicates(pairs: list[tuple[str, object]]) -> dict[str, object]:
+ "\n"
)
if text != canonical:
- _fail("manifest is not canonical sorted JSON")
+ _fail(f"{label} is not canonical sorted JSON")
return document, text
@@ -694,7 +764,14 @@ def _validate_source_files(
paths.append(path)
if paths != sorted(paths) or len(paths) != len(set(paths)):
_fail("source_files paths must be unique and sorted")
- expected = {"Makefile", "README.md", "app.py", "pyproject.toml"}
+ expected = {
+ "MANIFEST.in",
+ "Makefile",
+ "PACKAGE.md",
+ "README.md",
+ "app.py",
+ "pyproject.toml",
+ }
for directory_name in ("scripts", "src", "tests"):
directory = root / directory_name
if not directory.is_dir():
@@ -759,9 +836,21 @@ def _validate_capture(value: object) -> dict[str, tuple[int, int]]:
capture = _mapping(value, "capture")
_exact_keys(
capture,
- {"requests", "sampler_calls", "sampling_guard", "server"},
+ {
+ "chromium_launch_args",
+ "requests",
+ "sampler_calls",
+ "sampling_guard",
+ "server",
+ },
"capture",
)
+ launch_arguments = _validate_string_list(
+ capture["chromium_launch_args"],
+ "capture.chromium_launch_args",
+ )
+ if launch_arguments != ["--num-raster-threads=1"]:
+ _fail("capture must pin Chromium to one raster thread")
server = _string(capture["server"], "capture.server")
_validate_safe_text(server, "capture.server")
if server != "waitress":
@@ -1089,6 +1178,376 @@ def _validate_raw_evidence(textual: dict[str, str]) -> None:
_fail("quality-gate transcript omits ordered command headers")
+def _distribution_input_digest(root: Path) -> str:
+ digest = hashlib.sha256()
+ for relative in DISTRIBUTION_INPUTS:
+ data = _read_bounded(
+ root.joinpath(*relative.split("/")),
+ relative,
+ maximum=2 * 1024 * 1024,
+ )
+ encoded_path = relative.encode("ascii")
+ digest.update(len(encoded_path).to_bytes(4, "big"))
+ digest.update(encoded_path)
+ digest.update(len(data).to_bytes(8, "big"))
+ digest.update(data)
+ return digest.hexdigest()
+
+
+def _distribution_sha(value: object, label: str) -> str:
+ digest = _string(value, label)
+ if _SHA256.fullmatch(digest) is None:
+ _fail(f"{label} is not a lowercase SHA-256")
+ return digest
+
+
+def _sdist_inventory_plan() -> tuple[tuple[str, ...], frozenset[str]]:
+ directories = {_DISTRIBUTION_ROOT}
+ files: set[str] = set()
+ for relative in _SDIST_FILES:
+ full_name = f"{_DISTRIBUTION_ROOT}/{relative}"
+ files.add(full_name)
+ components = full_name.split("/")
+ for length in range(1, len(components)):
+ directories.add("/".join(components[:length]))
+ return tuple(sorted(directories | files)), frozenset(directories)
+
+
+def _validate_distribution_inventory(
+ root: Path,
+ value: object,
+ *,
+ kind: str,
+) -> None:
+ entries = _sequence(value, f"distribution.artifacts.{kind}.inventory")
+ expected_names: tuple[str, ...]
+ directories: frozenset[str]
+ if kind == "wheel":
+ expected_names = _WHEEL_MEMBERS
+ directories = frozenset()
+ source_names = {
+ path.removeprefix("src/"): path for path in DISTRIBUTION_INPUTS[3:]
+ }
+ else:
+ expected_names, directories = _sdist_inventory_plan()
+ source_names = {
+ f"{_DISTRIBUTION_ROOT}/{path}": path for path in DISTRIBUTION_INPUTS
+ }
+ observed: list[str] = []
+ empty_digest = hashlib.sha256(b"").hexdigest()
+ for index, raw_entry in enumerate(entries):
+ label = f"distribution.artifacts.{kind}.inventory[{index}]"
+ entry = _mapping(raw_entry, label)
+ _exact_keys(
+ entry,
+ {"compressed_size", "mode", "mtime", "name", "sha256", "size"},
+ label,
+ )
+ name = _string(entry["name"], f"{label}.name")
+ observed.append(name)
+ is_directory = name in directories
+ expected_mode = "0755" if is_directory else "0644"
+ if entry["mode"] != expected_mode:
+ _fail(f"{label}.mode is not canonical")
+ if entry["mtime"] != _FIXED_DISTRIBUTION_MTIME:
+ _fail(f"{label}.mtime is not canonical")
+ size = _integer(entry["size"], f"{label}.size")
+ digest = _distribution_sha(entry["sha256"], f"{label}.sha256")
+ if kind == "wheel":
+ _integer(
+ entry["compressed_size"],
+ f"{label}.compressed_size",
+ minimum=1,
+ )
+ elif entry["compressed_size"] is not None:
+ _fail(f"{label}.compressed_size must be null for tar members")
+ if is_directory and (size != 0 or digest != empty_digest):
+ _fail(f"{label} has invalid canonical directory facts")
+ source_path = source_names.get(name)
+ if source_path is not None:
+ source_data = _read_bounded(
+ root.joinpath(*source_path.split("/")),
+ source_path,
+ maximum=2 * 1024 * 1024,
+ )
+ if size != len(source_data) or digest != _sha256(source_data):
+ _fail(f"{label} does not match its repository source")
+ if tuple(observed) != expected_names:
+ _fail(f"distribution.artifacts.{kind}.inventory is not exact and ordered")
+
+
+def _validate_distribution_artifact(
+ root: Path,
+ value: object,
+ *,
+ kind: str,
+) -> str:
+ artifact = _mapping(value, f"distribution.artifacts.{kind}")
+ common = {
+ "canonical_builds_byte_equal",
+ "filename",
+ "inventory",
+ "member_count",
+ "raw_build_count",
+ "sha256",
+ "size",
+ }
+ extra = (
+ {"raw_builds_byte_equal", "sdist_rebuild_byte_equal"}
+ if kind == "wheel"
+ else {"raw_builds_byte_equality_claimed"}
+ )
+ _exact_keys(artifact, common | extra, f"distribution.artifacts.{kind}")
+ expected_count = 17 if kind == "wheel" else 29
+ expected_filename = (
+ "password_policy_state_space-0.1.0-py3-none-any.whl"
+ if kind == "wheel"
+ else "password_policy_state_space-0.1.0.tar.gz"
+ )
+ if artifact["filename"] != expected_filename:
+ _fail(f"distribution.artifacts.{kind}.filename is incorrect")
+ if artifact["member_count"] != expected_count:
+ _fail(f"distribution.artifacts.{kind}.member_count is incorrect")
+ if artifact["raw_build_count"] != 2:
+ _fail(f"distribution.artifacts.{kind}.raw_build_count is incorrect")
+ if not _boolean(
+ artifact["canonical_builds_byte_equal"],
+ f"distribution.artifacts.{kind}.canonical_builds_byte_equal",
+ ):
+ _fail(f"distribution.artifacts.{kind} canonical builds did not match")
+ if kind == "wheel":
+ if not _boolean(
+ artifact["raw_builds_byte_equal"],
+ "distribution.artifacts.wheel.raw_builds_byte_equal",
+ ) or not _boolean(
+ artifact["sdist_rebuild_byte_equal"],
+ "distribution.artifacts.wheel.sdist_rebuild_byte_equal",
+ ):
+ _fail("distribution wheel equality claims are not proven")
+ elif _boolean(
+ artifact["raw_builds_byte_equality_claimed"],
+ "distribution.artifacts.sdist.raw_builds_byte_equality_claimed",
+ ):
+ _fail("raw sdist byte equality must remain unclaimed")
+ size = _integer(artifact["size"], f"distribution.artifacts.{kind}.size", minimum=1)
+ if size > 5 * 1024 * 1024:
+ _fail(f"distribution.artifacts.{kind}.size exceeds the contract")
+ digest = _distribution_sha(
+ artifact["sha256"],
+ f"distribution.artifacts.{kind}.sha256",
+ )
+ _validate_distribution_inventory(root, artifact["inventory"], kind=kind)
+ return digest
+
+
+def _expected_distribution_transcript(document: dict[str, object]) -> str:
+ artifacts = _mapping(document["artifacts"], "distribution.artifacts")
+ wheel = _mapping(artifacts["wheel"], "distribution.artifacts.wheel")
+ sdist = _mapping(artifacts["sdist"], "distribution.artifacts.sdist")
+ source = _mapping(document["source"], "distribution.source")
+ return (
+ "$ python scripts/attest_distribution.py\n"
+ "distribution attestation: PASS (unofficial)\n"
+ f"source: {source['distribution_input_count']} indexed inputs; "
+ f"sha256={source['distribution_input_sha256']}\n"
+ f"wheel: sha256={wheel['sha256']}; "
+ "two builds and sdist rebuild match\n"
+ f"sdist: sha256={sdist['sha256']}; "
+ "two canonical builds match (raw equality unclaimed)\n"
+ "smoke: deterministic inspect passed; no password sampled\n"
+ "boundaries: no license, signature, dependency-integrity, "
+ "cross-platform, or arbitrary-archive claim\n"
+ )
+
+
+def _validate_distribution_attestation(
+ root: Path,
+ document: dict[str, object],
+ json_text: str,
+ transcript: str,
+ diagram: str,
+) -> None:
+ _exact_keys(
+ document,
+ {
+ "artifacts",
+ "build",
+ "claim_boundaries",
+ "official",
+ "schema_version",
+ "smoke",
+ "source",
+ "toolchain",
+ },
+ "distribution",
+ )
+ if document["schema_version"] != 1:
+ _fail("distribution.schema_version must be 1")
+ if _boolean(document["official"], "distribution.official"):
+ _fail("distribution attestation must remain unofficial")
+
+ artifacts = _mapping(document["artifacts"], "distribution.artifacts")
+ _exact_keys(artifacts, {"sdist", "wheel"}, "distribution.artifacts")
+ wheel_sha = _validate_distribution_artifact(
+ root,
+ artifacts["wheel"],
+ kind="wheel",
+ )
+ sdist_sha = _validate_distribution_artifact(
+ root,
+ artifacts["sdist"],
+ kind="sdist",
+ )
+
+ build = _mapping(document["build"], "distribution.build")
+ _exact_keys(
+ build,
+ {
+ "build_count",
+ "build_isolation",
+ "fixed_source_date_epoch",
+ "locale",
+ "network_package_index_enabled",
+ "timezone",
+ "umask",
+ },
+ "distribution.build",
+ )
+ if (
+ build["build_count"] != 2
+ or build["fixed_source_date_epoch"] != _FIXED_DISTRIBUTION_MTIME
+ or build["locale"] != "C.UTF-8"
+ or build["timezone"] != "UTC"
+ or build["umask"] != "0022"
+ or _boolean(build["build_isolation"], "distribution.build.build_isolation")
+ or _boolean(
+ build["network_package_index_enabled"],
+ "distribution.build.network_package_index_enabled",
+ )
+ ):
+ _fail("distribution.build does not match the reproducible build contract")
+
+ boundaries = _mapping(
+ document["claim_boundaries"],
+ "distribution.claim_boundaries",
+ )
+ boundary_keys = {
+ "arbitrary_archive_safety",
+ "artifact_signature_verified",
+ "cross_platform_reproducibility",
+ "dependency_integrity_verified",
+ "fresh_dependency_environment",
+ "license_declared",
+ }
+ _exact_keys(boundaries, boundary_keys, "distribution.claim_boundaries")
+ if any(
+ _boolean(boundaries[key], f"distribution.claim_boundaries.{key}")
+ for key in boundary_keys
+ ):
+ _fail("distribution claim boundaries must all remain false")
+
+ source = _mapping(document["source"], "distribution.source")
+ _exact_keys(
+ source,
+ {
+ "distribution_input_count",
+ "distribution_input_sha256",
+ "git_index_stage",
+ },
+ "distribution.source",
+ )
+ input_sha = _distribution_sha(
+ source["distribution_input_sha256"],
+ "distribution.source.distribution_input_sha256",
+ )
+ if source["distribution_input_count"] != 15 or source["git_index_stage"] != 0:
+ _fail("distribution source cardinality or index stage is incorrect")
+ if input_sha != _distribution_input_digest(root):
+ _fail("distribution input digest does not match repository sources")
+
+ toolchain = _mapping(document["toolchain"], "distribution.toolchain")
+ _exact_keys(toolchain, {"build", "python", "setuptools"}, "distribution.toolchain")
+ python_version = _string(toolchain["python"], "distribution.toolchain.python")
+ if (
+ toolchain["build"] != "1.5.0"
+ or toolchain["setuptools"] != "83.0.0"
+ or re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+", python_version) is None
+ ):
+ _fail("distribution.toolchain is not the pinned checker toolchain")
+
+ smoke = _mapping(document["smoke"], "distribution.smoke")
+ _exact_keys(
+ smoke,
+ {
+ "command",
+ "current_checker_dependencies",
+ "dependency_install_mode",
+ "deterministic",
+ "inspect_sha256",
+ "metadata_origin_in_target",
+ "package_origin_in_target",
+ "pip_compile_bytecode",
+ "pip_dependency_resolution",
+ "pip_index_enabled",
+ "resources_present",
+ "sampled_password",
+ },
+ "distribution.smoke",
+ )
+ dependencies = _mapping(
+ smoke["current_checker_dependencies"],
+ "distribution.smoke.current_checker_dependencies",
+ )
+ if dependencies != {"Flask": "3.1.3", "waitress": "3.0.2"}:
+ _fail("distribution smoke dependencies are not the pinned checker versions")
+ if (
+ smoke["command"] != "inspect --length 20 --format json"
+ or smoke["dependency_install_mode"] != "current-pinned-checker-environment"
+ or not _boolean(smoke["deterministic"], "distribution.smoke.deterministic")
+ or not _boolean(
+ smoke["metadata_origin_in_target"],
+ "distribution.smoke.metadata_origin_in_target",
+ )
+ or not _boolean(
+ smoke["package_origin_in_target"],
+ "distribution.smoke.package_origin_in_target",
+ )
+ or not _boolean(
+ smoke["resources_present"],
+ "distribution.smoke.resources_present",
+ )
+ or _boolean(
+ smoke["pip_compile_bytecode"],
+ "distribution.smoke.pip_compile_bytecode",
+ )
+ or _boolean(
+ smoke["pip_dependency_resolution"],
+ "distribution.smoke.pip_dependency_resolution",
+ )
+ or _boolean(smoke["pip_index_enabled"], "distribution.smoke.pip_index_enabled")
+ or _boolean(smoke["sampled_password"], "distribution.smoke.sampled_password")
+ ):
+ _fail("distribution smoke facts do not match the installed-target contract")
+ _distribution_sha(smoke["inspect_sha256"], "distribution.smoke.inspect_sha256")
+
+ _validate_safe_text(json_text, "distribution attestation")
+ if transcript != _expected_distribution_transcript(document):
+ _fail("distribution transcript does not agree with its canonical JSON")
+ required_diagram_terms = {
+ "15 stage-zero git blobs",
+ "17 exact members",
+ "23 files + 6 directories",
+ "installed smoke",
+ "official: false",
+ input_sha[:16],
+ wheel_sha[:16],
+ sdist_sha[:16],
+ }
+ folded_diagram = diagram.casefold()
+ if any(term not in folded_diagram for term in required_diagram_terms):
+ _fail("distribution diagram omits a measured fact or claim boundary")
+
+
def _module_tree(root: Path, module: str) -> ast.Module:
path = root / "src" / Path(*module.split(".")).with_suffix(".py")
try:
@@ -1387,6 +1846,19 @@ def validate_evidence(root: Path) -> None:
textual = _validate_artifacts(repository, document["artifacts"], viewports)
for artifact, text in textual.items():
_validate_safe_text(text, artifact)
+ distribution_document, distribution_text = _load_json(
+ repository / "docs/evidence/distribution-attestation.json",
+ label="distribution attestation",
+ )
+ if textual["docs/evidence/distribution-attestation.json"] != distribution_text:
+ _fail("distribution artifact text changed between bounded reads")
+ _validate_distribution_attestation(
+ repository,
+ distribution_document,
+ distribution_text,
+ textual["docs/evidence/distribution-check.txt"],
+ textual["docs/assets/distribution-contract.svg"],
+ )
_validate_raw_evidence(textual)
_validate_gif_fidelity(repository)
_validate_ast_claims(repository, textual)
diff --git a/scripts/distribution_contract.py b/scripts/distribution_contract.py
new file mode 100644
index 0000000..add0d75
--- /dev/null
+++ b/scripts/distribution_contract.py
@@ -0,0 +1,920 @@
+"""Bounded, project-specific contracts for release archives.
+
+This module deliberately implements the archive profile used by this project; it
+is not a generic hostile-archive sandbox. Callers supply the exact member
+allowlists and the expected project metadata derived from trusted source files.
+"""
+
+from __future__ import annotations
+
+import base64
+import csv
+import decimal
+import gzip
+import hashlib
+import hmac
+import io
+import os
+import re
+import shutil
+import stat
+import tarfile
+import tempfile
+import zipfile
+import zlib
+from collections.abc import Callable, Mapping, Sequence
+from contextlib import suppress
+from dataclasses import dataclass
+from pathlib import Path
+from typing import IO, BinaryIO, Literal, NoReturn
+
+PROJECT_NAME = "password-policy-state-space"
+PROJECT_VERSION = "0.1.0"
+NORMALIZED_NAME = "password_policy_state_space"
+DIST_INFO = f"{NORMALIZED_NAME}-{PROJECT_VERSION}.dist-info"
+SDIST_ROOT = f"{NORMALIZED_NAME}-{PROJECT_VERSION}"
+
+# 2024-01-01 00:00:00 UTC. ZIP timestamps have two-second resolution.
+FIXED_MTIME = 1_704_067_200
+FIXED_ZIP_TIME = (2024, 1, 1, 0, 0, 0)
+
+MAX_OUTER_SIZE = 5 * 1024 * 1024
+MAX_MEMBER_SIZE = 2 * 1024 * 1024
+MAX_WHEEL_MEMBERS = 128
+MAX_WHEEL_PAYLOAD = 10 * 1024 * 1024
+MAX_SDIST_MEMBERS = 256
+MAX_SDIST_PAYLOAD = 20 * 1024 * 1024
+MAX_SDIST_STREAM = 24 * 1024 * 1024
+MAX_COMPRESSION_RATIO = 100
+MAX_RAW_MTIME = 4_102_444_800
+_CHUNK_SIZE = 64 * 1024
+
+EXPECTED_WHEEL = (
+ b"Wheel-Version: 1.0\n"
+ b"Generator: setuptools (83.0.0)\n"
+ b"Root-Is-Purelib: true\n"
+ b"Tag: py3-none-any\n"
+ b"\n"
+)
+EXPECTED_ENTRY_POINTS = (
+ b"[console_scripts]\npassword-policy-lab = password_policy_lab.cli:main\n"
+)
+EXPECTED_TOP_LEVEL = b"password_policy_lab\n"
+EXPECTED_METADATA_PREFIX = (
+ b"Metadata-Version: 2.4\n"
+ b"Name: password-policy-state-space\n"
+ b"Version: 0.1.0\n"
+ b"Summary: Exact counting and uniform sampling for constrained password policies\n"
+ b"Author: Omar Ibrahim\n"
+ b"Project-URL: Repository, https://github.com/omar07ibrahim/PasswordGenerator\n"
+ b"Project-URL: Issues, https://github.com/omar07ibrahim/PasswordGenerator/issues\n"
+ b"Requires-Python: >=3.11\n"
+ b"Description-Content-Type: text/markdown\n"
+ b"Requires-Dist: Flask==3.1.3\n"
+ b"Requires-Dist: waitress==3.0.2\n"
+ b"Provides-Extra: dev\n"
+ b'Requires-Dist: build==1.5.0; extra == "dev"\n'
+ b'Requires-Dist: matplotlib==3.11.1; extra == "dev"\n'
+ b'Requires-Dist: mypy==2.3.0; extra == "dev"\n'
+ b'Requires-Dist: numpy==2.3.5; extra == "dev"\n'
+ b'Requires-Dist: Pillow==12.3.0; extra == "dev"\n'
+ b'Requires-Dist: playwright==1.61.0; extra == "dev"\n'
+ b'Requires-Dist: pytest==9.1.1; extra == "dev"\n'
+ b'Requires-Dist: pytest-cov==7.1.0; extra == "dev"\n'
+ b'Requires-Dist: ruff==0.16.0; extra == "dev"\n'
+ b'Requires-Dist: setuptools==83.0.0; extra == "dev"\n'
+ b"\n"
+)
+
+_SAFE_COMPONENT = re.compile(r"[A-Za-z0-9_.-]+\Z")
+_RECORD_DIGEST = re.compile(r"sha256=([A-Za-z0-9_-]{43})\Z")
+_PAX_MTIME = re.compile(r"(?:0|[1-9][0-9]{0,9})(?:\.(?:0|[0-9]{0,8}[1-9]))?\Z")
+_WINDOWS_RESERVED = frozenset(
+ {"CON", "PRN", "AUX", "NUL"}
+ | {f"COM{number}" for number in range(1, 10)}
+ | {f"LPT{number}" for number in range(1, 10)}
+)
+
+ErrorCode = Literal[
+ "allowlist-invalid",
+ "archive-invalid",
+ "archive-too-large",
+ "canonical-metadata-invalid",
+ "compression-invalid",
+ "member-invalid",
+ "member-limit-exceeded",
+ "metadata-invalid",
+ "output-invalid",
+ "path-invalid",
+ "record-invalid",
+]
+
+
+class DistributionContractError(ValueError):
+ """A stable rejection whose message never includes untrusted details."""
+
+ __slots__ = ("code",)
+
+ def __init__(self, code: ErrorCode) -> None:
+ self.code = code
+ super().__init__(f"distribution contract rejected: {code}")
+
+
+@dataclass(frozen=True, slots=True)
+class ProjectPayloads:
+ """Exact trusted payloads expected in project metadata members."""
+
+ metadata: bytes
+ wheel: bytes = EXPECTED_WHEEL
+ entry_points: bytes = EXPECTED_ENTRY_POINTS
+ top_level: bytes = EXPECTED_TOP_LEVEL
+
+ def __post_init__(self) -> None:
+ if (
+ not isinstance(self.metadata, bytes)
+ or len(self.metadata) > MAX_MEMBER_SIZE
+ or self.wheel != EXPECTED_WHEEL
+ or self.entry_points != EXPECTED_ENTRY_POINTS
+ or self.top_level != EXPECTED_TOP_LEVEL
+ ):
+ _reject("metadata-invalid")
+
+
+@dataclass(frozen=True, slots=True)
+class MemberRecord:
+ """Immutable facts measured while streaming one archive member."""
+
+ name: str
+ size: int
+ sha256: str
+ compressed_size: int | None
+ mode: int
+ mtime: int
+
+
+@dataclass(frozen=True, slots=True)
+class ArtifactRecord:
+ """Immutable facts measured for a complete accepted artifact."""
+
+ kind: Literal["wheel", "sdist"]
+ size: int
+ sha256: str
+ members: tuple[MemberRecord, ...]
+
+
+@dataclass(frozen=True, slots=True)
+class _Inspection:
+ record: ArtifactRecord
+ payloads: Mapping[str, bytes]
+
+
+def build_expected_metadata(description: bytes) -> bytes:
+ """Bind trusted ``PACKAGE.md`` bytes to the exact project metadata header."""
+
+ if (
+ not isinstance(description, bytes)
+ or len(description) > MAX_MEMBER_SIZE - len(EXPECTED_METADATA_PREFIX)
+ or b"\x00" in description
+ ):
+ _reject("metadata-invalid")
+ return EXPECTED_METADATA_PREFIX + description
+
+
+def inspect_wheel(
+ path: str | os.PathLike[str],
+ allowed_members: Sequence[str],
+ payloads: ProjectPayloads,
+ *,
+ require_canonical: bool = True,
+) -> ArtifactRecord:
+ """Inspect one wheel against an exact ordered, project-specific contract."""
+
+ return _public_inspect_wheel(
+ path, allowed_members, payloads, require_canonical=require_canonical
+ ).record
+
+
+def canonicalize_wheel(
+ source: str | os.PathLike[str],
+ destination: str | os.PathLike[str],
+ allowed_members: Sequence[str],
+ payloads: ProjectPayloads,
+) -> ArtifactRecord:
+ """Rewrite a validated wheel with fixed ZIP container metadata."""
+
+ inspected = _public_inspect_wheel(
+ source, allowed_members, payloads, require_canonical=False
+ )
+ expected = tuple(allowed_members)
+ output = Path(destination)
+
+ _atomic_output(
+ output,
+ lambda stream: _write_canonical_wheel(stream, expected, inspected.payloads),
+ )
+ return inspect_wheel(output, expected, payloads)
+
+
+def inspect_sdist(
+ path: str | os.PathLike[str],
+ allowed_files: Sequence[str],
+ payloads: ProjectPayloads,
+ *,
+ require_canonical: bool = True,
+) -> ArtifactRecord:
+ """Inspect one source distribution with an exact regular-file allowlist."""
+
+ return _public_inspect_sdist(
+ path, allowed_files, payloads, require_canonical=require_canonical
+ ).record
+
+
+def canonicalize_sdist(
+ source: str | os.PathLike[str],
+ destination: str | os.PathLike[str],
+ allowed_files: Sequence[str],
+ payloads: ProjectPayloads,
+) -> ArtifactRecord:
+ """Rewrite a validated sdist as deterministic USTAR inside deterministic gzip."""
+
+ inspected = _public_inspect_sdist(
+ source, allowed_files, payloads, require_canonical=False
+ )
+ files = _validated_allowlist(allowed_files, MAX_SDIST_MEMBERS)
+ expected = _sdist_member_plan(files)
+ output = Path(destination)
+
+ _atomic_output(
+ output,
+ lambda stream: _write_canonical_sdist(stream, expected, inspected.payloads),
+ )
+ return inspect_sdist(output, files, payloads)
+
+
+def materialize_canonical_sdist(
+ archive_path: str | os.PathLike[str],
+ destination: str | os.PathLike[str],
+ allowed_files: Sequence[str],
+ payloads: ProjectPayloads,
+) -> ArtifactRecord:
+ """Materialize accepted regular files without using ``extractall``.
+
+ ``destination`` must not already exist and becomes the sdist root contents;
+ the archive's fixed top-level directory is intentionally stripped.
+ """
+
+ inspected = _public_inspect_sdist(
+ archive_path, allowed_files, payloads, require_canonical=True
+ )
+ files = _validated_allowlist(allowed_files, MAX_SDIST_MEMBERS)
+ target = Path(destination)
+ parent = target.parent
+ temporary: Path | None = None
+ try:
+ _require_absent(target)
+ parent.mkdir(parents=True, exist_ok=True)
+ temporary = Path(tempfile.mkdtemp(prefix=".sdist-materialize-", dir=parent))
+ os.chmod(temporary, 0o755)
+ for relative in files:
+ full_name = f"{SDIST_ROOT}/{relative}"
+ output = temporary.joinpath(*relative.split("/"))
+ output.parent.mkdir(parents=True, exist_ok=True, mode=0o755)
+ _verify_materialized_parent(temporary, output.parent)
+ descriptor = os.open(
+ output,
+ os.O_WRONLY | os.O_CREAT | os.O_EXCL | _no_follow_flag(),
+ 0o600,
+ )
+ try:
+ stream = os.fdopen(descriptor, "wb", closefd=True)
+ descriptor = -1
+ with stream:
+ stream.write(inspected.payloads[full_name])
+ stream.flush()
+ os.fsync(stream.fileno())
+ os.chmod(output, 0o644, follow_symlinks=False)
+ finally:
+ if descriptor >= 0:
+ os.close(descriptor)
+ for directory in sorted(
+ (item for item in temporary.rglob("*") if item.is_dir()),
+ key=lambda item: len(item.parts),
+ reverse=True,
+ ):
+ os.chmod(directory, 0o755, follow_symlinks=False)
+ os.replace(temporary, target)
+ temporary = None
+ except DistributionContractError:
+ raise
+ except (OSError, ValueError):
+ raise DistributionContractError("output-invalid") from None
+ finally:
+ if temporary is not None:
+ shutil.rmtree(temporary, ignore_errors=True)
+ return inspected.record
+
+
+def _public_inspect_wheel(
+ path: str | os.PathLike[str],
+ allowed_members: Sequence[str],
+ payloads: ProjectPayloads,
+ *,
+ require_canonical: bool,
+) -> _Inspection:
+ try:
+ return _inspect_wheel(path, allowed_members, payloads, require_canonical)
+ except DistributionContractError:
+ raise
+ except (OSError, EOFError, UnicodeError, ValueError, zipfile.BadZipFile):
+ raise DistributionContractError("archive-invalid") from None
+
+
+def _public_inspect_sdist(
+ path: str | os.PathLike[str],
+ allowed_files: Sequence[str],
+ payloads: ProjectPayloads,
+ *,
+ require_canonical: bool,
+) -> _Inspection:
+ try:
+ return _inspect_sdist(path, allowed_files, payloads, require_canonical)
+ except DistributionContractError:
+ raise
+ except (OSError, EOFError, UnicodeError, ValueError, tarfile.TarError):
+ raise DistributionContractError("archive-invalid") from None
+
+
+def _inspect_wheel(
+ path: str | os.PathLike[str],
+ allowed_members: Sequence[str],
+ project_payloads: ProjectPayloads,
+ require_canonical: bool,
+) -> _Inspection:
+ expected = _validated_allowlist(allowed_members, MAX_WHEEL_MEMBERS)
+ required = {
+ f"{DIST_INFO}/METADATA",
+ f"{DIST_INFO}/WHEEL",
+ f"{DIST_INFO}/entry_points.txt",
+ f"{DIST_INFO}/top_level.txt",
+ f"{DIST_INFO}/RECORD",
+ }
+ if not required.issubset(expected):
+ _reject("allowlist-invalid")
+
+ with _open_outer(path) as stream:
+ outer_data, outer_hash = _read_outer(stream)
+ outer_size = len(outer_data)
+ with zipfile.ZipFile(
+ io.BytesIO(outer_data), mode="r", allowZip64=False
+ ) as archive:
+ if archive.comment:
+ _reject("canonical-metadata-invalid")
+ infos = archive.infolist()
+ if len(infos) > MAX_WHEEL_MEMBERS:
+ _reject("member-limit-exceeded")
+ observed = tuple(info.filename for info in infos)
+ _validate_observed_paths(observed)
+ if any(info.orig_filename != info.filename for info in infos):
+ _reject("path-invalid")
+ if observed != expected:
+ _reject("allowlist-invalid")
+
+ advertised_total = 0
+ for info in infos:
+ _validate_zip_info(info, require_canonical)
+ advertised_total += info.file_size
+ if advertised_total > MAX_WHEEL_PAYLOAD:
+ _reject("member-limit-exceeded")
+
+ records: list[MemberRecord] = []
+ contents: dict[str, bytes] = {}
+ measured_total = 0
+ for info in infos:
+ data, digest = _read_zip_member(archive, info)
+ measured_total += len(data)
+ if measured_total > MAX_WHEEL_PAYLOAD:
+ _reject("member-limit-exceeded")
+ contents[info.filename] = data
+ records.append(
+ MemberRecord(
+ name=info.filename,
+ size=len(data),
+ sha256=digest,
+ compressed_size=info.compress_size,
+ mode=info.external_attr >> 16,
+ mtime=_zip_timestamp(info.date_time),
+ )
+ )
+
+ _validate_project_payloads(contents, project_payloads)
+ _validate_record(contents[f"{DIST_INFO}/RECORD"], tuple(records), expected)
+ if require_canonical:
+ canonical = io.BytesIO()
+ _write_canonical_wheel(canonical, expected, contents)
+ _require_canonical_bytes(outer_data, canonical.getvalue())
+ return _Inspection(
+ ArtifactRecord("wheel", outer_size, outer_hash, tuple(records)), contents
+ )
+
+
+def _inspect_sdist(
+ path: str | os.PathLike[str],
+ allowed_files: Sequence[str],
+ project_payloads: ProjectPayloads,
+ require_canonical: bool,
+) -> _Inspection:
+ files = _validated_allowlist(allowed_files, MAX_SDIST_MEMBERS)
+ if "PKG-INFO" not in files:
+ _reject("allowlist-invalid")
+ expected_plan = _sdist_member_plan(files)
+ with _open_outer(path) as stream:
+ outer_data, outer_hash = _read_outer(stream)
+ outer_size = len(outer_data)
+ if require_canonical:
+ _validate_gzip_header(io.BytesIO(outer_data))
+ tar_data = _decompress_sdist(outer_data)
+ with tarfile.open(fileobj=io.BytesIO(tar_data), mode="r:") as archive:
+ if archive.pax_headers:
+ _reject("canonical-metadata-invalid")
+ records: list[MemberRecord] = []
+ contents: dict[str, bytes] = {}
+ measured_total = 0
+ aliases: set[str] = set()
+ for expected_name, expected_directory in expected_plan:
+ member = archive.next()
+ if member is None:
+ _reject("allowlist-invalid")
+ _validate_observed_path(member.name, aliases)
+ if member.name != expected_name:
+ _reject("allowlist-invalid")
+ _validate_tar_info(member, expected_directory, require_canonical)
+ if expected_directory:
+ records.append(
+ MemberRecord(
+ member.name,
+ 0,
+ hashlib.sha256(b"").hexdigest(),
+ None,
+ member.mode,
+ int(member.mtime),
+ )
+ )
+ continue
+ measured_total += member.size
+ if measured_total > MAX_SDIST_PAYLOAD:
+ _reject("member-limit-exceeded")
+ extracted = archive.extractfile(member)
+ if extracted is None:
+ _reject("member-invalid")
+ data, digest = _read_bounded(extracted, member.size)
+ contents[member.name] = data
+ records.append(
+ MemberRecord(
+ member.name,
+ len(data),
+ digest,
+ None,
+ member.mode,
+ int(member.mtime),
+ )
+ )
+ if archive.next() is not None:
+ if len(expected_plan) >= MAX_SDIST_MEMBERS:
+ _reject("member-limit-exceeded")
+ _reject("allowlist-invalid")
+
+ if contents[f"{SDIST_ROOT}/PKG-INFO"] != project_payloads.metadata:
+ _reject("metadata-invalid")
+ if require_canonical:
+ canonical = io.BytesIO()
+ _write_canonical_sdist(canonical, expected_plan, contents)
+ _require_canonical_bytes(outer_data, canonical.getvalue())
+ return _Inspection(
+ ArtifactRecord("sdist", outer_size, outer_hash, tuple(records)), contents
+ )
+
+
+def _validate_project_payloads(
+ contents: Mapping[str, bytes], payloads: ProjectPayloads
+) -> None:
+ expected = {
+ f"{DIST_INFO}/METADATA": payloads.metadata,
+ f"{DIST_INFO}/WHEEL": payloads.wheel,
+ f"{DIST_INFO}/entry_points.txt": payloads.entry_points,
+ f"{DIST_INFO}/top_level.txt": payloads.top_level,
+ }
+ if any(contents.get(name) != value for name, value in expected.items()):
+ _reject("metadata-invalid")
+
+
+def _validate_record(
+ record_data: bytes,
+ members: tuple[MemberRecord, ...],
+ expected_order: tuple[str, ...],
+) -> None:
+ record_name = f"{DIST_INFO}/RECORD"
+ try:
+ text = record_data.decode("ascii")
+ rows = list(csv.reader(io.StringIO(text, newline=""), strict=True))
+ except (UnicodeError, csv.Error):
+ _reject("record-invalid")
+ if len(rows) != len(members) or any(len(row) != 3 for row in rows):
+ _reject("record-invalid")
+ if tuple(row[0] for row in rows) != expected_order:
+ _reject("record-invalid")
+ by_name = {member.name: member for member in members}
+ canonical_lines: list[str] = []
+ for name, encoded_digest, encoded_size in rows:
+ if name == record_name:
+ if encoded_digest or encoded_size:
+ _reject("record-invalid")
+ canonical_lines.append(f"{name},,\n")
+ continue
+ match = _RECORD_DIGEST.fullmatch(encoded_digest)
+ member = by_name.get(name)
+ if match is None or member is None or not _canonical_decimal(encoded_size):
+ _reject("record-invalid")
+ expected_digest = bytes.fromhex(member.sha256)
+ canonical_digest = (
+ base64.urlsafe_b64encode(expected_digest).rstrip(b"=").decode("ascii")
+ )
+ if (
+ not hmac.compare_digest(match.group(1), canonical_digest)
+ or int(encoded_size) != member.size
+ ):
+ _reject("record-invalid")
+ canonical_lines.append(f"{name},{encoded_digest},{encoded_size}\n")
+ if record_data != "".join(canonical_lines).encode("ascii"):
+ _reject("record-invalid")
+
+
+def _validate_zip_info(info: zipfile.ZipInfo, require_canonical: bool) -> None:
+ mode = info.external_attr >> 16
+ if info.file_size < 0 or info.file_size > MAX_MEMBER_SIZE:
+ _reject("member-limit-exceeded")
+ if (
+ info.is_dir()
+ or not stat.S_ISREG(mode)
+ or info.flag_bits != 0
+ or info.extra
+ or info.comment
+ or info.compress_type not in {zipfile.ZIP_STORED, zipfile.ZIP_DEFLATED}
+ or info.compress_size < 0
+ ):
+ _reject("member-invalid")
+ if info.file_size and (
+ info.compress_size == 0
+ or info.file_size > info.compress_size * MAX_COMPRESSION_RATIO
+ ):
+ _reject("compression-invalid")
+ if require_canonical and (
+ info.create_system != 3
+ or mode != stat.S_IFREG | 0o644
+ or info.date_time != FIXED_ZIP_TIME
+ or info.compress_type != zipfile.ZIP_DEFLATED
+ or info.internal_attr != 0
+ ):
+ _reject("canonical-metadata-invalid")
+
+
+def _validate_tar_info(
+ member: tarfile.TarInfo, expected_directory: bool, require_canonical: bool
+) -> None:
+ if member.size < 0 or member.size > MAX_MEMBER_SIZE:
+ _reject("member-limit-exceeded")
+ _validate_member_pax_mtime(member, require_canonical)
+ if member.sparse is not None or member.linkname:
+ _reject("member-invalid")
+ if expected_directory:
+ if not member.isdir() or member.size != 0:
+ _reject("member-invalid")
+ elif not member.isreg():
+ _reject("member-invalid")
+ if require_canonical and (
+ member.mode != (0o755 if expected_directory else 0o644)
+ or member.uid != 0
+ or member.gid != 0
+ or member.uname != ""
+ or member.gname != ""
+ or member.mtime != FIXED_MTIME
+ ):
+ _reject("canonical-metadata-invalid")
+
+
+def _validate_member_pax_mtime(
+ member: tarfile.TarInfo, require_canonical: bool
+) -> None:
+ headers = member.pax_headers
+ if not headers:
+ return
+ if set(headers) != {"mtime"}:
+ _reject("member-invalid")
+ value = headers["mtime"]
+ if _PAX_MTIME.fullmatch(value) is None:
+ _reject("member-invalid")
+ parsed = decimal.Decimal(value)
+ if parsed > MAX_RAW_MTIME or parsed != decimal.Decimal(str(member.mtime)):
+ _reject("member-invalid")
+ if require_canonical:
+ _reject("canonical-metadata-invalid")
+
+
+def _validated_allowlist(names: Sequence[str], limit: int) -> tuple[str, ...]:
+ if isinstance(names, (str, bytes)):
+ _reject("allowlist-invalid")
+ try:
+ result = tuple(names)
+ except TypeError:
+ _reject("allowlist-invalid")
+ if not result or len(result) > limit:
+ _reject("allowlist-invalid")
+ aliases: set[str] = set()
+ for name in result:
+ if not isinstance(name, str):
+ _reject("allowlist-invalid")
+ _validate_safe_path(name)
+ alias = _path_alias(name)
+ if alias in aliases:
+ _reject("allowlist-invalid")
+ aliases.add(alias)
+ return result
+
+
+def _validate_observed_paths(names: Sequence[str]) -> None:
+ aliases: set[str] = set()
+ for name in names:
+ _validate_observed_path(name, aliases)
+
+
+def _validate_observed_path(name: str, aliases: set[str]) -> None:
+ _validate_safe_path(name)
+ alias = _path_alias(name)
+ if alias in aliases:
+ _reject("path-invalid")
+ aliases.add(alias)
+
+
+def _validate_safe_path(name: str) -> None:
+ if (
+ not name
+ or len(name) > 240
+ or name.startswith("/")
+ or name.endswith("/")
+ or "\\" in name
+ or "\x00" in name
+ ):
+ _reject("path-invalid")
+ try:
+ name.encode("ascii", errors="strict")
+ except UnicodeEncodeError:
+ _reject("path-invalid")
+ components = name.split("/")
+ for component in components:
+ stem = component.split(".", 1)[0].upper()
+ if (
+ not component
+ or component in {".", ".."}
+ or component.endswith(".")
+ or _SAFE_COMPONENT.fullmatch(component) is None
+ or stem in _WINDOWS_RESERVED
+ ):
+ _reject("path-invalid")
+
+
+def _path_alias(name: str) -> str:
+ return "/".join(component.casefold().rstrip(". ") for component in name.split("/"))
+
+
+def _sdist_member_plan(files: tuple[str, ...]) -> tuple[tuple[str, bool], ...]:
+ directories = {SDIST_ROOT}
+ full_files: set[str] = set()
+ for relative in files:
+ full = f"{SDIST_ROOT}/{relative}"
+ full_files.add(full)
+ components = full.split("/")
+ for length in range(1, len(components)):
+ directories.add("/".join(components[:length]))
+ if directories & full_files:
+ _reject("allowlist-invalid")
+ names = directories | full_files
+ if len(names) > MAX_SDIST_MEMBERS:
+ _reject("allowlist-invalid")
+ return tuple((name, name in directories) for name in sorted(names))
+
+
+def _open_outer(path: str | os.PathLike[str]) -> BinaryIO:
+ try:
+ descriptor = os.open(path, os.O_RDONLY | _no_follow_flag())
+ except OSError:
+ raise DistributionContractError("archive-invalid") from None
+ try:
+ metadata = os.fstat(descriptor)
+ if not stat.S_ISREG(metadata.st_mode):
+ _reject("archive-invalid")
+ if metadata.st_size > MAX_OUTER_SIZE:
+ _reject("archive-too-large")
+ return os.fdopen(descriptor, "rb", closefd=True)
+ except BaseException:
+ os.close(descriptor)
+ raise
+
+
+def _read_outer(stream: BinaryIO) -> tuple[bytes, str]:
+ digest = hashlib.sha256()
+ output = io.BytesIO()
+ size = 0
+ stream.seek(0)
+ while chunk := stream.read(_CHUNK_SIZE):
+ size += len(chunk)
+ if size > MAX_OUTER_SIZE:
+ _reject("archive-too-large")
+ digest.update(chunk)
+ output.write(chunk)
+ return output.getvalue(), digest.hexdigest()
+
+
+def _decompress_sdist(data: bytes) -> bytes:
+ decompressor = zlib.decompressobj(wbits=16 + zlib.MAX_WBITS)
+ try:
+ payload = decompressor.decompress(data, MAX_SDIST_STREAM + 1)
+ if len(payload) > MAX_SDIST_STREAM or decompressor.unconsumed_tail:
+ _reject("member-limit-exceeded")
+ payload += decompressor.flush(MAX_SDIST_STREAM - len(payload) + 1)
+ except zlib.error:
+ raise DistributionContractError("archive-invalid") from None
+ if len(payload) > MAX_SDIST_STREAM:
+ _reject("member-limit-exceeded")
+ if not decompressor.eof or decompressor.unused_data:
+ _reject("archive-invalid")
+ return payload
+
+
+def _read_zip_member(
+ archive: zipfile.ZipFile, info: zipfile.ZipInfo
+) -> tuple[bytes, str]:
+ try:
+ with archive.open(info, mode="r") as stream:
+ return _read_bounded(stream, info.file_size)
+ except (OSError, EOFError, RuntimeError, zipfile.BadZipFile):
+ raise DistributionContractError("archive-invalid") from None
+
+
+def _read_bounded(stream: IO[bytes], expected_size: int) -> tuple[bytes, str]:
+ digest = hashlib.sha256()
+ output = io.BytesIO()
+ size = 0
+ while chunk := stream.read(_CHUNK_SIZE):
+ size += len(chunk)
+ if size > MAX_MEMBER_SIZE or size > expected_size:
+ _reject("member-limit-exceeded")
+ digest.update(chunk)
+ output.write(chunk)
+ if size != expected_size:
+ _reject("member-invalid")
+ return output.getvalue(), digest.hexdigest()
+
+
+def _validate_gzip_header(stream: BinaryIO) -> None:
+ stream.seek(0)
+ header = stream.read(10)
+ if (
+ len(header) != 10
+ or header[:3] != b"\x1f\x8b\x08"
+ or header[3] != 0
+ or int.from_bytes(header[4:8], "little") != FIXED_MTIME
+ or header[8] != 2
+ or header[9] != 255
+ ):
+ _reject("canonical-metadata-invalid")
+
+
+def _zip_timestamp(value: tuple[int, int, int, int, int, int]) -> int:
+ if value == FIXED_ZIP_TIME:
+ return FIXED_MTIME
+ # A stable sentinel is enough for noncanonical inspection records.
+ return 0
+
+
+def _canonical_decimal(value: str) -> bool:
+ return (
+ bool(value)
+ and value.isascii()
+ and value.isdecimal()
+ and (value == "0" or not value.startswith("0"))
+ )
+
+
+def _write_canonical_wheel(
+ stream: BinaryIO,
+ members: Sequence[str],
+ payloads: Mapping[str, bytes],
+) -> None:
+ with zipfile.ZipFile(
+ stream,
+ mode="w",
+ compression=zipfile.ZIP_DEFLATED,
+ compresslevel=9,
+ strict_timestamps=True,
+ ) as archive:
+ for name in members:
+ info = zipfile.ZipInfo(name, date_time=FIXED_ZIP_TIME)
+ info.compress_type = zipfile.ZIP_DEFLATED
+ info.create_system = 3
+ info.external_attr = (stat.S_IFREG | 0o644) << 16
+ info.internal_attr = 0
+ info.extra = b""
+ info.comment = b""
+ archive.writestr(info, payloads[name], compresslevel=9)
+
+
+def _write_canonical_sdist(
+ stream: BinaryIO,
+ members: Sequence[tuple[str, bool]],
+ payloads: Mapping[str, bytes],
+) -> None:
+ with (
+ gzip.GzipFile(
+ filename="",
+ mode="wb",
+ fileobj=stream,
+ compresslevel=9,
+ mtime=FIXED_MTIME,
+ ) as compressed,
+ tarfile.open(
+ fileobj=compressed, mode="w", format=tarfile.USTAR_FORMAT
+ ) as archive,
+ ):
+ for full_name, is_directory in members:
+ info = tarfile.TarInfo(full_name)
+ info.uid = 0
+ info.gid = 0
+ info.uname = ""
+ info.gname = ""
+ info.mtime = FIXED_MTIME
+ if is_directory:
+ info.type = tarfile.DIRTYPE
+ info.mode = 0o755
+ info.size = 0
+ archive.addfile(info)
+ else:
+ data = payloads[full_name]
+ info.type = tarfile.REGTYPE
+ info.mode = 0o644
+ info.size = len(data)
+ archive.addfile(info, io.BytesIO(data))
+
+
+def _require_canonical_bytes(observed: bytes, expected: bytes) -> None:
+ if not hmac.compare_digest(observed, expected):
+ _reject("canonical-metadata-invalid")
+
+
+def _atomic_output(path: Path, writer: Callable[[BinaryIO], None]) -> None:
+ path.parent.mkdir(parents=True, exist_ok=True)
+ descriptor, temporary_name = tempfile.mkstemp(
+ prefix=f".{path.name}.", suffix=".tmp", dir=path.parent
+ )
+ temporary = Path(temporary_name)
+ try:
+ with os.fdopen(descriptor, "w+b", closefd=True) as stream:
+ writer(stream)
+ stream.flush()
+ os.fsync(stream.fileno())
+ os.chmod(temporary, 0o644, follow_symlinks=False)
+ if temporary.stat(follow_symlinks=False).st_size > MAX_OUTER_SIZE:
+ _reject("archive-too-large")
+ os.replace(temporary, path)
+ except DistributionContractError:
+ raise
+ except (OSError, ValueError, zipfile.BadZipFile, tarfile.TarError):
+ raise DistributionContractError("output-invalid") from None
+ finally:
+ with suppress(OSError):
+ temporary.unlink(missing_ok=True)
+
+
+def _require_absent(path: Path) -> None:
+ try:
+ path.lstat()
+ except FileNotFoundError:
+ return
+ except OSError:
+ _reject("output-invalid")
+ _reject("output-invalid")
+
+
+def _verify_materialized_parent(root: Path, parent: Path) -> None:
+ current = root
+ for component in parent.relative_to(root).parts:
+ current /= component
+ metadata = current.lstat()
+ if not stat.S_ISDIR(metadata.st_mode):
+ _reject("output-invalid")
+
+
+def _no_follow_flag() -> int:
+ return getattr(os, "O_NOFOLLOW", 0)
+
+
+def _reject(code: ErrorCode) -> NoReturn:
+ raise DistributionContractError(code)
diff --git a/scripts/evidence_rendering.py b/scripts/evidence_rendering.py
index 9963144..567148d 100644
--- a/scripts/evidence_rendering.py
+++ b/scripts/evidence_rendering.py
@@ -342,7 +342,11 @@ def write_setup_svg(path: Path) -> None:
"Install project",
("pip install -e '.[dev]'", "pinned top-level dev tools"),
),
- ("03", "Run gates", ("make check", "lint · types · tests")),
+ (
+ "03",
+ "Run gates",
+ ("make check", "lint · types · tests", "distribution · evidence"),
+ ),
)
nodes: list[str] = []
arrows: list[str] = []
@@ -419,6 +423,140 @@ def write_setup_svg(path: Path) -> None:
)
+def write_distribution_svg(
+ path: Path,
+ *,
+ input_sha256: str,
+ wheel_sha256: str,
+ sdist_sha256: str,
+) -> None:
+ """Render the measured Git-input-to-installed-wheel attestation flow."""
+
+ digests = (input_sha256, wheel_sha256, sdist_sha256)
+ if any(
+ len(digest) != 64
+ or any(character not in "0123456789abcdef" for character in digest)
+ for digest in digests
+ ):
+ raise ValueError("distribution evidence requires lowercase SHA-256 values")
+
+ nodes = (
+ _svg_node(
+ x=55,
+ y=205,
+ width=310,
+ height=162,
+ index="1",
+ title="Immutable inputs",
+ details=(
+ "15 stage-zero Git blobs",
+ f"sha256 {input_sha256[:16]}…",
+ "normalized modes + mtime",
+ ),
+ ),
+ _svg_node(
+ x=430,
+ y=205,
+ width=310,
+ height=162,
+ index="2",
+ title="Build A + B",
+ details=(
+ "setuptools 83.0.0",
+ "fixed epoch · umask 0022",
+ "package index disabled",
+ ),
+ ),
+ _svg_node(
+ x=805,
+ y=145,
+ width=350,
+ height=162,
+ index="3W",
+ title="Canonical wheel",
+ details=(
+ "17 exact members · 0644",
+ f"sha256 {wheel_sha256[:16]}…",
+ "two raw wheels byte-equal",
+ ),
+ accent=GOLD,
+ ),
+ _svg_node(
+ x=805,
+ y=380,
+ width=350,
+ height=162,
+ index="3S",
+ title="Canonical sdist",
+ details=(
+ "23 files + 6 directories",
+ f"sha256 {sdist_sha256[:16]}…",
+ "raw sdist equality unclaimed",
+ ),
+ accent=GOLD,
+ ),
+ _svg_node(
+ x=1220,
+ y=265,
+ width=310,
+ height=162,
+ index="4",
+ title="Sdist rebuild",
+ details=(
+ "safe manual materialization",
+ "canonical wheel byte-equal",
+ "no extractall",
+ ),
+ ),
+ _svg_node(
+ x=1595,
+ y=265,
+ width=310,
+ height=162,
+ index="5",
+ title="Installed smoke",
+ details=(
+ "offline pip --target",
+ "external cwd + exact origin",
+ "inspect only · no sample",
+ ),
+ ),
+ )
+ arrows = f"""
+
+
+
+
+
+
+
+
+"""
+ body = f""" The release archive is measured, normalized, rebuilt, then installed
+ Every value comes from the real project-specific attestation; raw backend artifacts are never presented as canonical releases.
+{"".join(nodes)}
+{arrows}
+
+ Claim boundary
+ No license, signature, dependency-integrity, cross-platform, or arbitrary-archive guarantee.
+ The smoke uses current pinned checker dependencies; it is not a fresh dependency environment.
+ official: false"""
+ path.write_text(
+ _svg_document(
+ title="Reproducible distribution attestation flow",
+ description=(
+ "Fifteen immutable Git inputs feed two builds, canonical wheel "
+ "and source archives, a source-archive rebuild, and an installed "
+ "deterministic inspection smoke test."
+ ),
+ width=1960,
+ height=830,
+ body=body,
+ ),
+ encoding="utf-8",
+ )
+
+
def _wrapped_lines(transcript: str, width: int) -> list[str]:
lines: list[str] = []
for line in transcript.rstrip("\n").splitlines():
diff --git a/scripts/generate_evidence.py b/scripts/generate_evidence.py
index 83a24e7..46d377a 100644
--- a/scripts/generate_evidence.py
+++ b/scripts/generate_evidence.py
@@ -16,7 +16,7 @@
import threading
import tomllib
import xml.etree.ElementTree as ElementTree
-from collections.abc import Callable, Iterator, Sequence
+from collections.abc import Callable, Iterator, Mapping, Sequence
from contextlib import contextmanager
from dataclasses import dataclass
from io import StringIO
@@ -25,6 +25,7 @@
from unittest.mock import patch
from urllib.parse import urlsplit
+import attest_distribution as distribution_attester
from evidence_rendering import (
GOLD,
LINE,
@@ -35,6 +36,7 @@
recompress_png,
render_terminal_png,
write_architecture_svg,
+ write_distribution_svg,
write_gif,
write_sampling_svg,
write_setup_svg,
@@ -70,12 +72,16 @@
"PYTHONPATH=src python -m pytest --cov=password_policy_lab "
"--cov-branch --cov-report=term-missing -q"
),
+ "python scripts/attest_distribution.py",
"python -m pip check",
)
+CHROMIUM_ARGS = ("--num-raster-threads=1",)
OUTPUT_PATHS = (
"docs/assets/architecture.svg",
"docs/assets/cli-inspect.png",
+ "docs/assets/distribution-check.png",
+ "docs/assets/distribution-contract.svg",
"docs/assets/quality-gate.png",
"docs/assets/setup-workflow.svg",
"docs/assets/state-space-sweep.png",
@@ -85,6 +91,8 @@
"docs/assets/web-invalid-length.png",
"docs/assets/web-validation-demo.gif",
"docs/evidence/cli-inspect.txt",
+ "docs/evidence/distribution-attestation.json",
+ "docs/evidence/distribution-check.txt",
"docs/evidence/quality-gate.txt",
"docs/evidence/state-space-sweep.csv",
"docs/evidence/web-validation-reference.png",
@@ -556,7 +564,9 @@ def _verify_setup_contract() -> None:
raise RuntimeError("evidence dependencies are not pinned in the dev extra")
makefile = (ROOT / "Makefile").read_text(encoding="utf-8")
required_make_contract = (
- "check: lint typecheck test dependencies evidence-check",
+ "check: lint typecheck test dependencies distribution-check evidence-check",
+ "distribution-check:",
+ "scripts/attest_distribution.py",
"evidence:",
"scripts/generate_evidence.py",
"evidence-check:",
@@ -671,6 +681,24 @@ def _goto(page: Page, url: str, expected_status: int) -> None:
_assert_no_output(page)
+def _settle_rendering(page: Page) -> None:
+ page.evaluate(
+ """async () => {
+ await document.fonts.ready;
+ for (const animation of document.getAnimations()) {
+ try {
+ animation.finish();
+ } catch {
+ animation.cancel();
+ }
+ }
+ await new Promise((resolve) => {
+ requestAnimationFrame(() => requestAnimationFrame(resolve));
+ });
+ }"""
+ )
+
+
def _capture_full_document(
page: Page,
*,
@@ -678,16 +706,17 @@ def _capture_full_document(
width: int,
viewport_height: int,
) -> None:
+ if page.viewport_size != {"width": width, "height": viewport_height}:
+ raise RuntimeError("full-document capture started from an invalid viewport")
+ _settle_rendering(page)
scroll_height = cast(
int,
page.evaluate("() => Math.ceil(document.documentElement.scrollHeight)"),
)
if not 1 <= scroll_height <= 16_000:
raise RuntimeError("document height is outside the screenshot safety bound")
- page.set_viewport_size({"width": width, "height": scroll_height})
page.evaluate("() => window.scrollTo(0, 0)")
- page.screenshot(path=path, animations="disabled")
- page.set_viewport_size({"width": width, "height": viewport_height})
+ page.screenshot(path=path, animations="disabled", full_page=True)
recompress_png(path)
@@ -696,7 +725,10 @@ def _capture_web_evidence() -> _CaptureResult:
external_requests: list[str] = []
with _guarded_server() as server:
with sync_playwright() as playwright:
- browser = playwright.chromium.launch(headless=True)
+ browser = playwright.chromium.launch(
+ headless=True,
+ args=list(CHROMIUM_ARGS),
+ )
chromium_version = browser.version
desktop = _new_context(browser, width=1440, height=960)
@@ -746,6 +778,7 @@ def _capture_web_evidence() -> _CaptureResult:
"() => window.scrollTo(0, "
"document.querySelector('.workspace').offsetTop - 12)"
)
+ _settle_rendering(page)
page.screenshot(
path=ASSET_DIR / "web-home-mobile.png",
animations="disabled",
@@ -828,9 +861,11 @@ def _capture_web_evidence() -> _CaptureResult:
"() => window.scrollTo(0, "
"document.querySelector('.workspace').offsetTop - 12)"
)
+ _settle_rendering(page)
frames = [page.screenshot(animations="disabled")]
page.locator("#length").fill("7")
page.locator("#length").evaluate("(element) => element.blur()")
+ _settle_rendering(page)
frames.append(page.screenshot(animations="disabled"))
with page.expect_navigation(wait_until="networkidle") as navigation:
page.locator("form").evaluate(
@@ -845,6 +880,7 @@ def _capture_web_evidence() -> _CaptureResult:
"() => window.scrollTo(0, "
"document.querySelector('.workspace').offsetTop - 12)"
)
+ _settle_rendering(page)
frames.append(page.screenshot(animations="disabled"))
fidelity = write_gif(
frames=frames,
@@ -888,6 +924,61 @@ def _capture_web_evidence() -> _CaptureResult:
)
+def _distribution_mapping(value: object, label: str) -> Mapping[str, object]:
+ if type(value) is not dict:
+ raise RuntimeError(f"distribution report field is not an object: {label}")
+ return cast(dict[str, object], value)
+
+
+def _distribution_digest(value: object, label: str) -> str:
+ if type(value) is not str or re.fullmatch(r"[0-9a-f]{64}", value) is None:
+ raise RuntimeError(f"distribution report digest is invalid: {label}")
+ return value
+
+
+def _write_distribution_evidence() -> None:
+ """Run one real attestation and render every derivative from that result."""
+
+ document = distribution_attester.attest(ROOT)
+ artifacts = _distribution_mapping(document.get("artifacts"), "artifacts")
+ wheel = _distribution_mapping(artifacts.get("wheel"), "artifacts.wheel")
+ sdist = _distribution_mapping(artifacts.get("sdist"), "artifacts.sdist")
+ source = _distribution_mapping(document.get("source"), "source")
+ input_sha256 = _distribution_digest(
+ source.get("distribution_input_sha256"),
+ "source.distribution_input_sha256",
+ )
+ wheel_sha256 = _distribution_digest(wheel.get("sha256"), "wheel.sha256")
+ sdist_sha256 = _distribution_digest(sdist.get("sha256"), "sdist.sha256")
+
+ json_text = distribution_attester._canonical_json(document)
+ transcript = (
+ "$ python scripts/attest_distribution.py\n"
+ + distribution_attester._text_report(document)
+ )
+ if _ABSOLUTE_PATH.search(json_text) or _ABSOLUTE_PATH.search(transcript):
+ raise RuntimeError("distribution evidence contains an absolute machine path")
+ (EVIDENCE_DIR / "distribution-attestation.json").write_text(
+ json_text,
+ encoding="utf-8",
+ )
+ (EVIDENCE_DIR / "distribution-check.txt").write_text(
+ transcript,
+ encoding="utf-8",
+ )
+ render_terminal_png(
+ transcript=transcript,
+ title="Distribution attestation · canonical archives verified",
+ path=ASSET_DIR / "distribution-check.png",
+ )
+ write_distribution_svg(
+ ASSET_DIR / "distribution-contract.svg",
+ input_sha256=input_sha256,
+ wheel_sha256=wheel_sha256,
+ sdist_sha256=sdist_sha256,
+ )
+
+
def _normalize_quality_output(output: str) -> str:
normalized = output.replace("\r\n", "\n").replace(str(ROOT), ".")
normalized = _PYTEST_DURATION.sub(r"\1", normalized)
@@ -929,6 +1020,7 @@ def _quality_invocations() -> tuple[tuple[str, ...], ...]:
"--cov-report=term-missing",
"-q",
),
+ (str(PYTHON), "scripts/attest_distribution.py"),
(str(PYTHON), "-m", "pip", "check"),
)
@@ -959,7 +1051,9 @@ def _write_quality_evidence(transcript: str) -> None:
def _source_paths() -> list[Path]:
paths = [
+ ROOT / "MANIFEST.in",
ROOT / "Makefile",
+ ROOT / "PACKAGE.md",
ROOT / "README.md",
ROOT / "app.py",
ROOT / "pyproject.toml",
@@ -1011,6 +1105,14 @@ def _artifact_assertions() -> dict[str, list[str]]:
"rendered from exact deterministic CLI transcript",
"sampled candidate absent",
],
+ "docs/assets/distribution-check.png": [
+ "rendered from the real distribution attestation transcript",
+ "canonical wheel, sdist rebuild, and installed smoke passed",
+ ],
+ "docs/assets/distribution-contract.svg": [
+ "rendered from measured distribution hashes and member counts",
+ "claim boundaries remain explicit",
+ ],
"docs/assets/quality-gate.png": [
"rendered from normalized real gate transcript",
"all commands exited zero",
@@ -1049,6 +1151,14 @@ def _artifact_assertions() -> dict[str, list[str]]:
"real CLI output",
"timestamp and absolute path absent",
],
+ "docs/evidence/distribution-attestation.json": [
+ "canonical path-free report from two real builds",
+ "exact archive inventories and honest claim boundaries",
+ ],
+ "docs/evidence/distribution-check.txt": [
+ "real normalized attestation output",
+ "no password sampled",
+ ],
"docs/evidence/quality-gate.txt": [
"real normalized command output",
"timing and absolute path absent",
@@ -1069,6 +1179,7 @@ def _media_type(path: Path) -> str:
return {
".csv": "text/csv",
".gif": "image/gif",
+ ".json": "application/json",
".png": "image/png",
".svg": "image/svg+xml",
".txt": "text/plain",
@@ -1125,6 +1236,7 @@ def _manifest(
return {
"artifacts": _artifact_manifest(),
"capture": {
+ "chromium_launch_args": list(CHROMIUM_ARGS),
"requests": capture.requests,
"sampler_calls": capture.sampler_calls,
"sampling_guard": "raise-on-call",
@@ -1181,7 +1293,10 @@ def _write_manifest(manifest: dict[str, object]) -> None:
def _provisional_quality_transcript() -> str:
existing = EVIDENCE_DIR / "quality-gate.txt"
if existing.is_file() and (ASSET_DIR / "quality-gate.png").is_file():
- return existing.read_text(encoding="utf-8")
+ transcript = existing.read_text(encoding="utf-8")
+ offsets = [transcript.find(command) for command in QUALITY_COMMANDS]
+ if all(offset >= 0 for offset in offsets) and offsets == sorted(offsets):
+ return transcript
return (
"\n".join(
f"$ {command}\nprovisional evidence graph ready"
@@ -1201,6 +1316,7 @@ def main() -> int:
write_architecture_svg(ASSET_DIR / "architecture.svg")
write_sampling_svg(ASSET_DIR / "uniform-sampling-flow.svg")
write_setup_svg(ASSET_DIR / "setup-workflow.svg")
+ _write_distribution_evidence()
sweep_rows = _write_cli_evidence()
_render_sweep_chart(sweep_rows)
@@ -1234,7 +1350,10 @@ def main() -> int:
raise RuntimeError("normalized quality gate did not reach a fixed point")
if (EVIDENCE_DIR / "quality-gate.txt").read_text(encoding="utf-8") != second_gate:
raise RuntimeError("committed quality transcript differs from verified gate")
- print("Evidence rebuilt and verified: 14 artifacts + canonical manifest.")
+ print(
+ f"Evidence rebuilt and verified: {len(OUTPUT_PATHS)} artifacts + "
+ "canonical manifest."
+ )
return 0
diff --git a/tests/test_distribution_attestation.py b/tests/test_distribution_attestation.py
new file mode 100644
index 0000000..71a8b46
--- /dev/null
+++ b/tests/test_distribution_attestation.py
@@ -0,0 +1,619 @@
+from __future__ import annotations
+
+import hashlib
+import importlib.util
+import io
+import json
+import sys
+from collections.abc import Mapping, Sequence
+from pathlib import Path
+from types import SimpleNamespace
+from typing import Protocol, cast
+
+import pytest
+
+
+class _IndexEntry(Protocol):
+ path: str
+ object_id: str
+
+
+class _SourceState(Protocol):
+ tree: str
+
+
+class _Attester(Protocol):
+ AttestationError: type[ValueError]
+ DISTRIBUTION_INPUTS: tuple[str, ...]
+ PACKAGE_INPUTS: tuple[str, ...]
+ SDIST_FILES: tuple[str, ...]
+ WHEEL_MEMBERS: tuple[str, ...]
+
+ def _parse_index_entries(self, raw: bytes) -> tuple[_IndexEntry, ...]: ...
+
+ def _parse_tree_entries(self, raw: bytes) -> tuple[_IndexEntry, ...]: ...
+
+ def _canonical_input_digest(
+ self,
+ files: Sequence[tuple[str, bytes]],
+ ) -> str: ...
+
+ def _process_environment(
+ self,
+ temporary_root: Path,
+ *,
+ python_path: Path | None = None,
+ ) -> dict[str, str]: ...
+
+ def _prepare_environment_directories(
+ self,
+ environment: Mapping[str, str],
+ ) -> None: ...
+
+ def _validate_work_root(self, repository: Path, raw_path: str) -> Path: ...
+
+ def _repository_argument(self, raw_path: str) -> Path: ...
+
+ def _validate_working_package_inventory(self, repository: Path) -> None: ...
+
+ def _collect_source(self, repository: Path) -> _SourceState: ...
+
+ def _artifact_document(self, record: object) -> dict[str, object]: ...
+
+ def _validate_claim_guards(
+ self,
+ *,
+ raw_wheels_equal: bool,
+ canonical_wheels_equal: bool,
+ canonical_sdists_equal: bool,
+ rebuilt_wheel_equal: bool,
+ smoke_passed: bool,
+ ) -> None: ...
+
+ def _canonical_json(self, document: Mapping[str, object]) -> str: ...
+
+ def run(
+ self,
+ argv: Sequence[str],
+ *,
+ stdout: io.StringIO,
+ stderr: io.StringIO,
+ repository: Path | None = None,
+ ) -> int: ...
+
+
+def _load_attester() -> _Attester:
+ script_directory = Path(__file__).resolve().parents[1] / "scripts"
+ script_path = script_directory / "attest_distribution.py"
+ spec = importlib.util.spec_from_file_location(
+ "portfolio_distribution_attester",
+ script_path,
+ )
+ if spec is None or spec.loader is None:
+ raise RuntimeError("could not load the distribution attester")
+ inserted = str(script_directory)
+ sys.path.insert(0, inserted)
+ try:
+ module = importlib.util.module_from_spec(spec)
+ sys.modules[spec.name] = module
+ spec.loader.exec_module(module)
+ finally:
+ sys.path.remove(inserted)
+ return cast(_Attester, module)
+
+
+attest_distribution = _load_attester()
+
+
+def _index_record(path: str, *, mode: str = "100644", stage: str = "0") -> bytes:
+ return f"{mode} {'a' * 40} {stage}\t{path}".encode("ascii") + b"\0"
+
+
+def _tree_record(path: str, *, object_id: str | None = None) -> bytes:
+ digest = "a" * 40 if object_id is None else object_id
+ return f"100644 blob {digest}\t{path}".encode("ascii") + b"\0"
+
+
+def _prepare_source_fixture(repository: Path) -> dict[str, bytes]:
+ source_root = Path(__file__).resolve().parents[1]
+ (repository / ".git").mkdir()
+ payloads: dict[str, bytes] = {}
+ for relative in attest_distribution.DISTRIBUTION_INPUTS:
+ data = source_root.joinpath(*relative.split("/")).read_bytes()
+ destination = repository.joinpath(*relative.split("/"))
+ destination.parent.mkdir(parents=True, exist_ok=True)
+ destination.write_bytes(data)
+ payloads[relative] = data
+ return payloads
+
+
+def _git_blob_id(data: bytes) -> str:
+ header = f"blob {len(data)}\0".encode("ascii")
+ return hashlib.sha1(header + data, usedforsecurity=False).hexdigest()
+
+
+def test_static_release_contract_has_exact_bounded_inventories() -> None:
+ assert len(attest_distribution.DISTRIBUTION_INPUTS) == 15
+ assert len(attest_distribution.PACKAGE_INPUTS) == 12
+ assert len(attest_distribution.WHEEL_MEMBERS) == 17
+ assert len(attest_distribution.SDIST_FILES) == 23
+ assert set(attest_distribution.PACKAGE_INPUTS).issubset(
+ attest_distribution.DISTRIBUTION_INPUTS
+ )
+ assert len(set(attest_distribution.WHEEL_MEMBERS)) == 17
+ assert len(set(attest_distribution.SDIST_FILES)) == 23
+
+
+def test_index_parser_accepts_only_regular_stage_zero_ascii_entries() -> None:
+ raw = _index_record("MANIFEST.in") + _index_record("PACKAGE.md")
+
+ entries = attest_distribution._parse_index_entries(raw)
+
+ assert [(entry.path, entry.object_id) for entry in entries] == [
+ ("MANIFEST.in", "a" * 40),
+ ("PACKAGE.md", "a" * 40),
+ ]
+
+
+@pytest.mark.parametrize(
+ "raw",
+ [
+ _index_record("MANIFEST.in", mode="100755"),
+ _index_record("MANIFEST.in", stage="2"),
+ _index_record("../MANIFEST.in"),
+ _index_record("MANIFEST.in") + _index_record("MANIFEST.in"),
+ b"100644 not-an-object 0\tMANIFEST.in\0",
+ b"100644 " + (b"a" * 40) + b" 0\tPACKAGE-\xff.md\0",
+ _index_record("MANIFEST.in")[:-1],
+ ],
+)
+def test_index_parser_rejects_ambiguous_or_nonregular_entries(raw: bytes) -> None:
+ with pytest.raises(attest_distribution.AttestationError, match="index-invalid"):
+ attest_distribution._parse_index_entries(raw)
+
+
+def test_source_collection_rejects_index_movement_after_immutable_snapshot(
+ tmp_path: Path,
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ payloads = _prepare_source_fixture(tmp_path)
+ object_ids = {path: _git_blob_id(data) for path, data in payloads.items()}
+ by_object_id = {object_ids[path]: data for path, data in payloads.items()}
+ tree_document = b"".join(
+ _tree_record(path, object_id=object_ids[path])
+ for path in attest_distribution.DISTRIBUTION_INPUTS
+ )
+ source_tree = "1" * 40
+ moved_tree = "2" * 40
+ write_tree_calls = 0
+ cat_file_targets: list[str] = []
+
+ def fake_git(
+ repository: Path,
+ arguments: Sequence[str],
+ *,
+ maximum_output: int = 2 * 1024 * 1024,
+ ) -> bytes:
+ nonlocal write_tree_calls
+ del maximum_output
+ assert repository == tmp_path
+ command = tuple(arguments)
+ if command == ("rev-parse", "--show-toplevel"):
+ return f"{tmp_path}\n".encode()
+ if command == ("write-tree",):
+ write_tree_calls += 1
+ tree = source_tree if write_tree_calls == 1 else moved_tree
+ return f"{tree}\n".encode("ascii")
+ if command[:4] == ("ls-tree", "-r", "-z", source_tree):
+ return tree_document
+ if command[:2] == ("cat-file", "blob"):
+ cat_file_targets.append(command[2])
+ return by_object_id[command[2]]
+ if command[:3] == ("ls-files", "--others", "--exclude-standard"):
+ return b""
+ raise AssertionError(f"unexpected Git command: {command!r}")
+
+ monkeypatch.setattr("portfolio_distribution_attester._git", fake_git)
+
+ with pytest.raises(
+ attest_distribution.AttestationError,
+ match="source-dirty",
+ ):
+ attest_distribution._collect_source(tmp_path)
+
+ assert write_tree_calls == 2
+ assert set(cat_file_targets) == set(by_object_id)
+ assert all(not target.startswith(":") for target in cat_file_targets)
+
+
+def test_source_collection_reads_only_the_immutable_index_tree(
+ tmp_path: Path,
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ payloads = _prepare_source_fixture(tmp_path)
+ object_ids = {path: _git_blob_id(data) for path, data in payloads.items()}
+ by_object_id = {object_ids[path]: data for path, data in payloads.items()}
+ tree_document = b"".join(
+ _tree_record(path, object_id=object_ids[path])
+ for path in attest_distribution.DISTRIBUTION_INPUTS
+ )
+ source_tree = "4" * 40
+ tree_targets: list[str] = []
+
+ def fake_git(
+ repository: Path,
+ arguments: Sequence[str],
+ *,
+ maximum_output: int = 2 * 1024 * 1024,
+ ) -> bytes:
+ del maximum_output
+ assert repository == tmp_path
+ command = tuple(arguments)
+ if command == ("rev-parse", "--show-toplevel"):
+ return f"{tmp_path}\n".encode()
+ if command == ("write-tree",):
+ return f"{source_tree}\n".encode("ascii")
+ if command[:3] == ("ls-tree", "-r", "-z"):
+ tree_targets.append(command[3])
+ if command[3] != source_tree:
+ raise AssertionError("tree lookup used a mutable ref")
+ return tree_document
+ if command[:2] == ("cat-file", "blob"):
+ return by_object_id[command[2]]
+ if command[:3] == ("ls-files", "--others", "--exclude-standard"):
+ return b""
+ raise AssertionError(f"unexpected Git command: {command!r}")
+
+ monkeypatch.setattr("portfolio_distribution_attester._git", fake_git)
+
+ source = attest_distribution._collect_source(tmp_path)
+
+ assert source.tree == source_tree
+ assert tree_targets == [source_tree]
+
+
+def test_input_digest_is_order_independent_but_boundary_sensitive() -> None:
+ files = [
+ (path, f"payload:{path}".encode())
+ for path in attest_distribution.DISTRIBUTION_INPUTS
+ ]
+
+ forward = attest_distribution._canonical_input_digest(files)
+ reversed_order = attest_distribution._canonical_input_digest(list(reversed(files)))
+ changed = list(files)
+ path, payload = changed[4]
+ changed[4] = (path, payload + b"\x00")
+
+ assert forward == reversed_order
+ assert forward != attest_distribution._canonical_input_digest(changed)
+ assert len(forward) == 64
+
+
+def test_input_digest_rejects_missing_duplicate_and_unknown_inputs() -> None:
+ files = [(path, b"x") for path in attest_distribution.DISTRIBUTION_INPUTS]
+ invalid = (
+ files[:-1],
+ [*files[:-1], files[0]],
+ [*files[:-1], ("src/password_policy_lab/extra.py", b"x")],
+ )
+
+ for candidate in invalid:
+ with pytest.raises(
+ attest_distribution.AttestationError,
+ match="index-invalid",
+ ):
+ attest_distribution._canonical_input_digest(candidate)
+
+
+def test_subprocess_environment_is_allowlisted_and_prepares_private_dirs(
+ tmp_path: Path,
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ monkeypatch.setenv("GITHUB_TOKEN", "must-not-survive")
+ monkeypatch.setenv("PYTHONPATH", "/host/source")
+ monkeypatch.setenv("HOME", "/host/home")
+ environment_root = tmp_path / "environment"
+ target = tmp_path / "target"
+
+ environment = attest_distribution._process_environment(
+ environment_root,
+ python_path=target,
+ )
+ attest_distribution._prepare_environment_directories(environment)
+
+ assert "GITHUB_TOKEN" not in environment
+ assert environment["PYTHONPATH"] == str(target)
+ assert environment["PIP_NO_INDEX"] == "1"
+ assert environment["PYTHONHASHSEED"] == "0"
+ assert environment["SOURCE_DATE_EPOCH"].isascii()
+ assert environment["TZ"] == "UTC"
+ assert set(environment) == {
+ "HOME",
+ "LANG",
+ "LC_ALL",
+ "PATH",
+ "PIP_CONFIG_FILE",
+ "PIP_DISABLE_PIP_VERSION_CHECK",
+ "PIP_NO_INDEX",
+ "PYTHONDONTWRITEBYTECODE",
+ "PYTHONHASHSEED",
+ "PYTHONNOUSERSITE",
+ "PYTHONPATH",
+ "SOURCE_DATE_EPOCH",
+ "TMPDIR",
+ "TZ",
+ }
+ assert Path(environment["HOME"]).stat().st_mode & 0o777 == 0o700
+ assert Path(environment["TMPDIR"]).stat().st_mode & 0o777 == 0o700
+
+
+def test_work_root_accepts_repo_local_relative_and_absolute_paths(
+ tmp_path: Path,
+) -> None:
+ relative = attest_distribution._validate_work_root(tmp_path, "relative/work")
+ absolute_path = tmp_path / "absolute" / "work"
+ absolute = attest_distribution._validate_work_root(
+ tmp_path,
+ absolute_path.as_posix(),
+ )
+
+ assert relative == tmp_path / "relative" / "work"
+ assert absolute == absolute_path
+ assert relative.is_dir()
+ assert absolute.is_dir()
+
+
+def test_work_root_rejects_escape_and_existing_symlink(
+ tmp_path: Path,
+) -> None:
+ target = tmp_path / "target"
+ target.mkdir()
+ linked = tmp_path / "linked"
+ linked.symlink_to(target, target_is_directory=True)
+
+ with pytest.raises(
+ attest_distribution.AttestationError,
+ match="work-root-invalid",
+ ):
+ attest_distribution._validate_work_root(
+ tmp_path,
+ (tmp_path.parent / "outside").as_posix(),
+ )
+ with pytest.raises(
+ attest_distribution.AttestationError,
+ match="work-root-invalid",
+ ):
+ attest_distribution._validate_work_root(
+ tmp_path,
+ (linked / "child").as_posix(),
+ )
+
+
+def test_repository_argument_requires_canonical_absolute_nonsymlink_path(
+ tmp_path: Path,
+) -> None:
+ repository = tmp_path / "repository"
+ repository.mkdir()
+ linked = tmp_path / "repository-link"
+ linked.symlink_to(repository, target_is_directory=True)
+
+ assert attest_distribution._repository_argument(repository.as_posix()) == repository
+ with pytest.raises(
+ attest_distribution.AttestationError,
+ match="arguments-invalid",
+ ):
+ attest_distribution._repository_argument("relative/repository")
+ with pytest.raises(
+ attest_distribution.AttestationError,
+ match="arguments-invalid",
+ ):
+ attest_distribution._repository_argument(linked.as_posix())
+
+
+def test_working_package_inventory_allows_only_exact_sources_and_bytecode_cache(
+ tmp_path: Path,
+) -> None:
+ for relative in attest_distribution.PACKAGE_INPUTS:
+ path = tmp_path.joinpath(*relative.split("/"))
+ path.parent.mkdir(parents=True, exist_ok=True)
+ path.write_bytes(b"fixture")
+ cache = tmp_path / "src/password_policy_lab/__pycache__"
+ cache.mkdir()
+ (cache / "cli.cpython-312.pyc").write_bytes(b"cache")
+
+ attest_distribution._validate_working_package_inventory(tmp_path)
+
+ unexpected = tmp_path / "src/password_policy_lab/ignored_extra.py"
+ unexpected.write_bytes(b"unexpected")
+ with pytest.raises(
+ attest_distribution.AttestationError,
+ match="index-invalid",
+ ):
+ attest_distribution._validate_working_package_inventory(tmp_path)
+
+
+def test_artifact_inventory_document_is_path_free_and_exact() -> None:
+ member = SimpleNamespace(
+ name="password_policy_lab/__init__.py",
+ size=7,
+ sha256="a" * 64,
+ compressed_size=9,
+ mode=0o100644,
+ mtime=1_704_067_200,
+ )
+ record = SimpleNamespace(
+ kind="wheel",
+ size=123,
+ sha256="b" * 64,
+ members=(member,),
+ )
+
+ document = attest_distribution._artifact_document(record)
+ encoded = attest_distribution._canonical_json(document)
+
+ assert document == {
+ "inventory": [
+ {
+ "compressed_size": 9,
+ "mode": "0644",
+ "mtime": 1_704_067_200,
+ "name": "password_policy_lab/__init__.py",
+ "sha256": "a" * 64,
+ "size": 7,
+ }
+ ],
+ "member_count": 1,
+ "sha256": "b" * 64,
+ "size": 123,
+ }
+ assert encoded.endswith("\n")
+ assert "/home/" not in encoded
+ assert list(json.loads(encoded)) == [
+ "inventory",
+ "member_count",
+ "sha256",
+ "size",
+ ]
+
+
+@pytest.mark.parametrize(
+ (
+ "raw_wheels_equal",
+ "canonical_wheels_equal",
+ "canonical_sdists_equal",
+ "rebuilt_wheel_equal",
+ "smoke_passed",
+ "failure_code",
+ ),
+ [
+ (False, True, True, True, True, "artifact-mismatch"),
+ (True, False, True, True, True, "artifact-mismatch"),
+ (True, True, False, True, True, "artifact-mismatch"),
+ (True, True, True, False, True, "artifact-mismatch"),
+ (True, True, True, True, False, "smoke-failed"),
+ ],
+)
+def test_claim_guards_reject_unproven_positive_report_flags(
+ raw_wheels_equal: bool,
+ canonical_wheels_equal: bool,
+ canonical_sdists_equal: bool,
+ rebuilt_wheel_equal: bool,
+ smoke_passed: bool,
+ failure_code: str,
+) -> None:
+ with pytest.raises(
+ attest_distribution.AttestationError,
+ match=failure_code,
+ ):
+ attest_distribution._validate_claim_guards(
+ raw_wheels_equal=raw_wheels_equal,
+ canonical_wheels_equal=canonical_wheels_equal,
+ canonical_sdists_equal=canonical_sdists_equal,
+ rebuilt_wheel_equal=rebuilt_wheel_equal,
+ smoke_passed=smoke_passed,
+ )
+
+
+def test_json_cli_emits_canonical_unofficial_claim_boundaries(
+ tmp_path: Path,
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ report: dict[str, object] = {
+ "claim_boundaries": {
+ "arbitrary_archive_safety": False,
+ "artifact_signature_verified": False,
+ "cross_platform_reproducibility": False,
+ "dependency_integrity_verified": False,
+ "fresh_dependency_environment": False,
+ "license_declared": False,
+ },
+ "official": False,
+ "schema_version": 1,
+ }
+
+ def fake_attest(repository: Path, *, work_root: str) -> dict[str, object]:
+ assert repository == tmp_path
+ assert work_root == ".evidence-work/distribution"
+ return report
+
+ monkeypatch.setattr(
+ "portfolio_distribution_attester.attest",
+ fake_attest,
+ )
+ stdout = io.StringIO()
+ stderr = io.StringIO()
+
+ status = attest_distribution.run(
+ ["--format", "json"],
+ stdout=stdout,
+ stderr=stderr,
+ repository=tmp_path,
+ )
+
+ assert status == 0
+ assert stderr.getvalue() == ""
+ assert stdout.getvalue() == attest_distribution._canonical_json(report)
+ payload = json.loads(stdout.getvalue())
+ assert payload["official"] is False
+ assert not any(payload["claim_boundaries"].values())
+
+
+@pytest.mark.parametrize(
+ ("arguments", "expected_code"),
+ [
+ (["--format", "yaml"], "arguments-invalid"),
+ (["--format", "json", "--format", "text"], "arguments-invalid"),
+ (["--root", "/tmp/elsewhere"], "arguments-invalid"),
+ (["--root", "/tmp/a", "--root", "/tmp/b"], "arguments-invalid"),
+ ],
+)
+def test_cli_returns_stable_argument_failure_codes(
+ tmp_path: Path,
+ arguments: list[str],
+ expected_code: str,
+) -> None:
+ stdout = io.StringIO()
+ stderr = io.StringIO()
+
+ status = attest_distribution.run(
+ arguments,
+ stdout=stdout,
+ stderr=stderr,
+ repository=tmp_path,
+ )
+
+ assert status == 1
+ assert stdout.getvalue() == ""
+ assert stderr.getvalue() == (
+ f"error: distribution attestation rejected: {expected_code}\n"
+ )
+
+
+def test_cli_maps_attestation_failure_without_leaking_values(
+ tmp_path: Path,
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ def failed_attest(repository: Path, *, work_root: str) -> dict[str, object]:
+ del repository, work_root
+ raise attest_distribution.AttestationError("build-failed")
+
+ monkeypatch.setattr(
+ "portfolio_distribution_attester.attest",
+ failed_attest,
+ )
+ stdout = io.StringIO()
+ stderr = io.StringIO()
+
+ status = attest_distribution.run(
+ [],
+ stdout=stdout,
+ stderr=stderr,
+ repository=tmp_path,
+ )
+
+ assert status == 1
+ assert stdout.getvalue() == ""
+ assert stderr.getvalue() == (
+ "error: distribution attestation rejected: build-failed\n"
+ )
+ assert str(tmp_path) not in stderr.getvalue()
diff --git a/tests/test_distribution_contract.py b/tests/test_distribution_contract.py
new file mode 100644
index 0000000..01b971e
--- /dev/null
+++ b/tests/test_distribution_contract.py
@@ -0,0 +1,905 @@
+from __future__ import annotations
+
+import base64
+import gzip
+import hashlib
+import importlib.util
+import io
+import stat
+import struct
+import sys
+import tarfile
+import zipfile
+from collections.abc import Callable, Iterator, Mapping, Sequence
+from contextlib import contextmanager
+from functools import partial
+from pathlib import Path
+from types import ModuleType
+from typing import Any, Protocol, cast
+
+import pytest
+
+
+class _ContractError(Protocol):
+ code: str
+
+
+def _load_contract() -> ModuleType:
+ path = Path(__file__).resolve().parents[1] / "scripts/distribution_contract.py"
+ spec = importlib.util.spec_from_file_location(
+ "distribution_contract_for_tests", path
+ )
+ if spec is None or spec.loader is None:
+ raise RuntimeError("could not load distribution contract")
+ module = importlib.util.module_from_spec(spec)
+ sys.modules[spec.name] = module
+ spec.loader.exec_module(module)
+ return module
+
+
+contract = cast(Any, _load_contract())
+
+DESCRIPTION = b"# Installable package\n\nA compact package description.\n"
+PAYLOADS = contract.ProjectPayloads(contract.build_expected_metadata(DESCRIPTION))
+WHEEL_FILES = (
+ "password_policy_lab/__init__.py",
+ f"{contract.DIST_INFO}/METADATA",
+ f"{contract.DIST_INFO}/WHEEL",
+ f"{contract.DIST_INFO}/entry_points.txt",
+ f"{contract.DIST_INFO}/top_level.txt",
+ f"{contract.DIST_INFO}/RECORD",
+)
+SDIST_FILES = (
+ "PACKAGE.md",
+ "PKG-INFO",
+ "src/password_policy_lab/__init__.py",
+)
+
+
+def _wheel_payloads(
+ replacements: Mapping[str, bytes] | None = None,
+) -> dict[str, bytes]:
+ result = {
+ "password_policy_lab/__init__.py": b'__version__ = "0.1.0"\n',
+ f"{contract.DIST_INFO}/METADATA": PAYLOADS.metadata,
+ f"{contract.DIST_INFO}/WHEEL": PAYLOADS.wheel,
+ f"{contract.DIST_INFO}/entry_points.txt": PAYLOADS.entry_points,
+ f"{contract.DIST_INFO}/top_level.txt": PAYLOADS.top_level,
+ }
+ if replacements is not None:
+ result.update(replacements)
+ return result
+
+
+def _record(order: Sequence[str], payloads: Mapping[str, bytes]) -> bytes:
+ rows: list[str] = []
+ for name in order:
+ if name == f"{contract.DIST_INFO}/RECORD":
+ rows.append(f"{name},,\n")
+ continue
+ data = payloads[name]
+ digest = base64.urlsafe_b64encode(hashlib.sha256(data).digest()).rstrip(b"=")
+ rows.append(f"{name},sha256={digest.decode('ascii')},{len(data)}\n")
+ return "".join(rows).encode("ascii")
+
+
+def _write_wheel(
+ path: Path,
+ *,
+ order: Sequence[str] = WHEEL_FILES,
+ replacements: Mapping[str, bytes] | None = None,
+ canonical: bool = True,
+ mode: int = stat.S_IFREG | 0o644,
+ compression: int = zipfile.ZIP_DEFLATED,
+ extra: bytes = b"",
+ member_comment: bytes = b"",
+ archive_comment: bytes = b"",
+ record_override: bytes | None = None,
+) -> None:
+ payloads = _wheel_payloads(replacements)
+ payloads[f"{contract.DIST_INFO}/RECORD"] = (
+ _record(order, payloads) if record_override is None else record_override
+ )
+ with zipfile.ZipFile(path, "w", compression=compression) as archive:
+ archive.comment = archive_comment
+ for name in order:
+ info = zipfile.ZipInfo(
+ name,
+ date_time=(
+ contract.FIXED_ZIP_TIME if canonical else (2025, 2, 2, 2, 2, 2)
+ ),
+ )
+ info.create_system = 3
+ info.external_attr = mode << 16
+ info.compress_type = compression
+ info.extra = extra
+ info.comment = member_comment
+ archive.writestr(info, payloads[name], compresslevel=9)
+
+
+def _sdist_plan(files: Sequence[str] = SDIST_FILES) -> tuple[tuple[str, bool], ...]:
+ directories = {contract.SDIST_ROOT}
+ regular = {f"{contract.SDIST_ROOT}/{name}" for name in files}
+ for name in regular:
+ parts = name.split("/")
+ directories.update("/".join(parts[:index]) for index in range(1, len(parts)))
+ return tuple((name, name in directories) for name in sorted(directories | regular))
+
+
+def _sdist_payloads(
+ replacements: Mapping[str, bytes] | None = None,
+) -> dict[str, bytes]:
+ result = {
+ f"{contract.SDIST_ROOT}/PACKAGE.md": DESCRIPTION,
+ f"{contract.SDIST_ROOT}/PKG-INFO": PAYLOADS.metadata,
+ f"{contract.SDIST_ROOT}/src/password_policy_lab/__init__.py": (
+ b'__version__ = "0.1.0"\n'
+ ),
+ }
+ if replacements is not None:
+ result.update(replacements)
+ return result
+
+
+TarMutator = Callable[[tarfile.TarInfo, bool], None]
+
+
+def _write_sdist(
+ path: Path,
+ *,
+ files: Sequence[str] = SDIST_FILES,
+ replacements: Mapping[str, bytes] | None = None,
+ canonical: bool = True,
+ plan: Sequence[tuple[str, bool]] | None = None,
+ mutate: TarMutator | None = None,
+ tar_format: int = tarfile.USTAR_FORMAT,
+ gzip_name: str = "",
+ global_pax: Mapping[str, str] | None = None,
+) -> None:
+ payloads = _sdist_payloads(replacements)
+ selected_plan = _sdist_plan(files) if plan is None else tuple(plan)
+ with (
+ path.open("wb") as output,
+ gzip.GzipFile(
+ filename=gzip_name,
+ fileobj=output,
+ mode="wb",
+ compresslevel=9,
+ mtime=contract.FIXED_MTIME if canonical else 1_700_000_000,
+ ) as compressed,
+ tarfile.open(
+ fileobj=compressed,
+ mode="w",
+ format=tar_format,
+ pax_headers={} if global_pax is None else dict(global_pax),
+ ) as archive,
+ ):
+ for name, is_directory in selected_plan:
+ info = tarfile.TarInfo(name)
+ info.type = tarfile.DIRTYPE if is_directory else tarfile.REGTYPE
+ info.mode = (0o755 if is_directory else 0o644) if canonical else 0o600
+ info.uid = 0 if canonical else 1000
+ info.gid = 0 if canonical else 1000
+ info.uname = "" if canonical else "builder"
+ info.gname = "" if canonical else "builder"
+ info.mtime = contract.FIXED_MTIME if canonical else 1_700_000_000
+ data = b"" if is_directory else payloads[name]
+ info.size = len(data)
+ if mutate is not None:
+ mutate(info, is_directory)
+ archive.addfile(info, None if info.isdir() else io.BytesIO(data))
+
+
+def _assert_rejected(
+ operation: Callable[[], object], code: str | None = None
+) -> _ContractError:
+ with pytest.raises(contract.DistributionContractError) as captured:
+ operation()
+ error = cast(_ContractError, captured.value)
+ if code is not None:
+ assert error.code == code
+ assert str(error) == f"distribution contract rejected: {error.code}"
+ assert "tmp" not in str(error).lower()
+ return error
+
+
+def _inspect_fixture_wheel(path: Path) -> None:
+ contract.inspect_wheel(path, WHEEL_FILES, PAYLOADS)
+
+
+def _inspect_fixture_sdist_noncanonical(path: Path) -> None:
+ contract.inspect_sdist(path, SDIST_FILES, PAYLOADS, require_canonical=False)
+
+
+@contextmanager
+def _patched_constant(name: str, value: int) -> Iterator[None]:
+ original = getattr(contract, name)
+ setattr(contract, name, value)
+ try:
+ yield
+ finally:
+ setattr(contract, name, original)
+
+
+def test_canonical_wheel_inspection_records_streamed_facts(tmp_path: Path) -> None:
+ wheel = tmp_path / "package.whl"
+ _write_wheel(wheel)
+
+ result = contract.inspect_wheel(wheel, WHEEL_FILES, PAYLOADS)
+
+ assert result.kind == "wheel"
+ assert result.size == wheel.stat().st_size
+ assert result.sha256 == hashlib.sha256(wheel.read_bytes()).hexdigest()
+ assert tuple(member.name for member in result.members) == WHEEL_FILES
+ assert all(member.mode == stat.S_IFREG | 0o644 for member in result.members)
+ assert all(member.mtime == contract.FIXED_MTIME for member in result.members)
+
+
+@pytest.mark.parametrize(
+ "unsafe",
+ (
+ "../escape.py",
+ "/absolute.py",
+ "nested\\windows.py",
+ "nested//empty.py",
+ "nested/./dot.py",
+ "nested/../parent.py",
+ "nul\x00suffix.py",
+ "caf\N{LATIN SMALL LETTER E WITH ACUTE}.py",
+ "NUL.txt",
+ "trailing./file.py",
+ ),
+)
+def test_path_contract_rejects_nonportable_names(tmp_path: Path, unsafe: str) -> None:
+ wheel = tmp_path / "package.whl"
+ _write_wheel(wheel)
+ names = (unsafe, *WHEEL_FILES[1:])
+
+ _assert_rejected(
+ lambda: contract.inspect_wheel(wheel, names, PAYLOADS), "path-invalid"
+ )
+
+
+def test_path_contract_rejects_duplicates_and_case_aliases(tmp_path: Path) -> None:
+ wheel = tmp_path / "package.whl"
+ _write_wheel(wheel)
+ duplicate = (*WHEEL_FILES[:-1], WHEEL_FILES[0])
+ alias = (
+ WHEEL_FILES[0],
+ WHEEL_FILES[0].upper(),
+ *WHEEL_FILES[1:],
+ )
+
+ _assert_rejected(
+ lambda: contract.inspect_wheel(wheel, duplicate, PAYLOADS),
+ "allowlist-invalid",
+ )
+ _assert_rejected(
+ lambda: contract.inspect_wheel(wheel, alias, PAYLOADS), "allowlist-invalid"
+ )
+
+
+def test_zip_nul_name_is_rejected_even_when_zipfile_truncates_it(
+ tmp_path: Path,
+) -> None:
+ wheel = tmp_path / "package.whl"
+ _write_wheel(wheel)
+ data = wheel.read_bytes()
+ original = WHEEL_FILES[0].encode("ascii")
+ corrupted = original.replace(b"_", b"\x00", 1)
+ assert len(corrupted) == len(original)
+ wheel.write_bytes(data.replace(original, corrupted))
+
+ _assert_rejected(lambda: contract.inspect_wheel(wheel, WHEEL_FILES, PAYLOADS))
+
+
+@pytest.mark.parametrize(
+ ("variant", "code"),
+ (
+ ("link", "member-invalid"),
+ ("extra", "member-invalid"),
+ ("member-comment", "member-invalid"),
+ ("archive-comment", "canonical-metadata-invalid"),
+ ("compression", "member-invalid"),
+ ),
+)
+def test_wheel_rejects_links_extras_comments_and_unsupported_compression(
+ tmp_path: Path, variant: str, code: str
+) -> None:
+ wheel = tmp_path / "package.whl"
+ if variant == "link":
+ _write_wheel(wheel, mode=stat.S_IFLNK | 0o777)
+ elif variant == "extra":
+ _write_wheel(wheel, extra=b"\x01\x00\x00\x00")
+ elif variant == "member-comment":
+ _write_wheel(wheel, member_comment=b"comment")
+ elif variant == "archive-comment":
+ _write_wheel(wheel, archive_comment=b"comment")
+ else:
+ _write_wheel(wheel, compression=zipfile.ZIP_BZIP2)
+
+ _assert_rejected(lambda: contract.inspect_wheel(wheel, WHEEL_FILES, PAYLOADS), code)
+
+
+def test_wheel_rejects_encryption_flag_before_reading(tmp_path: Path) -> None:
+ wheel = tmp_path / "package.whl"
+ _write_wheel(wheel)
+ data = bytearray(wheel.read_bytes())
+ local = data.index(b"PK\x03\x04")
+ central = data.index(b"PK\x01\x02")
+ struct.pack_into(
+ " None:
+ wheel = tmp_path / "package.whl"
+ _write_wheel(wheel, replacements={"password_policy_lab/__init__.py": b"aaaa"})
+ with _patched_constant("MAX_MEMBER_SIZE", 3):
+ _assert_rejected(
+ lambda: contract.inspect_wheel(wheel, WHEEL_FILES, PAYLOADS),
+ "member-limit-exceeded",
+ )
+ with _patched_constant("MAX_COMPRESSION_RATIO", 1):
+ _assert_rejected(
+ lambda: contract.inspect_wheel(wheel, WHEEL_FILES, PAYLOADS),
+ "compression-invalid",
+ )
+
+
+def test_outer_file_must_be_small_regular_and_not_a_symlink(tmp_path: Path) -> None:
+ wheel = tmp_path / "package.whl"
+ _write_wheel(wheel)
+ link = tmp_path / "linked.whl"
+ link.symlink_to(wheel.name)
+ directory = tmp_path / "archive-directory"
+ directory.mkdir()
+
+ _assert_rejected(lambda: contract.inspect_wheel(link, WHEEL_FILES, PAYLOADS))
+ _assert_rejected(lambda: contract.inspect_wheel(directory, WHEEL_FILES, PAYLOADS))
+ with _patched_constant("MAX_OUTER_SIZE", wheel.stat().st_size - 1):
+ _assert_rejected(
+ lambda: contract.inspect_wheel(wheel, WHEEL_FILES, PAYLOADS),
+ "archive-too-large",
+ )
+
+
+def test_wheel_requires_exact_member_order(tmp_path: Path) -> None:
+ wheel = tmp_path / "package.whl"
+ changed_order = (WHEEL_FILES[1], WHEEL_FILES[0], *WHEEL_FILES[2:])
+ _write_wheel(wheel, order=changed_order)
+
+ _assert_rejected(
+ lambda: contract.inspect_wheel(wheel, WHEEL_FILES, PAYLOADS),
+ "allowlist-invalid",
+ )
+
+
+@pytest.mark.parametrize(
+ "record",
+ (
+ b"",
+ b"password_policy_lab/__init__.py,sha256=bad,1\n",
+ b"password_policy_lab/__init__.py,,\n",
+ ),
+)
+def test_wheel_rejects_malformed_or_incomplete_record(
+ tmp_path: Path, record: bytes
+) -> None:
+ wheel = tmp_path / "package.whl"
+ _write_wheel(wheel, record_override=record)
+
+ _assert_rejected(
+ lambda: contract.inspect_wheel(wheel, WHEEL_FILES, PAYLOADS), "record-invalid"
+ )
+
+
+def test_wheel_rejects_record_digest_size_and_self_hash_corruption(
+ tmp_path: Path,
+) -> None:
+ good = _wheel_payloads()
+ good_record = _record(WHEEL_FILES, good)
+ lines = good_record.splitlines(keepends=True)
+ first_path, first_digest, first_size = lines[0].removesuffix(b"\n").split(b",")
+ wrong_size = (
+ b",".join((first_path, first_digest, str(int(first_size) + 1).encode("ascii")))
+ + b"\n"
+ + b"".join(lines[1:])
+ )
+ replacement = b"A" if first_digest[-1:] != b"A" else b"B"
+ wrong_digest = (
+ b",".join((first_path, first_digest[:-1] + replacement, first_size))
+ + b"\n"
+ + b"".join(lines[1:])
+ )
+ mutations = (
+ wrong_size,
+ wrong_digest,
+ good_record.replace(b"RECORD,,", b"RECORD,sha256=bad,1"),
+ )
+ for index, mutation in enumerate(mutations):
+ wheel = tmp_path / f"package-{index}.whl"
+ _write_wheel(wheel, record_override=mutation)
+
+ _assert_rejected(
+ partial(_inspect_fixture_wheel, wheel),
+ "record-invalid",
+ )
+
+
+def test_wheel_rejects_noncanonical_base64url_record_digest(tmp_path: Path) -> None:
+ wheel = tmp_path / "package.whl"
+ good_record = _record(WHEEL_FILES, _wheel_payloads())
+ lines = good_record.splitlines(keepends=True)
+ first_path, first_digest, first_size = lines[0].removesuffix(b"\n").split(b",")
+ alphabet = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
+ encoded = bytearray(first_digest.removeprefix(b"sha256="))
+ last_index = alphabet.index(encoded[-1])
+ encoded[-1] = alphabet[(last_index & 0b110000) | ((last_index + 1) & 0b001111)]
+ assert (
+ base64.urlsafe_b64decode(encoded + b"=")
+ == hashlib.sha256(_wheel_payloads()[WHEEL_FILES[0]]).digest()
+ )
+ mutation = (
+ b",".join((first_path, b"sha256=" + encoded, first_size))
+ + b"\n"
+ + b"".join(lines[1:])
+ )
+ _write_wheel(wheel, record_override=mutation)
+
+ _assert_rejected(partial(_inspect_fixture_wheel, wheel), "record-invalid")
+
+
+@pytest.mark.parametrize(
+ "name",
+ (
+ f"{contract.DIST_INFO}/METADATA",
+ f"{contract.DIST_INFO}/WHEEL",
+ f"{contract.DIST_INFO}/entry_points.txt",
+ f"{contract.DIST_INFO}/top_level.txt",
+ ),
+)
+def test_wheel_rejects_project_metadata_corruption(tmp_path: Path, name: str) -> None:
+ wheel = tmp_path / "package.whl"
+ _write_wheel(wheel, replacements={name: b"corrupted\n"})
+
+ _assert_rejected(
+ lambda: contract.inspect_wheel(wheel, WHEEL_FILES, PAYLOADS),
+ "metadata-invalid",
+ )
+
+
+def test_wheel_canonicalization_is_deterministic(tmp_path: Path) -> None:
+ first_raw = tmp_path / "first-raw.whl"
+ second_raw = tmp_path / "second-raw.whl"
+ _write_wheel(first_raw, canonical=False)
+ _write_wheel(second_raw, canonical=False, mode=stat.S_IFREG | 0o600)
+ first = tmp_path / "first.whl"
+ second = tmp_path / "second.whl"
+
+ first_record = contract.canonicalize_wheel(first_raw, first, WHEEL_FILES, PAYLOADS)
+ second_record = contract.canonicalize_wheel(
+ second_raw, second, WHEEL_FILES, PAYLOADS
+ )
+
+ assert first.read_bytes() == second.read_bytes()
+ assert first_record.sha256 == second_record.sha256
+ assert contract.inspect_wheel(first, WHEEL_FILES, PAYLOADS) == first_record
+
+
+@pytest.mark.parametrize("mutation", ("prefix", "suffix", "local-time"))
+def test_canonical_wheel_rejects_bytes_outside_its_exact_container_profile(
+ tmp_path: Path, mutation: str
+) -> None:
+ wheel = tmp_path / "package.whl"
+ _write_wheel(wheel)
+ data = bytearray(wheel.read_bytes())
+ if mutation == "prefix":
+ data[:0] = b"self-extracting-prefix"
+ elif mutation == "suffix":
+ data.extend(b"trailing-junk")
+ else:
+ local = data.index(b"PK\x03\x04")
+ struct.pack_into(" None:
+ raw = tmp_path / "raw.tar.gz"
+ canonical = tmp_path / "canonical.tar.gz"
+ _write_sdist(raw, canonical=False, gzip_name="raw-name")
+ result = contract.canonicalize_sdist(raw, canonical, SDIST_FILES, PAYLOADS)
+
+ assert result.kind == "sdist"
+ assert tuple(member.name for member in result.members) == tuple(
+ name for name, _ in _sdist_plan()
+ )
+ with tarfile.open(canonical, "r:gz") as archive:
+ members = archive.getmembers()
+ assert all(member.uid == member.gid == 0 for member in members)
+ assert all(member.uname == member.gname == "" for member in members)
+ assert all(member.mtime == contract.FIXED_MTIME for member in members)
+ assert all(
+ member.mode == (0o755 if member.isdir() else 0o644) for member in members
+ )
+
+
+@pytest.mark.parametrize(
+ ("field", "value"),
+ (
+ ("mode", 0o600),
+ ("uid", 1),
+ ("gid", 1),
+ ("uname", "builder"),
+ ("gname", "builder"),
+ ("mtime", contract.FIXED_MTIME + 1),
+ ),
+)
+def test_sdist_rejects_noncanonical_member_metadata(
+ tmp_path: Path, field: str, value: object
+) -> None:
+ archive = tmp_path / "package.tar.gz"
+
+ def mutate(info: tarfile.TarInfo, is_directory: bool) -> None:
+ if not is_directory and info.name.endswith("PACKAGE.md"):
+ setattr(info, field, value)
+
+ _write_sdist(archive, mutate=mutate)
+
+ _assert_rejected(
+ lambda: contract.inspect_sdist(archive, SDIST_FILES, PAYLOADS),
+ "canonical-metadata-invalid",
+ )
+
+
+def test_sdist_requires_sorted_exact_rooted_plan(tmp_path: Path) -> None:
+ archive = tmp_path / "package.tar.gz"
+ reversed_plan = tuple(reversed(_sdist_plan()))
+ _write_sdist(archive, plan=reversed_plan)
+
+ _assert_rejected(
+ lambda: contract.inspect_sdist(archive, SDIST_FILES, PAYLOADS),
+ "allowlist-invalid",
+ )
+
+
+@pytest.mark.parametrize("member_type", (tarfile.SYMTYPE, tarfile.CHRTYPE))
+def test_sdist_rejects_links_and_devices(tmp_path: Path, member_type: bytes) -> None:
+ archive = tmp_path / "package.tar.gz"
+
+ def mutate(info: tarfile.TarInfo, is_directory: bool) -> None:
+ if not is_directory and info.name.endswith("__init__.py"):
+ info.type = member_type
+ if member_type == tarfile.SYMTYPE:
+ info.linkname = "../../escape"
+ info.size = 0
+ else:
+ info.devmajor = 1
+ info.devminor = 3
+ info.size = 0
+
+ _write_sdist(archive, mutate=mutate)
+
+ _assert_rejected(
+ lambda: contract.inspect_sdist(archive, SDIST_FILES, PAYLOADS),
+ "member-invalid",
+ )
+
+
+def test_sdist_rejects_pax_path_overrides(tmp_path: Path) -> None:
+ archive = tmp_path / "package.tar.gz"
+
+ def mutate(info: tarfile.TarInfo, is_directory: bool) -> None:
+ if not is_directory and info.name.endswith("PACKAGE.md"):
+ info.pax_headers = {"path": info.name}
+
+ _write_sdist(archive, mutate=mutate, tar_format=tarfile.PAX_FORMAT)
+
+ _assert_rejected(
+ lambda: contract.inspect_sdist(archive, SDIST_FILES, PAYLOADS),
+ "member-invalid",
+ )
+
+
+def test_raw_sdist_accepts_backend_pax_mtime_then_canonicalizes(
+ tmp_path: Path,
+) -> None:
+ raw = tmp_path / "raw.tar.gz"
+ canonical = tmp_path / "canonical.tar.gz"
+
+ def add_backend_mtime(info: tarfile.TarInfo, is_directory: bool) -> None:
+ value = "1700000000.0" if is_directory else "1700000000.125"
+ info.pax_headers = {"mtime": value}
+
+ _write_sdist(
+ raw,
+ canonical=False,
+ mutate=add_backend_mtime,
+ tar_format=tarfile.PAX_FORMAT,
+ )
+
+ raw_result = contract.inspect_sdist(
+ raw, SDIST_FILES, PAYLOADS, require_canonical=False
+ )
+ canonical_result = contract.canonicalize_sdist(
+ raw, canonical, SDIST_FILES, PAYLOADS
+ )
+
+ assert raw_result.kind == canonical_result.kind == "sdist"
+ assert raw_result.sha256 != canonical_result.sha256
+ with tarfile.open(canonical, "r:gz") as accepted:
+ assert all(not member.pax_headers for member in accepted)
+ assert contract.inspect_sdist(canonical, SDIST_FILES, PAYLOADS) == canonical_result
+
+
+def test_canonical_sdist_still_rejects_an_otherwise_valid_pax_mtime(
+ tmp_path: Path,
+) -> None:
+ archive = tmp_path / "package.tar.gz"
+
+ def add_mtime(info: tarfile.TarInfo, is_directory: bool) -> None:
+ del is_directory
+ info.pax_headers = {"mtime": f"{contract.FIXED_MTIME}.0"}
+
+ _write_sdist(archive, mutate=add_mtime, tar_format=tarfile.PAX_FORMAT)
+
+ contract.inspect_sdist(archive, SDIST_FILES, PAYLOADS, require_canonical=False)
+ _assert_rejected(
+ lambda: contract.inspect_sdist(archive, SDIST_FILES, PAYLOADS),
+ "canonical-metadata-invalid",
+ )
+
+
+@pytest.mark.parametrize(
+ "value",
+ (
+ "01700000000.0",
+ "+1700000000.0",
+ "1700000000.00",
+ "1700000000.",
+ "1700000000e0",
+ "NaN",
+ "Infinity",
+ "-1",
+ "4102444801",
+ "1700000000.123456789",
+ ),
+)
+def test_raw_sdist_rejects_noncanonical_or_unbounded_pax_mtime(
+ tmp_path: Path, value: str
+) -> None:
+ archive = tmp_path / "package.tar.gz"
+
+ def add_mtime(info: tarfile.TarInfo, is_directory: bool) -> None:
+ if not is_directory and info.name.endswith("PACKAGE.md"):
+ info.pax_headers = {"mtime": value}
+
+ _write_sdist(archive, mutate=add_mtime, tar_format=tarfile.PAX_FORMAT)
+
+ _assert_rejected(
+ lambda: contract.inspect_sdist(
+ archive, SDIST_FILES, PAYLOADS, require_canonical=False
+ )
+ )
+
+
+def test_raw_sdist_rejects_extra_and_global_pax_keys(tmp_path: Path) -> None:
+ member_archive = tmp_path / "member.tar.gz"
+ global_archive = tmp_path / "global.tar.gz"
+
+ def add_extra_key(info: tarfile.TarInfo, is_directory: bool) -> None:
+ if not is_directory and info.name.endswith("PACKAGE.md"):
+ info.pax_headers = {
+ "mtime": "1700000000.0",
+ "atime": "1700000000.0",
+ }
+
+ _write_sdist(member_archive, mutate=add_extra_key, tar_format=tarfile.PAX_FORMAT)
+ _write_sdist(
+ global_archive,
+ tar_format=tarfile.PAX_FORMAT,
+ global_pax={"comment": "unexpected"},
+ )
+
+ for archive in (member_archive, global_archive):
+ _assert_rejected(partial(_inspect_fixture_sdist_noncanonical, archive))
+
+
+def test_sdist_rejects_gnu_sparse_metadata(tmp_path: Path) -> None:
+ archive = tmp_path / "package.tar.gz"
+
+ def mutate(info: tarfile.TarInfo, is_directory: bool) -> None:
+ if not is_directory and info.name.endswith("PACKAGE.md"):
+ info.pax_headers = {
+ "GNU.sparse.map": f"0,{info.size}",
+ "GNU.sparse.size": str(info.size),
+ }
+
+ _write_sdist(archive, mutate=mutate, tar_format=tarfile.PAX_FORMAT)
+
+ _assert_rejected(
+ lambda: contract.inspect_sdist(archive, SDIST_FILES, PAYLOADS),
+ "member-invalid",
+ )
+
+
+def test_sdist_enforces_member_and_payload_limits_with_tiny_files(
+ tmp_path: Path,
+) -> None:
+ archive = tmp_path / "package.tar.gz"
+ _write_sdist(archive)
+ with _patched_constant("MAX_MEMBER_SIZE", len(PAYLOADS.metadata) - 1):
+ _assert_rejected(
+ lambda: contract.inspect_sdist(archive, SDIST_FILES, PAYLOADS),
+ "member-limit-exceeded",
+ )
+ with _patched_constant("MAX_SDIST_PAYLOAD", len(PAYLOADS.metadata)):
+ _assert_rejected(
+ lambda: contract.inspect_sdist(archive, SDIST_FILES, PAYLOADS),
+ "member-limit-exceeded",
+ )
+
+
+def test_sdist_rejects_an_oversized_header_before_advancing_past_its_body(
+ tmp_path: Path,
+) -> None:
+ archive = tmp_path / "oversized-header.tar.gz"
+ root = tarfile.TarInfo(contract.SDIST_ROOT)
+ root.type = tarfile.DIRTYPE
+ root.mode = 0o755
+ root.size = 0
+ oversized = tarfile.TarInfo(f"{contract.SDIST_ROOT}/PACKAGE.md")
+ oversized.type = tarfile.REGTYPE
+ oversized.mode = 0o644
+ oversized.size = contract.MAX_MEMBER_SIZE + 1
+ headers_only = root.tobuf(format=tarfile.USTAR_FORMAT) + oversized.tobuf(
+ format=tarfile.USTAR_FORMAT
+ )
+ archive.write_bytes(gzip.compress(headers_only, mtime=contract.FIXED_MTIME))
+
+ _assert_rejected(
+ lambda: contract.inspect_sdist(
+ archive, SDIST_FILES, PAYLOADS, require_canonical=False
+ ),
+ "member-limit-exceeded",
+ )
+
+
+def test_sdist_rejects_pkg_info_corruption(tmp_path: Path) -> None:
+ archive = tmp_path / "package.tar.gz"
+ _write_sdist(
+ archive,
+ replacements={f"{contract.SDIST_ROOT}/PKG-INFO": b"corrupted\n"},
+ )
+
+ _assert_rejected(
+ lambda: contract.inspect_sdist(archive, SDIST_FILES, PAYLOADS),
+ "metadata-invalid",
+ )
+
+
+def test_sdist_canonicalization_is_deterministic_and_has_no_gzip_name(
+ tmp_path: Path,
+) -> None:
+ first_raw = tmp_path / "first-raw.tar.gz"
+ second_raw = tmp_path / "second-raw.tar.gz"
+ _write_sdist(first_raw, canonical=False, gzip_name="first-source")
+ _write_sdist(second_raw, canonical=False, gzip_name="second-source")
+ first = tmp_path / "first.tar.gz"
+ second = tmp_path / "second.tar.gz"
+
+ first_record = contract.canonicalize_sdist(first_raw, first, SDIST_FILES, PAYLOADS)
+ second_record = contract.canonicalize_sdist(
+ second_raw, second, SDIST_FILES, PAYLOADS
+ )
+
+ assert first.read_bytes() == second.read_bytes()
+ assert first_record.sha256 == second_record.sha256
+ header = first.read_bytes()[:10]
+ assert header[3] == 0
+ assert int.from_bytes(header[4:8], "little") == contract.FIXED_MTIME
+
+
+@pytest.mark.parametrize("mutation", ("crc", "size", "suffix"))
+def test_sdist_rejects_invalid_or_trailing_gzip_container_bytes(
+ tmp_path: Path, mutation: str
+) -> None:
+ archive = tmp_path / "package.tar.gz"
+ _write_sdist(archive)
+ data = bytearray(archive.read_bytes())
+ if mutation == "crc":
+ data[-8] ^= 1
+ elif mutation == "size":
+ data[-1] ^= 1
+ else:
+ data.extend(b"trailing-junk")
+ archive.write_bytes(data)
+
+ _assert_rejected(
+ lambda: contract.inspect_sdist(
+ archive, SDIST_FILES, PAYLOADS, require_canonical=False
+ ),
+ "archive-invalid",
+ )
+
+
+def test_materialize_canonical_sdist_writes_only_prevalidated_regular_files(
+ tmp_path: Path,
+) -> None:
+ raw = tmp_path / "raw.tar.gz"
+ archive = tmp_path / "package.tar.gz"
+ destination = tmp_path / "materialized"
+ _write_sdist(raw, canonical=False)
+ expected = contract.canonicalize_sdist(raw, archive, SDIST_FILES, PAYLOADS)
+
+ result = contract.materialize_canonical_sdist(
+ archive, destination, SDIST_FILES, PAYLOADS
+ )
+
+ assert result == expected
+ assert (destination / "PACKAGE.md").read_bytes() == DESCRIPTION
+ assert (destination / "PKG-INFO").read_bytes() == PAYLOADS.metadata
+ assert (
+ destination / "src/password_policy_lab/__init__.py"
+ ).read_bytes() == b'__version__ = "0.1.0"\n'
+ assert all(
+ stat.S_IMODE(path.stat().st_mode) == (0o755 if path.is_dir() else 0o644)
+ for path in destination.rglob("*")
+ )
+ _assert_rejected(
+ lambda: contract.materialize_canonical_sdist(
+ archive, destination, SDIST_FILES, PAYLOADS
+ ),
+ "output-invalid",
+ )
+
+
+def test_materialize_canonical_sdist_never_replaces_an_empty_destination(
+ tmp_path: Path,
+) -> None:
+ raw = tmp_path / "raw.tar.gz"
+ archive = tmp_path / "package.tar.gz"
+ destination = tmp_path / "reserved"
+ _write_sdist(raw, canonical=False)
+ contract.canonicalize_sdist(raw, archive, SDIST_FILES, PAYLOADS)
+ destination.mkdir()
+
+ _assert_rejected(
+ lambda: contract.materialize_canonical_sdist(
+ archive, destination, SDIST_FILES, PAYLOADS
+ ),
+ "output-invalid",
+ )
+ assert destination.is_dir()
+ assert not tuple(destination.iterdir())
+
+
+def test_expected_metadata_builder_is_bounded_and_rejects_nul() -> None:
+ assert contract.build_expected_metadata(DESCRIPTION) == PAYLOADS.metadata
+ _assert_rejected(
+ lambda: contract.build_expected_metadata(b"bad\x00description"),
+ "metadata-invalid",
+ )
+ with _patched_constant("MAX_MEMBER_SIZE", len(contract.EXPECTED_METADATA_PREFIX)):
+ _assert_rejected(
+ lambda: contract.build_expected_metadata(b"x"), "metadata-invalid"
+ )
diff --git a/tests/test_evidence.py b/tests/test_evidence.py
index 978d882..88cc4dc 100644
--- a/tests/test_evidence.py
+++ b/tests/test_evidence.py
@@ -1,5 +1,6 @@
from __future__ import annotations
+import copy
import importlib.util
import json
import struct
@@ -45,6 +46,19 @@ def _expected_inspection(self, report: StateSpaceInspection) -> str: ...
def _load_json(self, path: Path) -> tuple[dict[str, object], str]: ...
+ def _distribution_input_digest(self, root: Path) -> str: ...
+
+ def _validate_distribution_attestation(
+ self,
+ root: Path,
+ document: dict[str, object],
+ json_text: str,
+ transcript: str,
+ diagram: str,
+ ) -> None: ...
+
+ def _validate_capture(self, value: object) -> dict[str, tuple[int, int]]: ...
+
def _validate_ast_claims(
self,
root: Path,
@@ -253,6 +267,105 @@ def test_manifest_loader_rejects_duplicates_and_noncanonical_json(
check_evidence._load_json(path)
+def test_distribution_attestation_is_source_bound_and_rejects_overclaim() -> None:
+ root = Path(__file__).resolve().parents[1]
+ path = root / "docs/evidence/distribution-attestation.json"
+ document, text = check_evidence._load_json(path)
+ transcript = (root / "docs/evidence/distribution-check.txt").read_text(
+ encoding="utf-8"
+ )
+ diagram = (root / "docs/assets/distribution-contract.svg").read_text(
+ encoding="utf-8"
+ )
+
+ check_evidence._validate_distribution_attestation(
+ root,
+ document,
+ text,
+ transcript,
+ diagram,
+ )
+ source = cast(dict[str, object], document["source"])
+ assert set(source) == {
+ "distribution_input_count",
+ "distribution_input_sha256",
+ "git_index_stage",
+ }
+ assert source["distribution_input_sha256"] == (
+ check_evidence._distribution_input_digest(root)
+ )
+
+ overclaimed = copy.deepcopy(document)
+ boundaries = cast(dict[str, object], overclaimed["claim_boundaries"])
+ boundaries["license_declared"] = True
+ with pytest.raises(
+ check_evidence.EvidenceValidationError,
+ match="claim boundaries",
+ ):
+ check_evidence._validate_distribution_attestation(
+ root,
+ overclaimed,
+ text,
+ transcript,
+ diagram,
+ )
+
+ stale = copy.deepcopy(document)
+ stale_source = cast(dict[str, object], stale["source"])
+ stale_source["distribution_input_sha256"] = "0" * 64
+ with pytest.raises(
+ check_evidence.EvidenceValidationError,
+ match="input digest",
+ ):
+ check_evidence._validate_distribution_attestation(
+ root,
+ stale,
+ text,
+ transcript,
+ diagram,
+ )
+
+
+def test_distribution_attestation_rejects_self_referential_git_fields() -> None:
+ root = Path(__file__).resolve().parents[1]
+ document, text = check_evidence._load_json(
+ root / "docs/evidence/distribution-attestation.json"
+ )
+ transcript = (root / "docs/evidence/distribution-check.txt").read_text(
+ encoding="utf-8"
+ )
+ diagram = (root / "docs/assets/distribution-contract.svg").read_text(
+ encoding="utf-8"
+ )
+ source = cast(dict[str, object], document["source"])
+ source["git_index_tree"] = "a" * 40
+
+ with pytest.raises(
+ check_evidence.EvidenceValidationError,
+ match="unexpected schema",
+ ):
+ check_evidence._validate_distribution_attestation(
+ root,
+ document,
+ text,
+ transcript,
+ diagram,
+ )
+
+
+def test_capture_rejects_unpinned_parallel_rasterization() -> None:
+ root = Path(__file__).resolve().parents[1]
+ document, _ = check_evidence._load_json(root / check_evidence.MANIFEST_PATH)
+ capture = cast(dict[str, object], document["capture"])
+ capture["chromium_launch_args"] = []
+
+ with pytest.raises(
+ check_evidence.EvidenceValidationError,
+ match="one raster thread",
+ ):
+ check_evidence._validate_capture(capture)
+
+
def test_ast_claims_match_the_audited_core() -> None:
root = Path(__file__).resolve().parents[1]
textual = {