From c5fb51d76ffea04393dabbb9c679dc4bb9bccc57 Mon Sep 17 00:00:00 2001 From: Ali Date: Sat, 1 Aug 2026 17:44:13 -0400 Subject: [PATCH 1/2] Build PhaseProbe v0.1.0 --- .gitattributes | 5 + .github/ISSUE_TEMPLATE/bug_report.yml | 35 + .github/ISSUE_TEMPLATE/config.yml | 5 + .github/ISSUE_TEMPLATE/feature_request.yml | 23 + .../good_first_accessibility.yml | 17 + .../ISSUE_TEMPLATE/good_first_pendulum.yml | 17 + .github/ISSUE_TEMPLATE/good_first_schema.yml | 17 + .github/PULL_REQUEST_TEMPLATE.md | 23 + .github/workflows/ci.yml | 56 ++ .github/workflows/release.yml | 30 + .gitignore | 15 + AGENTS.md | 11 + ARCHITECTURE.md | 55 ++ CHANGELOG.md | 12 + CITATION.cff | 17 + CODE_OF_CONDUCT.md | 9 + CONTRIBUTING.md | 36 + LICENSE | 201 ++++++ LIMITATIONS.md | 14 + PRIOR_ART.md | 33 + README.md | 159 +++++ ROADMAP.md | 14 + RUN_LOG.md | 95 +++ SCIENTIFIC_METHODS.md | 59 ++ SECURITY.md | 17 + assets/demo-session.txt | 35 + assets/demo-static.png | Bin 0 -> 64796 bytes assets/demo.gif | Bin 0 -> 106192 bytes assets/social-preview.jpg | Bin 0 -> 103034 bytes examples/README.md | 40 ++ examples/configs/logistic-negative.json | 27 + examples/configs/logistic-scan.json | 27 + examples/configs/lorenz-negative.json | 30 + examples/configs/lorenz-perturb.json | 30 + examples/configs/predator-prey-check.json | 25 + examples/configs/predator-prey-negative.json | 25 + examples/configs/toggle-negative.json | 30 + examples/configs/toggle-perturb.json | 31 + pyproject.toml | 114 ++++ scripts/check_links.py | 58 ++ scripts/generate_demo.py | 180 +++++ scripts/hygiene.py | 135 ++++ src/phaseprobe/__init__.py | 7 + src/phaseprobe/__main__.py | 5 + src/phaseprobe/artifacts.py | 124 ++++ src/phaseprobe/cli.py | 234 +++++++ src/phaseprobe/config.py | 115 ++++ src/phaseprobe/data/__init__.py | 1 + src/phaseprobe/data/examples/__init__.py | 1 + .../data/examples/logistic-negative.json | 27 + .../data/examples/logistic-scan.json | 27 + .../data/examples/lorenz-negative.json | 30 + .../data/examples/lorenz-perturb.json | 30 + .../data/examples/predator-prey-check.json | 25 + .../data/examples/predator-prey-negative.json | 25 + .../data/examples/toggle-negative.json | 30 + .../data/examples/toggle-perturb.json | 31 + src/phaseprobe/engine.py | 641 ++++++++++++++++++ src/phaseprobe/errors.py | 31 + src/phaseprobe/generate.py | 57 ++ src/phaseprobe/models/__init__.py | 36 + src/phaseprobe/models/_common.py | 56 ++ src/phaseprobe/models/logistic.py | 63 ++ src/phaseprobe/models/lorenz.py | 64 ++ src/phaseprobe/models/predator_prey.py | 86 +++ src/phaseprobe/models/toggle.py | 73 ++ src/phaseprobe/replay.py | 183 +++++ src/phaseprobe/reporting.py | 172 +++++ src/phaseprobe/types.py | 68 ++ tests/conftest.py | 15 + .../fixtures/logistic_map-replay.json | 100 +++ .../generated/test_logistic_map_transition.py | 13 + tests/test_artifacts_replay.py | 86 +++ tests/test_cli.py | 165 +++++ tests/test_config.py | 69 ++ tests/test_engine.py | 124 ++++ uv.lock | 522 ++++++++++++++ 77 files changed, 5098 insertions(+) create mode 100644 .gitattributes create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/ISSUE_TEMPLATE/good_first_accessibility.yml create mode 100644 .github/ISSUE_TEMPLATE/good_first_pendulum.yml create mode 100644 .github/ISSUE_TEMPLATE/good_first_schema.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml create mode 100644 .gitignore create mode 100644 AGENTS.md create mode 100644 ARCHITECTURE.md create mode 100644 CHANGELOG.md create mode 100644 CITATION.cff create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 LIMITATIONS.md create mode 100644 PRIOR_ART.md create mode 100644 README.md create mode 100644 ROADMAP.md create mode 100644 RUN_LOG.md create mode 100644 SCIENTIFIC_METHODS.md create mode 100644 SECURITY.md create mode 100644 assets/demo-session.txt create mode 100644 assets/demo-static.png create mode 100644 assets/demo.gif create mode 100644 assets/social-preview.jpg create mode 100644 examples/README.md create mode 100644 examples/configs/logistic-negative.json create mode 100644 examples/configs/logistic-scan.json create mode 100644 examples/configs/lorenz-negative.json create mode 100644 examples/configs/lorenz-perturb.json create mode 100644 examples/configs/predator-prey-check.json create mode 100644 examples/configs/predator-prey-negative.json create mode 100644 examples/configs/toggle-negative.json create mode 100644 examples/configs/toggle-perturb.json create mode 100644 pyproject.toml create mode 100644 scripts/check_links.py create mode 100644 scripts/generate_demo.py create mode 100644 scripts/hygiene.py create mode 100644 src/phaseprobe/__init__.py create mode 100644 src/phaseprobe/__main__.py create mode 100644 src/phaseprobe/artifacts.py create mode 100644 src/phaseprobe/cli.py create mode 100644 src/phaseprobe/config.py create mode 100644 src/phaseprobe/data/__init__.py create mode 100644 src/phaseprobe/data/examples/__init__.py create mode 100644 src/phaseprobe/data/examples/logistic-negative.json create mode 100644 src/phaseprobe/data/examples/logistic-scan.json create mode 100644 src/phaseprobe/data/examples/lorenz-negative.json create mode 100644 src/phaseprobe/data/examples/lorenz-perturb.json create mode 100644 src/phaseprobe/data/examples/predator-prey-check.json create mode 100644 src/phaseprobe/data/examples/predator-prey-negative.json create mode 100644 src/phaseprobe/data/examples/toggle-negative.json create mode 100644 src/phaseprobe/data/examples/toggle-perturb.json create mode 100644 src/phaseprobe/engine.py create mode 100644 src/phaseprobe/errors.py create mode 100644 src/phaseprobe/generate.py create mode 100644 src/phaseprobe/models/__init__.py create mode 100644 src/phaseprobe/models/_common.py create mode 100644 src/phaseprobe/models/logistic.py create mode 100644 src/phaseprobe/models/lorenz.py create mode 100644 src/phaseprobe/models/predator_prey.py create mode 100644 src/phaseprobe/models/toggle.py create mode 100644 src/phaseprobe/replay.py create mode 100644 src/phaseprobe/reporting.py create mode 100644 src/phaseprobe/types.py create mode 100644 tests/conftest.py create mode 100644 tests/generated/fixtures/logistic_map-replay.json create mode 100644 tests/generated/test_logistic_map_transition.py create mode 100644 tests/test_artifacts_replay.py create mode 100644 tests/test_cli.py create mode 100644 tests/test_config.py create mode 100644 tests/test_engine.py create mode 100644 uv.lock diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..d88bc32 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +* text=auto eol=lf + +*.gif binary +*.jpg binary +*.png binary diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..ba1bc18 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,35 @@ +name: Bug report +description: Report a reproducible PhaseProbe defect. +title: "[Bug]: " +labels: ["bug"] +body: + - type: textarea + attributes: + label: Result first + description: What happened, and what should have happened? + validations: + required: true + - type: textarea + attributes: + label: Minimal reproduction + description: Include the command and a minimal non-sensitive configuration. + validations: + required: true + - type: input + attributes: + label: PhaseProbe and Python versions + placeholder: "phaseprobe 0.1.0; Python 3.12.13" + validations: + required: true + - type: dropdown + attributes: + label: Operating system + options: [Windows, Linux, Other] + validations: + required: true + - type: checkboxes + attributes: + label: Safety + options: + - label: I removed credentials, private model data, and personal information. + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..cbc6a16 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Private security report + url: https://github.com/aliengineering-byte/phaseprobe/security/advisories/new + about: Report vulnerabilities without exposing exploit details publicly. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..7d18aeb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,23 @@ +name: Feature request +description: Propose a focused simulation-testing improvement. +title: "[Feature]: " +labels: ["enhancement"] +body: + - type: textarea + attributes: + label: Testing job + description: Which transition, replay, evidence, or regression-testing job is missing? + validations: + required: true + - type: textarea + attributes: + label: Proposed evidence + description: Describe positive cases, negative controls, scientific terminology, and validation. + validations: + required: true + - type: textarea + attributes: + label: Alternatives and prior art + description: Link relevant tools or primary papers and explain the remaining gap. + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/good_first_accessibility.yml b/.github/ISSUE_TEMPLATE/good_first_accessibility.yml new file mode 100644 index 0000000..fdde20b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/good_first_accessibility.yml @@ -0,0 +1,17 @@ +name: "Good first issue: report accessibility" +description: Improve keyboard, contrast, and semantic HTML evidence without external assets. +title: "[Good first issue] Improve offline report accessibility" +labels: ["good first issue", "documentation"] +body: + - type: markdown + attributes: + value: | + Audit the self-contained HTML report for semantic headings, table captions, contrast, print layout, and screen-reader labels. Add tests and before/after screenshots. Keep the report script-free, offline, and free of CDN resources. + - type: checkboxes + attributes: + label: Contributor checklist + options: + - label: I will document the audit method and measured changes. + required: true + - label: I will preserve HTML escaping and offline operation. + required: true diff --git a/.github/ISSUE_TEMPLATE/good_first_pendulum.yml b/.github/ISSUE_TEMPLATE/good_first_pendulum.yml new file mode 100644 index 0000000..e344450 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/good_first_pendulum.yml @@ -0,0 +1,17 @@ +name: "Good first issue: damped pendulum adapter" +description: Add a small deterministic adapter with positive/negative controls. +title: "[Good first issue] Add a damped-pendulum adapter" +labels: ["good first issue", "example"] +body: + - type: markdown + attributes: + value: | + Implement a typed damped-pendulum adapter, an energy-decay invariant, a positive transition/check, a negative control, a primary citation, deterministic tests, replay coverage, and documentation. Do not label numerical damping as physical evidence without a solver-refinement comparison. + - type: checkboxes + attributes: + label: Contributor checklist + options: + - label: I will add configuration, tolerances, invalid-state policy, and bounded trace retention. + required: true + - label: I will add positive, negative-control, replay, and generated-test coverage. + required: true diff --git a/.github/ISSUE_TEMPLATE/good_first_schema.yml b/.github/ISSUE_TEMPLATE/good_first_schema.yml new file mode 100644 index 0000000..bed8681 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/good_first_schema.yml @@ -0,0 +1,17 @@ +name: "Good first issue: JSON Schema fixtures" +description: Publish schema documents and compatibility fixtures for config/replay/report JSON. +title: "[Good first issue] Add JSON Schema validation fixtures" +labels: ["good first issue", "tooling"] +body: + - type: markdown + attributes: + value: | + Add repository-owned JSON Schema documents for configuration, replay, and report version 1.0. Include valid and invalid fixtures, cross-platform tests, migration notes, and no new runtime dependency. + - type: checkboxes + attributes: + label: Contributor checklist + options: + - label: I will preserve existing version and integrity semantics. + required: true + - label: I will add actionable failure diagnostics and tests. + required: true diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..5d4236f --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,23 @@ +## Outcome + +Describe the user-visible testing outcome first. + +## Scientific interpretation + +- What does the evidence establish? +- What does it not establish? +- Which primary sources support model or algorithm claims? + +## Validation + +- [ ] `python -m ruff format --check .` +- [ ] `python -m ruff check .` +- [ ] `python -m mypy src tests` +- [ ] `python -m pytest --cov=phaseprobe` +- [ ] Positive case and negative control +- [ ] Replay and generated pytest execution +- [ ] Package build and clean packed-install smoke +- [ ] `python scripts/check_links.py` +- [ ] `python scripts/hygiene.py` + +List exact commands and measured results. Do not include private paths, credentials, PDFs, or fabricated evidence. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..af7f9c8 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,56 @@ +name: CI + +on: + push: + branches: [main, "release/**"] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + quality-and-tests: + name: ${{ matrix.os }} / Python ${{ matrix.python }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + python: ["3.10", "3.12", "3.14"] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + cache: pip + - run: python -m pip install --upgrade pip + - run: python -m pip install -e ".[dev]" + - run: python -m ruff format --check . + - run: python -m ruff check . + - run: python -m mypy src tests + - run: python -m pytest --cov=phaseprobe --cov-report=term + - run: python -m phaseprobe --version + - run: python -m phaseprobe scan --example logistic-negative + + package-and-hygiene: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: pip + - run: python -m pip install --upgrade pip build + - run: python -m build + - run: python -m venv .packed-smoke + - run: .packed-smoke/bin/python -m pip install dist/*.whl + - run: .packed-smoke/bin/python -m phaseprobe --version + - run: .packed-smoke/bin/python -m phaseprobe scan --example logistic-negative + - run: python scripts/check_links.py + - run: python scripts/hygiene.py + - uses: actions/upload-artifact@v4 + with: + name: phaseprobe-packages + path: dist/* + if-no-files-found: error diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..251df8b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,30 @@ +name: Tagged release validation + +on: + push: + tags: ["v*"] + +permissions: + contents: read + +jobs: + validate-tagged-package: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: pip + - run: python -m pip install --upgrade pip build + - run: python -m build + - run: python -m venv .tag-smoke + - run: .tag-smoke/bin/python -m pip install dist/*.whl + - run: .tag-smoke/bin/python -m phaseprobe --version + - run: .tag-smoke/bin/python -m phaseprobe scan --example logistic + - run: python scripts/hygiene.py + - uses: actions/upload-artifact@v4 + with: + name: phaseprobe-${{ github.ref_name }} + path: dist/* + if-no-files-found: error diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7a7f81e --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +.research_private/ +.venv/ +.tools/ +.cache/ +.phaseprobe/ +dist/ +build/ +*.egg-info/ +__pycache__/ +*.pyc +.coverage +htmlcov/ +.pytest_cache/ +.mypy_cache/ +.ruff_cache/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..8081b14 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,11 @@ +# PhaseProbe contributor instructions + +- Work exclusively inside the PhaseProbe repository root; never inspect or modify neighboring projects or workspaces. +- Use the public author identity `Ali` only: no surname, personal email, workstation paths, credentials, or provider configuration in tracked files. +- Keep scientific language precise: distinguish finite-time trajectory divergence, sensitive dependence, numerical instability, invariant violation, qualitative regime change, bifurcation evidence, stochastic variation, invalid integration, and solver failure. Never claim exact or formal results beyond the implemented evidence. +- Support Windows and Linux on Python 3.10+ with no runtime LLM, API key, GPU, Docker, account, telemetry, or hosted service. +- Keep copyrighted books, PDFs, extracted text, research notes, caches, environments, tools, build output, and run output out of Git. +- Required validation: `python -m ruff format --check .`, `python -m ruff check .`, `python -m mypy src tests`, `python -m pytest`, `python -m build`, packed-install smoke test, quick start, examples, replay, generated-test execution, privacy/secret/large-file scans, and clean Git status. +- Do not fabricate tests, performance, scientific evidence, benchmark results, users, or adoption claims. +- Do not publish to PyPI without separate authorization. +- Release only after all local gates pass; then use the authenticated `aliengineering-byte` GitHub account, pass CI, tag an annotated `v0.1.0`, create the release, and verify a fresh unauthenticated clone. diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..10b9700 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,55 @@ +# Architecture + +PhaseProbe separates simulation semantics from search, evidence, and test materialization. + +```text +JSON config / built-in example + | + v + typed adapter ----> deterministic step + observe + | | + v v + bounded engine ----> finite validation ----> capped trace + SHA-256 + | | + | +---- scan / perturb / check policy + v + outcome ----> run artifacts ----> replay verification ----> fixed pytest template + | + +---- terminal / JSON / self-contained HTML +``` + +## Modules + +- `config.py` validates schema `1.0`, loads packaged examples, and emits canonical JSON. +- `types.py` defines the public adapter protocol, state shape, trace point, and invariant result. +- `models/` contains four independent reference adapters. They are examples, not engine special cases. +- `engine.py` owns bounded execution, NaN/Inf/hard-limit checks, trace retention, scanning, perturbation, bracket refinement, repeatability confirmation, and CI policy evaluation. +- `artifacts.py` creates one finite run directory and hashes each evidence file. +- `replay.py` verifies fixture integrity before re-executing exact model/config/seed/state/parameter inputs and comparing classifications plus retained trace hashes. +- `generate.py` uses a fixed code template and sanitized names. It never evaluates configuration text. +- `reporting.py` renders terminal, JSON, and offline HTML with explicit limitations. +- `cli.py` maps the six public commands to stable exit codes. + +## Adapter design + +The engine deliberately does not require NumPy. A state is a tuple of finite floats; this keeps serialization and perturbation explicit. Downstream adapters may wrap larger simulators, but their `step` boundary must return a bounded tuple suitable for deterministic replay. Observations are scalar mappings and cannot carry arbitrary executable objects. + +An adapter supplies scientific judgment: initial conditions, state advance, observables, qualitative classification, and invariants. The engine supplies operational judgment: search bounds, retention, failure containment, hashes, artifacts, and policy exits. + +## Determinism boundary + +PhaseProbe controls seeds, canonical configuration serialization, fixed command order, search grids, state perturbations, trace retention, and fixture hashing. An external adapter remains responsible for deterministic solver settings, thread behavior, native library versions, and hardware-sensitive arithmetic. Exact replay hashes intentionally expose drift; users may choose a classification-only policy in a future version, but v0.1.0 replay is strict. + +## Artifact safety + +Trace points are capped per series. Run IDs combine UTC time and an evidence digest. Replay fixtures carry a schema version and SHA-256 over every unsigned field. Generated tests copy a validated fixture into `tests/generated/fixtures/` and contain only a sanitized identifier plus a fixed relative path. + +## Extension checklist + +1. Give the adapter a stable `identity` version. +2. Make `initial_state` deterministic for the declared seed. +3. Return the same state dimension after every step. +4. Define classifier thresholds in configuration tolerances. +5. Distinguish mathematical invariants from diagnostic bounds in invariant details. +6. Add positive, negative-control, invalid-state, repeatability, and replay tests. +7. Cite a primary technical source for the model and document solver limitations. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f196884 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,12 @@ +# Changelog + +All notable changes are documented here. PhaseProbe follows semantic versioning. + +## 0.1.0 — 2026-08-01 + +- Added `scan`, `perturb`, `check`, `replay`, `generate-test`, and `report` commands. +- Added typed adapters for logistic map, Lorenz, Lotka–Volterra predator–prey, and a mutually repressing genetic toggle. +- Added deterministic seeds, canonical configuration, fixed-step execution, invalid-integration diagnostics, capped trace retention, and exact trace hashes. +- Added transition brackets, bounded initial-state search, repeatability confirmation, replay fixtures with integrity hashes, and fixed-template pytest generation. +- Added terminal, versioned JSON, and self-contained offline HTML evidence. +- Added Windows/Linux CI, packaging and packed-install checks, documentation/hygiene gates, and repository-native demo assets. diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000..d64eb3b --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,17 @@ +cff-version: 1.2.0 +message: "If you use PhaseProbe, cite the software release." +title: "PhaseProbe" +type: software +authors: + - name: "Ali" +version: 0.1.0 +date-released: 2026-08-01 +url: "https://github.com/aliengineering-byte/phaseprobe" +repository-code: "https://github.com/aliengineering-byte/phaseprobe" +license: Apache-2.0 +abstract: "PhaseProbe finds bounded qualitative simulation transitions and turns reproducible evidence into replay fixtures and pytest regressions." +keywords: + - simulation testing + - reproducibility + - dynamical systems + - regression testing diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..112d9df --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,9 @@ +# Code of conduct + +PhaseProbe contributors must make technical collaboration safe, respectful, and evidence-led. + +Welcome people regardless of background or identity. Discuss ideas and code rather than personal characteristics. Give reproducible evidence, acknowledge uncertainty, accept correction, and avoid harassment, threats, sexualized conduct, discrimination, doxxing, or sustained disruption. + +Maintainers may edit or remove contributions and restrict participation when behavior harms the community. Report conduct concerns privately through the repository’s security/contact path; do not include sensitive personal information in a public issue. Reports will be reviewed fairly and with as much confidentiality as practical. + +Good-faith disagreement about scientific methods is welcome. Fabricated evidence, misrepresentation of certainty, or retaliation against someone raising a reproducibility concern is not. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..2327771 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,36 @@ +# Contributing + +Contributions are welcome when they preserve PhaseProbe’s narrow testing job and scientific language. + +## Setup + +```bash +python -m venv .venv +# Windows: .venv\Scripts\python -m pip install -e ".[dev]" +# Linux: .venv/bin/python -m pip install -e ".[dev]" +python -m ruff format --check . +python -m ruff check . +python -m mypy src tests +python -m pytest --cov=phaseprobe +``` + +Before a pull request, also run `python -m build`, install the wheel into a clean environment, execute the logistic quick start, run `python scripts/check_links.py`, and run `python scripts/hygiene.py`. + +## Scientific changes + +New adapters or classifiers must include: + +- equation or model definition and a primary technical citation; +- deterministic positive case and negative control; +- seed, solver/iteration settings, tolerances, burn-in, observation window, and invalid-state policy; +- an explanation of what the classifier establishes and what it does not; +- replay and generated-test coverage; +- solver-refinement or convergence evidence where numerical integration matters. + +Do not call finite-time divergence a Lyapunov exponent, a numerical bracket an exact bifurcation point, or a bounded search result globally minimal. + +## Pull requests + +Keep changes focused, update documentation and `CHANGELOG.md`, and report exact validation commands. Never add copyrighted books, PDFs, extracted private text, credentials, personal email addresses, telemetry, runtime API/LLM/GPU/Docker requirements, or unsupported performance/adoption claims. + +At least three starter tasks are available as structured good-first-issue templates: add a damped-pendulum adapter, add JSON Schema validation fixtures, and improve HTML-report accessibility. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ebdb6ae --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2026 Ali + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LIMITATIONS.md b/LIMITATIONS.md new file mode 100644 index 0000000..fa1c5a1 --- /dev/null +++ b/LIMITATIONS.md @@ -0,0 +1,14 @@ +# Limitations + +- A reported bracket is finite-time, classifier-specific numerical evidence, not an exact bifurcation point. +- A smallest result is the smallest reproducible candidate found by the declared finite grid/refinement, not a proof of global minimality. +- The Lorenz metric is a finite-time divergence rate, not a Lyapunov exponent and not by itself proof of chaos. +- Fixed-step RK4 is transparent but not a substitute for adaptive solvers, stiffness detection, convergence studies, interval arithmetic, or rigorous numerics. +- Exact trace hashes can detect harmless floating-point differences across architectures or native math libraries. v0.1.0 has no configurable fuzzy replay mode. +- Only one scan parameter or one initial-state dimension is active per search. Non-monotonic multidimensional delta debugging is future work. +- Built-in models are small validation examples. Their success does not validate a downstream scientific model. +- Seeds are explicit, but an external adapter can still be nondeterministic through threads, native libraries, unordered data, clocks, file systems, or unrecorded external state. +- Trace retention is capped and may omit earlier behavior. Configuration must preserve a scientifically adequate observation window. +- Classification thresholds can create boundary ambiguity. The scan stops refinement when a midpoint matches neither stable endpoint class, but it cannot remove classifier bias. +- Replay fixtures can expose model configuration and state. Review them before committing if a downstream adapter contains sensitive data. +- Name research found no exact package collision on 2026-08-01, but it is not legal or trademark advice. diff --git a/PRIOR_ART.md b/PRIOR_ART.md new file mode 100644 index 0000000..d20d09d --- /dev/null +++ b/PRIOR_ART.md @@ -0,0 +1,33 @@ +# Prior art and name audit + +Last verified: 2026-08-01. + +PhaseProbe is positioned narrowly: simulation-specific transition discovery, bounded counterexample minimization, deterministic replay, and regression-test generation. It is not a replacement for a simulator, ODE solver, sensitivity-analysis package, formal bifurcation package, property-based testing framework, or rigorous proof tool. + +## Name decision + +The `PhaseProbe` name is retained. Exact-name checks returned no repository named `phaseprobe` from the GitHub repository-search API and HTTP 404 (no registered project) from both the PyPI project JSON endpoint and npm registry on 2026-08-01. A broader web search found the descriptive term “phase probe” in accelerator instrumentation, but no material software-package collision for this developer tool. This is a practical collision audit, not a legal opinion or trademark clearance. PyPI publication remains explicitly out of scope. + +Evidence: [GitHub repository search](https://api.github.com/search/repositories?q=phaseprobe+in%3Aname), [PyPI project endpoint](https://pypi.org/pypi/phaseprobe/json), [npm registry endpoint](https://registry.npmjs.org/phaseprobe), and an example of the unrelated [accelerator phase-probe usage](https://indico.jacow.org/event/96/contributions/12067/). + +## Comparison + +| tool | primary job | target user | existing strength | overlap | remaining gap | PhaseProbe differentiation | last verified date | +| --- | --- | --- | --- | --- | --- | --- | --- | +| [Hypothesis](https://hypothesis.readthedocs.io/en/latest/) | Property-based test generation and shrinking | Python developers | Broad input strategies, edge-case generation, mature shrinking | Search and counterexample reduction | Does not provide a simulation trace/classification/replay artifact workflow by itself | Domain-specific bounded scans, trace evidence, replay fixtures, and generated pytest tests | 2026-08-01 | +| [SciPy `solve_ivp`](https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.solve_ivp.html) | Numerical initial-value integration | Scientific Python users | Multiple adaptive ODE solvers and error controls | Produces trajectories for models | Does not classify regimes or turn discovered transitions into replayable regressions | PhaseProbe can sit above an adapter/solver; v0.1.0 examples use a documented fixed-step method to keep runtime dependencies at zero | 2026-08-01 | +| [Mesa](https://mesa.readthedocs.io/stable/) | Agent-based model construction, execution, visualization, and analysis | Agent-based modelers | Rich ABM components and data collection | Simulation configuration and repeated runs | No focused transition-to-replay-to-pytest pipeline in the core job | Adapter-oriented testing layer, not an ABM framework | 2026-08-01 | +| [NetLogo](https://docs.netlogo.org/) | Programmable multi-agent modeling environment | Researchers and educators | Mature modeling language, model library, and interactive environment | Parameter sweeps and qualitative behavior exploration | Developer-facing Python fixture/test generation is not its central job | Headless local regression evidence for adapters | 2026-08-01 | +| [cadCAD](https://cadcad.readthedocs.io/en/latest/) | Modeling complex adaptive and dynamical systems | System modelers | Structured generalized dynamical-system workflows | Parameterized simulation runs | No dedicated smallest-found transition fixture and pytest generation workflow | Testing pipeline around a small typed adapter | 2026-08-01 | +| [Golly](https://golly.sourceforge.io/) | Explore cellular automata | Cellular-automata users | Fast cross-platform CA exploration and many rule formats | Qualitative state evolution | Not a general simulation regression tool | Model-agnostic adapter and CI artifacts | 2026-08-01 | +| [py-pde](https://py-pde.readthedocs.io/en/latest/) | Solve PDEs including diffusion and stochastic equations | PDE modelers | Field abstractions, boundary conditions, and PDE solvers | Reaction/diffusion trajectory generation | Does not center on qualitative-transition minimization and pytest materialization | Consumes bounded observables/classifiers rather than replacing PDE solvers | 2026-08-01 | +| [PyDSTool / PyCont](https://pydstool.github.io/PyDSTool/UserDocumentation.html) | Dynamical-system simulation, continuation, and bifurcation analysis | Dynamical-systems researchers | Integrated trajectories, continuation, phase planes, and bifurcation tools | Parameter changes and bifurcation evidence | Formal analysis is broader and deeper, while direct developer regression-fixture output is not the primary job | Explicitly reports numerical brackets rather than formal bifurcation points and produces deterministic test artifacts | 2026-08-01 | +| [AUTO-07p](https://archive-dsweb.siam.org/Software/auto-07p.html) | Continuation and bifurcation analysis for ODEs | Applied mathematicians | Established numerical continuation and bifurcation capabilities | Transition analysis | Higher setup cost and a different goal from bounded black-box regression discovery | Lightweight black-box classification and replay; never presents itself as formal continuation | 2026-08-01 | +| [Attractors.jl](https://juliadynamics.github.io/DynamicalSystemsDocs.jl/attractors/stable/) | Find attractors, basins, basin boundaries, stability measures, and continuation | Nonlinear-dynamics researchers | Deep attractor and basin analysis | Regime and multistability exploration | Regression-test generation and Python replay fixtures are not the central workflow | Testing-focused evidence handoff with deliberately simpler analysis | 2026-08-01 | +| [SALib](https://github.com/SALib/SALib) | Global sensitivity analysis | Modelers and uncertainty-quantification practitioners | Sobol, Morris, FAST, and other established methods | Bounded input exploration and sensitivity | Sensitivity indices answer a different question from finding a reproducible class boundary | Searches a declared qualitative predicate and records the smallest case found in that search | 2026-08-01 | +| [Exploratory metamorphic testing](https://pmc.ncbi.nlm.nih.gov/articles/PMC7252536/) | Test relations among related scientific-simulation inputs and outputs | Scientific software teams | Addresses test-oracle difficulty using relations across executions | Baseline/perturbed twin runs and invariants | A research method, not a packaged transition/replay/pytest workflow | Implements a narrow, deterministic operational workflow without claiming to subsume metamorphic testing | 2026-08-01 | +| General record/replay tools (for example [VCR](https://github.com/vcr/vcr)) | Capture and replay external interactions | Application developers | Stable deterministic fixtures for I/O interactions | Fixture integrity and replay concepts | External-call cassettes do not re-execute a scientific model from model/config/seed/tolerances | Re-executes supported simulation adapters and verifies trace/classification hashes | 2026-08-01 | + +## Differentiation boundary + +PhaseProbe v0.1.0 offers bounded empirical evidence. It does not prove chaos, compute a formal Lyapunov exponent, prove global minimality, locate an exact bifurcation point, validate a scientific model against nature, or replace solver-convergence studies. Its “smallest” result always means the smallest reproducible change found by the declared finite search and refinement procedure. diff --git a/README.md b/README.md new file mode 100644 index 0000000..dd57571 --- /dev/null +++ b/README.md @@ -0,0 +1,159 @@ +# PhaseProbe + +> Find a simulation behavior boundary. Preserve it as a replay. Turn it into a test. + +Maintaining a simulation is risky when a tiny parameter or initial-condition change can cross a qualitative boundary while ordinary numeric assertions still look plausible. PhaseProbe runs a bounded, deterministic search, records exactly what it tested, and emits an offline report plus an executable pytest regression. + +```console +$ python -m pip install . +$ phaseprobe scan --example logistic +QUALITATIVE TRANSITION FOUND + +Model: logistic-map +Search dimension: r +Baseline regime: period-2 +Changed regime: period-4 +Replay: .phaseprobe/runs//replay.json + +$ phaseprobe generate-test .phaseprobe/runs//replay.json +$ python -m pytest -q tests/generated +1 passed +``` + +[![Terminal demo: scan, refine, replay, and generated pytest](assets/demo-static.png)](assets/demo.gif) + +No API key, LLM, GPU, Docker, account, telemetry, network connection, or hosted service is required at runtime. PhaseProbe 0.1.0 has no third-party runtime dependencies. + +## Five-minute quick start + +Requires Python 3.10 or newer on Windows or Linux. + +```bash +python -m pip install . +phaseprobe scan --example logistic +phaseprobe replay .phaseprobe/runs//replay.json +phaseprobe generate-test .phaseprobe/runs//replay.json +python -m pytest -q tests/generated +phaseprobe report .phaseprobe/runs/ +``` + +Replace `` with the directory printed by `scan`. The scan returns `0` when it successfully finds a transition. Add `--fail-on-finding` only when a finding should fail CI. + +The quick-start evidence is empirical: the built-in classifier finds a finite-time period-2/period-4 bracket for the logistic map, performs bounded binary refinement, repeats both endpoints, saves trace hashes and a versioned fixture, and generates a fixed pytest template. It does not claim an exact bifurcation point. + +## Commands + +| command | job | normal success | +| --- | --- | --- | +| `scan` | Bounded one-dimensional parameter scan, adjacent class-change detection, and stable bracket refinement | Finding or no finding, exit `0` | +| `perturb` | Baseline/perturbed twin runs over bounded initial-state changes | Finding or no finding, exit `0` | +| `check` | Execute a declared configuration policy for CI | Exit `1` only when policy fails | +| `replay` | Validate fixture integrity and re-execute model, parameters, seed, initial state, tolerances, and retention | Matching class and exact retained trace hashes | +| `generate-test` | Validate and copy a fixture into a non-extensible pytest template | Executable test under `tests/generated/` | +| `report` | Regenerate terminal, versioned JSON, and self-contained offline HTML evidence | Local report files | + +Common options: + +```console +phaseprobe scan --config examples/configs/logistic-scan.json +phaseprobe perturb --example lorenz --json +phaseprobe check --example predator-prey +phaseprobe scan --example logistic --fail-on-finding +``` + +Exit codes are stable: `0` completed, `1` declared policy or explicit `--fail-on-finding`, `2` invalid input/configuration, `3` numerical failure, and `4` internal PhaseProbe defect. + +## Included deterministic examples + +| example | command | positive evidence | negative control | direct technical source | +| --- | --- | --- | --- | --- | +| Logistic map | `phaseprobe scan --example logistic` | Finite-time period-2 to period-4 classification bracket | `--example logistic-negative` stays period-2 over its declared range | [May, 1976](https://www.nature.com/articles/261459a0) | +| Lorenz system | `phaseprobe perturb --example lorenz` | Small initial separation exceeds the declared finite-time trajectory-distance threshold | Short window plus unreachable threshold reports no finding | [Lorenz, 1963](https://journals.ametsoc.org/view/journals/atsc/20/2/1520-0469_1963_020_0130_dnf_2_0_co_2.xml) | +| Predator–prey | `phaseprobe check --example predator-prey` | Refined RK4 step preserves the analytic first integral within tolerance | Coarse step fails the invariant-drift policy | [Lotka, 1920](https://doi.org/10.1073/pnas.6.7.410) | +| Genetic toggle | `phaseprobe perturb --example toggle` | Bounded initial-state perturbation reaches the opposite dominant state | Smaller declared range stays in the baseline basin | [Gardner, Cantor & Collins, 2000](https://www.nature.com/articles/35002131) | + +Each configuration records the seed, fixed integration/iteration settings, tolerances, burn-in, observation window, classification rule, refinement rule, invalid-state policy, and trace cap. See [examples/README.md](examples/README.md) for equations and interpretation. + +### Measured reference run + +On 2026-08-01 with CPython 3.12.13 on Windows, the instrumented logistic quick-start scan completed in 10.114 seconds with a 34.81 MiB peak process-tree working set. All eight positive/negative example commands completed in 12.238 seconds. The final 38-test suite with branch coverage completed in 64.86 seconds at 87.92% coverage. These are observations from one local run, not cross-machine performance claims; the generated transcript is in [assets/demo-session.txt](assets/demo-session.txt). + +## Evidence artifacts + +Every execution is bounded under `.phaseprobe/runs//`: + +```text +run.json complete versioned evidence +findings.json compact finding or negative result +replay.json schema-versioned integrity-protected fixture +trace.jsonl capped baseline/changed retained points +report.html self-contained offline report +manifest.json sizes and SHA-256 hashes +``` + +Model names used for generated test paths are sanitized. The generated source comes from a fixed template; configuration strings never become executable Python. + +## What PhaseProbe adds—and what it does not + +| existing category | established strength | PhaseProbe’s narrower job | +| --- | --- | --- | +| Solvers such as SciPy | Integrate differential equations with mature numerical methods | Consume an adapter’s trajectories and preserve a discovered qualitative boundary as test evidence | +| Property-based testing such as Hypothesis | Generate and shrink broad input domains | Search declared simulation dimensions with model-specific observables and classes | +| Bifurcation/attractor tools such as AUTO, PyDSTool, and Attractors.jl | Deep continuation, bifurcation, attractor, and basin analysis | Lightweight black-box numerical brackets plus replay and pytest materialization | +| Sensitivity tools such as SALib | Quantify input contributions to output variation | Find a reproducible qualitative predicate change | +| Simulation environments such as Mesa, NetLogo, cadCAD, and Golly | Build, run, and explore models | Test adapters without becoming another simulation environment | + +The evidence-backed audit is in [PRIOR_ART.md](PRIOR_ART.md). + +## Adapter interface + +Adapters implement a small typed protocol: + +```python +class ModelAdapter(Protocol): + name: str + identity: str + dimensions: tuple[str, ...] + + def initial_state(self, config, seed): ... + def step(self, state, parameters, dt): ... + def observe(self, state): ... + def classify(self, trace, tolerances): ... + def invariants(self, trace, parameters, tolerances): ... +``` + +The explicit serializable state tuple makes bounded perturbation, invalid-value detection, trace hashing, and replay straightforward. See [ARCHITECTURE.md](ARCHITECTURE.md) for extension guidance. + +## Scientific scope + +PhaseProbe uses these terms deliberately: + +- finite-time trajectory divergence: measured separation over the recorded window; +- sensitive-dependence evidence: repeatable finite-time divergence under a declared small perturbation, not by itself a proof of chaos; +- numerical instability: behavior attributable to the numerical method or step size; +- invariant violation: a declared conservation or boundedness rule failed; +- qualitative regime change: the adapter’s declared classifier changed; +- bifurcation evidence: a numerical class-change bracket, not an exact bifurcation point; +- invalid integration: NaN, infinity, overflow, state-shape mismatch, or hard bound breach; +- solver failure: the step method could not advance a valid state. + +The Lorenz example reports `finite-time divergence rate`; it does not compute or claim a Lyapunov exponent. “Smallest” always means smallest found within the declared bounded search. Read [SCIENTIFIC_METHODS.md](SCIENTIFIC_METHODS.md) and [LIMITATIONS.md](LIMITATIONS.md). + +## Development + +```bash +python -m pip install -e ".[dev]" +python -m ruff format --check . +python -m ruff check . +python -m mypy src tests +python -m pytest --cov=phaseprobe +python -m build +python scripts/check_links.py +python scripts/hygiene.py +``` + +See [CONTRIBUTING.md](CONTRIBUTING.md) and the concrete good-first-issue templates in `.github/ISSUE_TEMPLATE/`. + +## License and citation + +Apache-2.0. Copyright 2026 Ali. Citation metadata is in [CITATION.cff](CITATION.cff). diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 0000000..a2336eb --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,14 @@ +# Roadmap + +## After 0.1.0 + +- External adapter discovery with an explicit safe registration mechanism. +- Non-monotonic multi-dimension search: coarse exploration, stable bracket discovery, local refinement, repeatability, and delta debugging. +- Optional classification/tolerance replay modes alongside strict trace hashes. +- Adapter-provided solver convergence studies and paired step-size evidence. +- Streaming trace summaries for expensive simulators without weakening the configured cap. +- JSON Schema publication and compatibility migration tooling. +- JUnit/SARIF policy output for CI annotation. +- More reference adapters, accepted only with positive cases, negative controls, invalid-integration tests, and primary citations. + +PyPI publication is intentionally absent and requires separate authorization. diff --git a/RUN_LOG.md b/RUN_LOG.md new file mode 100644 index 0000000..1024ccc --- /dev/null +++ b/RUN_LOG.md @@ -0,0 +1,95 @@ +# PhaseProbe v0.1.0 run log + +Evidence is recorded only from executed commands. Private research paths, PDFs, extracted text, credentials, and machine-specific absolute paths are excluded from this public log. + +## Scope and research — 2026-08-01 + +- Created only the PhaseProbe project root, then `.gitignore`, `AGENTS.md`, and this log; read all three back before research or implementation. +- Created the ignored private research ledger and source directories before retrieval. Four requested PDFs were retrieved privately, SHA-256 hashed, and excluded from Git. The unidentified PDF was identified from internal metadata rather than its URL. No PDF, extracted text, quotation, illustration, or chapter substitute is tracked. +- Verified prior art and public technical claims against primary papers or official project documentation. `PRIOR_ART.md` records direct links and a dated comparison. +- Exact-name registry checks on 2026-08-01 returned no `phaseprobe` GitHub repository and HTTP 404 from PyPI and npm project endpoints. The name decision is documented as a practical collision audit, not legal clearance. +- Installed uv 0.11.10 from its official release artifact under ignored `.tools/`; downloaded artifact SHA-256: `7A0C424C7BC55A74751F13592235953EBBE182FA00355F7AE3FB7AB734A51638`. +- Installed GitHub CLI 2.97.0 from its official release artifact under ignored `.tools/`; downloaded artifact SHA-256: `35D7FE05C4DD1411FFDA1E73DFC7C6F44B75C936CA51FA6595C657FDC0350CEC`. +- Installed CPython 3.12.13, environments, dependencies, and caches only within ignored project directories. `uv lock --check` and `uv sync --frozen --all-extras` passed. + +## Architecture freeze + +v0.1.0 uses a zero-runtime-dependency Python `src/` package with typed model adapters, canonical JSON configuration, explicit seeds, deterministic fixed-step execution, finite-state validation, capped traces, exact trace hashes, bounded one-dimensional scan/perturb search, repeatability confirmation, policy checks, integrity-protected replay fixtures, a fixed pytest generator, and terminal/JSON/offline-HTML reports. + +The public commands are `scan`, `perturb`, `check`, `replay`, `generate-test`, and `report`. The stable exit contract is 0 completed, 1 policy/explicit fail-on-finding, 2 invalid input, 3 numerical failure, and 4 internal defect. + +## Measured numerical evidence + +Command suite: all eight built-in positive and negative-control commands, each with JSON output and its expected exit code. + +| example | observed result | baseline | changed/control | exit | +| --- | --- | --- | --- | ---: | +| logistic | qualitative transition found | period-2 | period-4 | 0 | +| logistic-negative | no qualitative transition found | period-2 | n/a | 0 | +| lorenz | finite-time trajectory divergence found | two-lobe finite-time trajectory | same qualitative class; separation predicate triggered | 0 | +| lorenz-negative | no sensitive perturbation found | two-lobe finite-time trajectory | threshold not reached | 0 | +| predator-prey | check policy passed | bounded positive oscillation | no invariant violation | 0 | +| predator-prey-negative | check policy failed | bounded positive oscillation | coarse-step invariant drift | 1 | +| toggle | qualitative state switch found | u-dominant | v-dominant | 0 | +| toggle-negative | no sensitive perturbation found | u-dominant | u-dominant | 0 | + +All eight commands completed in 12.238 seconds. The logistic classification bracket was `3.449477539 .. 3.449478027`, with a `4.882812501e-07` final endpoint separation. This is finite-time classifier evidence, not an exact bifurcation point. The Lorenz result is explicitly a finite-time divergence rate, not a Lyapunov exponent. + +The predator–prey refined RK4 configuration passed its declared `1e-7` invariant-drift tolerance; the deliberately coarse negative control failed that same policy. Invalid-state tests covered hard-limit failure, and the engine checks NaN, infinity, arithmetic/domain failures, and state-dimension mismatch. + +## Performance evidence + +- Instrumented `phaseprobe scan --example logistic`: exit 0, 10.114 seconds wall time, 34.81 MiB peak process-tree working set, and 20.90 MiB peak process-tree private bytes. Monitoring followed the Windows virtual-environment launcher and its child processes. +- Repository-native demo generation: 14.957 seconds total. Its embedded real-command transcript measured scan 9.879 seconds, replay 1.328 seconds, generation 1.330 seconds, and generated pytest 1.631 seconds. +- Full example suite: 12.238 seconds. +- Final coverage suite: 38 tests in 64.86 seconds. The quick-start target of 30 seconds, full-example target of three minutes, and 512 MiB memory target were met on this measured environment. + +These are single-environment observations, not cross-machine benchmark claims. + +## Validation gates + +| gate | exact command or method | observed result | +| --- | --- | --- | +| format | `python -m ruff format --check .` | 28 files already formatted | +| lint | `python -m ruff check .` | passed | +| strict typing | `python -m mypy src tests` | passed; 25 source files | +| tests and branch coverage | `python -m pytest --cov=phaseprobe --cov-report=term-missing` | 38 passed in 64.86 seconds; 87.92% coverage; 85% gate met | +| generated test | `python -m pytest -q tests/generated` | 1 passed; committed fixture replays exact class and trace hashes | +| configuration lock | `uv lock --check` | passed | +| frozen development install | `uv sync --frozen --all-extras` | passed | +| version | `python -m phaseprobe --version` | `phaseprobe 0.1.0` | +| examples | eight positive/negative commands | expected statuses and exits; 12.238 seconds | +| quick start | instrumented positive logistic scan | 10.114 seconds; 34.81 MiB peak working set | +| replay | positive logistic fixture | both endpoints matched classification, model identity, and exact retained trace hashes | +| test generation | positive logistic fixture | fixed-template test and copied integrity fixture created; pytest passed | +| reports | terminal, JSON, HTML regeneration tests | passed; HTML contains no script or CDN reference | +| demo | `python scripts/generate_demo.py` | GIF, static PNG, sanitized real-command transcript created | +| documentation links | `python scripts/check_links.py` | 15 Markdown files, 22 external links inventoried, no missing local links | +| privacy/secret/large file | `python scripts/hygiene.py` | 77 public files scanned before Git, no issues, no file over 1 MB | +| package contents | wheel/SDist archive inspection | wheel 32 files, source archive 76 files, no PDF/private research/tool/environment path | +| runtime dependencies | wheel metadata plus clean uv environment | no runtime `Requires-Dist`; only PhaseProbe installed before test tooling | +| packed install | wheel into isolated project-local environment | version, positive scan, strict replay, test generation, and generated pytest passed | +| packed quick start | isolated wheel environment | 9.131 seconds | + +## Package artifacts + +- Wheel: `phaseprobe-0.1.0-py3-none-any.whl`, 42,313 bytes, final SHA-256 `207F303D70ABB5D9CCBC019BCF1AB9AF51D769838054283DF3A7C5D5D79CBA18`. +- Source distribution: `phaseprobe-0.1.0.tar.gz`, 352,805 bytes, final SHA-256 `4BC793351FBEAC740A4ECC15A681AFE567ACBE3E94D0DCD54262667BAECDA6A5`. + +## Failures and repairs + +1. The first editable build failed because package metadata referenced a README that had not yet been written. Added the README and reran successfully. +2. The first broad Ruff invocation traversed ignored project-local tool/cache files. Added explicit `.tools`, `.cache`, environment, private research, output, and build exclusions; all subsequent `ruff .` gates were bounded to public project content. +3. The first generated-test subprocess passed an absolute Windows test path; pytest misinterpreted it and inspected a protected compatibility directory. Ran the subprocess from the generated directory with a relative filename. +4. Initial measured coverage was 82.19%, below the declared 85% gate. Added command-level replay, test-generation, report, failure-code, and integrity tests; final coverage is 87.92%. +5. A nested generated-pytest process inherited coverage-control variables and produced incompatible combine data. Removed only those instrumentation variables from the nested test environment; functional behavior remained unchanged. +6. The first packed-smoke script used an unavailable old-PowerShell parameter. Replaced it with an explicit artifact count and compatible selection. +7. A uv-created smoke environment intentionally had no `pip` module, so `python -m pip freeze` was not a valid dependency query. Repeated the gate with `uv pip freeze --python`; wheel metadata independently confirmed zero runtime dependencies. +8. The first committed generated test failed Ruff import-block normalization. Updated the generator template, regenerated the test, and restarted the full final gate. + +## Skipped or bounded checks + +- External documentation links are inventoried in CI but not fetched there to avoid network-flaky builds; the prior-art/name audit used live official/primary sources during this release run. +- No formal trademark or legal clearance was performed. +- No PyPI or npm publication was attempted or authorized. +- GitHub/CI/release/fresh-public-clone evidence is appended only after those remote actions complete. diff --git a/SCIENTIFIC_METHODS.md b/SCIENTIFIC_METHODS.md new file mode 100644 index 0000000..5bec841 --- /dev/null +++ b/SCIENTIFIC_METHODS.md @@ -0,0 +1,59 @@ +# Scientific methods + +PhaseProbe 0.1.0 produces bounded computational evidence. It does not perform formal verification, model validation against observations, or computer-assisted proof. + +## Execution record + +Every result records the model identity, parameters, initial state, explicit seed, integration or iteration settings, tolerance map, burn-in, retained observation window, classification rule, refinement rule, invalid-state policy, capped trace, trace hash, and repeatability evidence. JSON objects are serialized with sorted keys and disallow NaN. + +## Integration and iteration + +The logistic map advances its exact recurrence once per declared step. The Lorenz, predator–prey, and genetic-toggle examples use classical fixed-step fourth-order Runge–Kutta (RK4). Fixed steps make the execution path transparent and dependency-free; they do not guarantee an accurate solution. The predator–prey positive/negative pair demonstrates why step refinement and an analytic invariant matter. + +PhaseProbe stops a run as `invalid integration` when the adapter returns a wrong state dimension, NaN, infinity, a value beyond the declared hard state limit, or raises an arithmetic/domain failure. This is distinct from an invariant violation on an otherwise finite trajectory. + +## Qualitative classification + +Classification belongs to the adapter and is configured by explicit tolerances: + +- Logistic: test retained tail recurrence for periods 1, 2, 4, 8, and 16 in that order. Failure to converge within the finite burn-in/window is `aperiodic-or-unresolved`, not a declaration of chaos. +- Lorenz: label finite-time lobe visitation only. The perturbation predicate is Euclidean twin-trajectory separation. +- Predator–prey: require retained positive populations; separately evaluate drift of the analytic Lotka–Volterra first integral. +- Genetic toggle: compare terminal `u-v` against a declared dominance tolerance. + +Classifier output is model- and window-specific. A class change is a `qualitative regime change` under that rule, not automatically a physical phase transition. + +## Parameter scanning + +`scan` evaluates an inclusive deterministic one-dimensional grid and finds the first adjacent class change. It then probes midpoints while the midpoint reproduces one of the two stable endpoint classes. If a midpoint reproduces neither class, refinement stops instead of relabeling ambiguity. Final endpoints are rerun and their exact trace hashes compared. + +The output is a numerical transition bracket. It is `bifurcation evidence` only when the classifier and model justify that interpretation, and it is never reported as an exact bifurcation point. + +## Initial-state perturbation + +`perturb` runs a baseline plus candidates ordered from smallest to largest over a linear or logarithmic declared range. A predicate may require a target class or a maximum twin-trajectory distance. When a non-trigger/trigger interval exists, deterministic binary refinement narrows it. The smallest triggering candidate is repeated exactly. + +For Lorenz, PhaseProbe computes + +```text +finite-time divergence rate = log(max separation / initial separation) / elapsed window +``` + +as a descriptive finite-window quantity. The method does not implement tangent-space evolution, renormalization, asymptotic limiting, or convergence validation required to claim a Lyapunov exponent. + +## Counterexample minimization + +For one monotonic declared predicate, bracketed binary refinement is used. PhaseProbe does not assume every simulation predicate is monotonic. v0.1.0 only automates one-dimensional scans and one active perturbation dimension; it therefore does not claim multidimensional delta debugging. The reported phrase is always “smallest reproducible change found within the declared search space.” + +## Invariants + +An invariant result includes its name, pass/fail value, measured value, tolerance, and detail. Some checks, such as the Lorenz finite-state bound, are explicitly diagnostic bounds rather than mathematical invariants. Passing an invariant does not validate the model or prove solver convergence. + +## Primary sources for examples + +- Robert M. May, “Simple mathematical models with very complicated dynamics,” *Nature* 261, 459–467 (1976), [doi:10.1038/261459a0](https://www.nature.com/articles/261459a0). +- Edward N. Lorenz, “Deterministic Nonperiodic Flow,” *Journal of the Atmospheric Sciences* 20, 130–141 (1963), [publisher record](https://journals.ametsoc.org/view/journals/atsc/20/2/1520-0469_1963_020_0130_dnf_2_0_co_2.xml). +- Alfred J. Lotka, “Analytical Note on Certain Rhythmic Relations in Organic Systems,” *PNAS* 6, 410–415 (1920), [doi:10.1073/pnas.6.7.410](https://doi.org/10.1073/pnas.6.7.410). +- Timothy S. Gardner, Charles R. Cantor, and James J. Collins, “Construction of a genetic toggle switch in *Escherichia coli*,” *Nature* 403, 339–342 (2000), [doi:10.1038/35002131](https://www.nature.com/articles/35002131). + +Private books informed only bounded conceptual orientation. No public claim depends on them, and no PDF, extracted text, quotation, illustration, or chapter substitute is distributed. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..dba023a --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,17 @@ +# Security policy + +## Supported version + +Security fixes target the latest 0.1.x release until a newer minor release replaces it. + +## Reporting + +Use GitHub’s private vulnerability reporting feature for the public repository. Do not open a public issue with an exploit, credential, sensitive fixture, or personal data. No password or token is ever required by PhaseProbe maintainers in chat. + +Include the affected version, operating system, minimal reproduction, impact, and suggested remediation if known. Ali will acknowledge a valid report through GitHub and coordinate disclosure after a fix is available. + +## Security boundary + +PhaseProbe executes installed Python adapter code with the user’s permissions. Configuration files are data, but an adapter is code and must be reviewed before installation. Generated tests use a fixed template and sanitized names; replay fixtures are integrity checked before execution. HTML reports contain escaped data and no script or external resource. + +Review downstream fixtures before committing because model parameters and state may be sensitive. PhaseProbe has no telemetry and performs no runtime network calls. diff --git a/assets/demo-session.txt b/assets/demo-session.txt new file mode 100644 index 0000000..6795736 --- /dev/null +++ b/assets/demo-session.txt @@ -0,0 +1,35 @@ +$ phaseprobe scan --example logistic +QUALITATIVE TRANSITION FOUND + +Model: logistic-map +Evidence: qualitative-regime-change +Search dimension: r +Stable bracket: 3.449477539 .. 3.449478027 +Smallest reproducible change found: 4.882812501e-07 +Baseline regime: period-2 +Changed regime: period-4 +Invariant violations: 0 +Repeatable: true +Replay: .phaseprobe/runs//replay.json + +Scope: Smallest reproducible separation found by the declared bounded scan and binary refinement; not a proof of a globally minimal perturbation or exact bifurcation point. +HTML report: .phaseprobe/runs//report.html + +$ phaseprobe replay .phaseprobe/runs//replay.json +REPLAY VERIFIED + +Model: logistic-map +baseline: classification=True, trace-hash=True, model-identity=True +changed: classification=True, trace-hash=True, model-identity=True + +$ phaseprobe generate-test .phaseprobe/runs//replay.json +PYTEST REGRESSION GENERATED + +Test: tests/generated/test_logistic_map_transition.py +Replay fixture: tests/generated/fixtures/logistic_map-replay.json + +$ python -m pytest -q tests/generated +. [100%] +1 passed in 0.97s + +Measured generation run: scan=9.879s, replay=1.328s, generate=1.330s, pytest=1.631s. diff --git a/assets/demo-static.png b/assets/demo-static.png new file mode 100644 index 0000000000000000000000000000000000000000..fccdfc7c00967cae4071eb10c34252b3373665af GIT binary patch literal 64796 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 z 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 AlK=n! literal 0 HcmV?d00001 diff --git a/assets/demo.gif b/assets/demo.gif new file mode 100644 index 0000000000000000000000000000000000000000..8ca047160a2485cd5fa2db8c5e718ee2f77ff8af GIT binary patch literal 106192 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 Y<_XI52S_^I-K2xrR%z2Z>Sbm9`6 zbuefu`NCRH-y$(6N$K+?>2J+!^Z9(A*KS92WyRZe>W4U6Uw!;j1Njo3lBjbFKVPO8 z=cj|&U*Al&YPET16WkWT$6?6ij~sfT26i?E65>m5UBlZtVmaL$whhzyE?dw>I zb*P5{k);P&{>~O%$ry*^0zNhH*E<%<_*x4w=#RXzr_ZG#c)Kfc<%8~S32O(}$nW{{ z8R}J4o2mWnzUJD^WcCLl-rXs+akOS3?>>CyhvaG=40uj>K4zYe2ikGZcJtCvYMya* zG$C?ctsTPEO9TTxmfCBv)y{CDpP_=>)PfHN&tLEP zrnHAE-Y34$F;%(Q?HhF97Jra6UOpT1{A>#xUJtP+kRC27>e)F8IBOBiYls+sQuiA+ zESm+FBWcm8%tmEBy;y%uwwkz65%eXG;O@Vy6b)QQ|i?}fEfAc|fI8Nxw_;J{tEQ0K#`epYZ2mCZ^(gOsx=9De`-B zX;LAx3Xu!^p5SD)I>73K&khwjt)IWz90GT|eGkz0uFdG)?~$r#H%S+#cXTYl%(QfnaD-rYi|$ zC8-WVr4qTq1#qupAQ)st%5<>-pSa3d*l@sPcS)Q))A%3uQcx9GaR-bJH=at0OGYg1 z29r6aJn!T_@8WwS851=;Kz=&??0lHp(0nwo^m+UPq3W+71+>Pu<^YiDB4OX-##i`| zJ^<{J`W2jL-7$cMg+eA7qwy+tue5WO1Di{GXH4V;T!T5mA9u9g$5K<;=gAyrg35^aMeZ*4NHlQ0;z)Ps|5d z%=^e7q)=8%J)GQ_i__Zru9eOC(@!Fb^Mp^Ql9s1ve=Se(rhg3GXR$(#EV>U|rVsx5 zvV7$rb_x*SfYJP!IrPr9$=pJbt_<&J%MJ|pa$>FxL^bgx=`fqpecg6k=NJLleP=ZK zP2ZYY9sbHIJX^c=KJ6$VGF0dVzaojDL{s~XG`B;A0)yFRtgV+scaL_3|2u9^x~Xjf zyK6y^y|~`rg7(7GbR!G_gPu|evitZy_)hIT$-%N8j{s`iep-Jcu~suHN8@+t3A5H& zd@cw}YBaTMZF4J1c}6L1j+G5U>K)>Tr6>nOM7Sx7OgBVGJBEg_$AWEFWwdYeBR(V> zM~4(+)wsq!pxhBDQuy^W3-J{%f>2JVWzHhyQ7R~{&`@y()DVIM`9TI#(7{sD5cZO^YW$8 zQr4Ile8I>z-h}wdSX+iL1yES%RG=-J!?X-8@d*p#(i?}3Nch*{UQu|&1gcv75&0<|Q zkP$jTi%${S{_+boY{-9GrF=l{K2MgC%33|^i;HtLXmGKHbJ_%Z&3_4Nq4ZAhtPp-pLS;{YziK@QAT-z}$~QcvcgrPM0~_5QZ3hZh5fD>v}F1D5u!eDiwv zRr)^wo+~B`f8rL^(3eu-B0nPT8lEHY?(o=+eoA9tO_|m%KjowTtOLx-){DG zJZo;s;S8++ITRn$Qr`+D9bqU_a?PxmVdJb3M9LXph}RIo4Hp{++_J;Rj{qf%2HN)H+)>283FRC4E$QsuoP5koeSD4 zF`MqfyLfd4d=vSIhSvNv64*QM|G^52fE*}uuCQ&h-!SgRi=KK^TfNNXPZ9bBGj5G# zF2ldlQw$LT_mA%rGs~-?Z8xW#MNwYCl0fU%p$760ojTl0+=WT=*FG?WlOV8FG3!`0 zgTCs>s_9A3d84n3(jm@mb?*F7G8VYPyjgdnIRj>suyVU2R-4h8j{w$)BS!6u7S7H0 z?*Q4E8eEK(x1+gDCN^AVGYyKI!>+|NnX?|8sVlW(PPUCCL>Wx9{2VUaV0wZp_AXH- zey}Z9WpLg{LyeN!DVAGDYz@lI5SxK}o5X^@Cw$@Uh*T8u$EeePfPBXAlgEOSaJ>`h zp%t2fhi&_R0FR3NzT*?_@QEXf|Fx8UjDPcTUx3gAd-gaoezC0lg{XlemPr1z!vV7VO&-yFdH+LgUe|wwS5qyCs9J#i$x8BPTGD^*Nayn^Qhq@1=1C}-rXHSgCo*R&*p zs%7Puv2a1r2Xy=)3M=pYU!u!%WpO5;GuRr-;;EsZhg&N%7=)GMPzT{=hbyf8@pn+C!NQ zNy~f?6U0f3Q-~A!U;G#}Rrj?$PBlcecK0$z%4E-{<;>Y|D)31OrO!Qj?3oiN``dEe zt_vG-S>(o|{tqzR5d(3Ed5Kx=ae9cqPg1|~zQLeH3y^kyndx4y(1r;3g{lh;HEq|? zS*{|>ietq_AcElMMm6PP-*`TMeCS0sb^dN;__Lmg59ygnX7Tj)`Cq3|4iUfMZFV>q(B1 zd~ejbOeXgbDYzk9T74-)L)1jxNEkzIqz@4)YKUX@SNmz|=v8^oUuE+BaQ&Bz$K<`+ z@CsmB_+!CM`22Lw*^zMH=fBFJ({No&#B=Qqk=jX@|N90TdAt1vE8O-m^S|>a&ob+; zx)4GTr~6L2GW7iNrMR~D>_*VT_VIIp?PI|${k&>H@Z;wPL@97(z@2o3?$bX2;`E&e zN4)gB6uY)hJ!YW>)6dHz<{yq|J-g~6(O{Rm_38q-qR!HB9s%&#p$)5 z^ClSvCz<<;+UqutUPjY$4Ws{(IZN67NY@#nlq-&>#(E+^Z2;1KZ3*yNxuP(3sS>;w z2}9y+4Pe*Vu4;tvc@fi6LcdNFUG@j)wtzTXamD}V^dENnX|MK?eyJ?@xu)Uw;(23NLsUup_dSj?hvOw-_Ndl^Xsx3+Ngs&ne zDK2w*B2q0;o8N-R5E6GmH#Lma=2iab@baO{*=cgn2Tm z5nP_JbAj9_$svh;KsALn(xDdr^rV`vJop5**;pc~2`4({fm!w+-JiX5hQGdPls26%Qcx zZmUA)Z1Fy>$al51Y^x|qaIh6MUeik#$#XUBAMRPaL3`=Zmu&_Ti@&t>^D0FzWRQI( zr)A>@>Q#2HLJ2wiieD|Tks})8w#Jb^%j7LG!UKChtWF;=5?7{E4act^au;!WR7<;F z(CjqDgCdu3(bf}m&oPhRt=_(@u$v1VJ~G@E`K3lZ4m@;hmH>-pnB~^3kJ$g5){Xho z=Sxb~ZtRWCt9`#dSz&iNM_@S^QGdSuhQQ_77%4_>Ji`sySB`qPH!|)7A<_tZB7!0& z7LMz%9(l^5l`06)^&VVSDYuY=Q66+aPpT441ibVSni`GykzR2LAe-0qx#lt|VYIZ2 z69+Dra!%8S#UhJR&>Q~?7=i*-BMWbzoGBo>Kz}{+%nkn@ zPgts@j?eVxLz*63D^NG^R}hG!T^1m!Nq@>Ln48)76^dOEH!b^4aSZ7OB>YEBnwTEv z!#InmS@x?=>)jKKkK#$n8t2ko=3|XyZ|)_g8h9=v$bg$E(9}jchcB8{95^jZ5kg56 z?pJqo{{ZQwy)yb8CH)t5cib-A*l9v6<@jRw+1$1Fw)X}j4eNhHDxQ&_zf)VLVu6BC zthFPbATNRs8jt720Qw8A9HH0-^}WH5zvre^4ktS5T@9C@J0;tca`PN0*KNEqe%_3U0%Z+rIt~+cx4U9I7^& zR7XvfuTLY>j<+gq7Op@)*!TMNmx^*%ffY=@_j7 z8GMp+Ff@CuNuz#TTQ~(;2%!MXX=OX+s-c?*vo?7K9n(Ax1N6q1bOxPmeGWmUMN zZi7rflA0nuILZf z&3>NM8y`*Qw9QX}+88lQ@Cl@1>d;*fy{?QYyG{bSX4Q_aN~qg6M67COBnGGW+oSi` zAsdPVod?j2-J~?rKBqk?EWQ z5phK?VBxEET0M?P8TWKluk_$ww`$v)HJ_j{4o^c=3Y=804pgt6Nls8L*}pghod)FZ zZT#e3JEecrEy=EXc@uORzMRIiiYU+Fa}7$X`2%ki3LVhg@Luix{rQx`taq;#0=8N_ zzO){&65~|zC789&YvHVAR?DfavwU^Mws%KeYw10seu{0@yIEO%iai0gDXBiSgse8b zJA}9MAo|(v4E?cl8bI%TH&NM_Yk%gwZt49XwMUJ}$2isbz7eE!*0~y-y?4ijcubsf z&L4Pio<8LWg-!&8PWT#`Q-%)b(gywa{|VAPu5m{7&2`!j@JO8ZGM^Cd-9u!11lhrQH>-_T4ZY7`?TC&3|MB0ZUOdKHR{yp5 zuTf{{0OH;IDSLLq^T$U}x4REUkJ59#wWHid)t^o3fYdxoIhem40E{wGdU+I3Nv?@? z48mT2JA2FWEOQ_z=CQak4xBQng>+dn~ zMPHU+%9u_Dd!X>x5=_^~Z=$q6m}xi4rOt* zGoq9%R(JJAZSUTh-{B}FtU)qQ#H)=whn8M^pn*rD_2oBIr-HC808y}fJsSRIS^n2Zo^CeJpdbZfAz^wOM^e%i zS`!=lGKN}E&Z|Zx=ltkTa7A~)ZQi+|BFb{j7wy%JB9UZ%DgB_D#lVB#o9cPvX{osn zEC?=P4h6G@Bvd^be<0rrkBJYRU}^wUR{&r$ijZIEyx_kl>Wj{~_D!G|>E|rxh|9?6 zjIuS#qkg4|R6^|P{L*VDSh<}1H0pSL!s~OMcVS%aRLtv2G_vpf0eWqISlZcopac~= zl;#W_#@;~xxwA0!E~`&cik*w;W7*QgTv)PV0}`Lj(ALbxCV!f`t#y!=alm#L>bQ{a zPEOmZFY{qwoVYpq$liUn7U(UHaijb(%H!2yCIr60p=Gt zylnvdVr1F#@}sSqgF69g*=Ba?n z3v?mEB5uCy3aLFIPo$IoU0_HE3e{WMw}8*$O3*49O^;vjoKxUo>}IdXj+Rze2M&{V z?^+O755l?F(rH3uqt;?N8fT%pM4;<}6)%oj`!M-C^osG0-|&|%NuKiMLqw9?1+GtN zInjluJL84lI%pg$Si2u%PL{BIkRt3F>Dmd=y0Rd|THlA{s{P1nVY$d6rK9D!M-XoM zwtP%pw5QJ3J?Q&Wn}e!n3S6p%lPQ$JSqP{3KLFFY->k=sC-(#K)}80TJZc(7cz&?HpLg9rAsZ~hUk3O+-Z+;n^RGXONPyr@DOXEPI(Qh9rUKZ zu+%5mLNy;*=TerkBC;jR72IL-42_+zPMttUOa^)c?7;NUYMkGGMK zCw;I+jz-kUXy0BC5ih{`GVo90yt0xs-kO zfcIQWa8EJKliJw{n&|?(=FbV2G~nqQAH*#b%~7>xuD(UnGt2?f za(SD;{G^Gd?R6ZS=Jj8g7Se(r2``tL`*ug%*MjYLTd>(b#Zq?J{Uo1p!qJJCnIr1V zsm?nO7^0(~U^Sl;-qkw}vHZpZEW{Y)wcEW`pe}!TMAuFtJ za<3K^_ALa9zZ}T);c4FpnuOFJVViol3#t#>+YJm|(0WqUV#KvD^T=QOXN_h!@JH@jcm|JoWh71hNTu<3|aMI_N{{r~P80KV{PloQX}XHWAZ;R0Qs}Mbzk! zd!FY*HL{2?PJ}hRbN~A3BM(}mu0n~y+Gq9QC!JYBfe$uSZMYBb(Jb+ei-!=>_>?f1 zrdXKr&9fdZ-g~!&nIzck{$Ihv+oG96vmw=*%1~ShG261Ht@`gvU(I21zMY_Rk4-+wBLdE?|KXmzx(4I-{K8bzX7fhHg`S3+^ELT`dvmWtNFv}L6X6KCAksr3R{HPdkE*!i`K zr7(zakj+5P`?}%jbItH1@JvwD!W?uU`6n)eh0#gNENJMu|6m9LeM;bcN;5{Ec#H?x zU-7(Atn@xJX;D5-=}rB#eWyM=2fd;r@PcpD%{Fc!b^V!bMx10D<;RBsi_0e?YA4*5 zN}+Wz>u!RK!}dMOmN+ma%PG(X(a!9nm~=+{0A`EbyxU=Kvm<}87ZPeCTgde=L(R{q z^-|2xvhw^oF~?Py{2TLVOLX0Xx1U3r!)9iw)Kj#JxQT-j14@HvH?K9+lj(T}Xh^$- z7?(7)<1E$lh_;^+bKd?5QNNY0%btLDB}-j*|MB`RHUFyqd~6c$>)wWDpy+464!*&@ z^!VLFkRG>`?$Nae9n*Z?6rvQUT-FAcAZ4!T-gh|*vpg^iuV?St*Kesx|6C?9#YsZW zX|~Qbt%Kp<2_c`KkKY+WzHb7>yaKv+UHyk>6qni;4IM|29UHYfJ`{M>(fbVCaYUaQ zqduhBp0c@$U7>7+*&*cl4@*vY&{q`^)B%JD|G*sr7%+z4nLX86g6@XU=(j{)?L~A= z?>(__%8Db_J%zps{@>?Y{7%d3WE?Rcv=s!rAryg-L~P7yztm=f9uKx{=;;^#@6Qvv zckKOt;N5~tIc+UI>l|zNwx>Zo6V37^)hI&rUPe=hu6YQ!B@(ED5nI9;>IXV;vIo zo`F((m%*Qje&a%O9%T=HORw{^sDCL=;8grzpmLx*vM_wAbhn>eCx7l+mV zzS!0$2r*9tK;eH71c)ZR*ARN>|L=k^{0@oyyE}ROc5^Exk&i`-+ zPuU{D`S=~v6i?aIp_5dh6CB!|TmN;Wqha%5n-g(#@H=3LaUohiC3)Q;V4I*J0VWJ$ zZ?RtR=gE>9CaRlG*g#Os!l90}pl>In<;}stb*1jyUL?_;3wvMz8m1o9*Zf(#2Wig+ zVQdxf_7lEuv^z(nl_BTi1mbBc(Trf({MGG6~z6;C&K1}0`_lj5j z9@5e~`e|>q52ZI1pHs|yc1WcwkntD#KFE#CM*LEqmsy!wgBnKT*ZEE;gx7BThJ<;Q z-Wri$rp|l|iHF>9OLAKT^yCOp+1*Cx>=xMGgVb|R>~YKmAs-liKSc)zlA9#|UjRw~ zwf~5+`WkpkxwN~CxH)-0a{AptiytnJOSy4#;6bUZCfY_F`MR-V!XL*&;fvE9G=rDK b^(tKR(*Vx$`u$7HS@=1.27,<2"] +build-backend = "hatchling.build" + +[project] +name = "phaseprobe" +version = "0.1.0" +description = "Find reproducible qualitative simulation transitions and turn them into regression tests." +readme = "README.md" +requires-python = ">=3.10" +license = "Apache-2.0" +authors = [{ name = "Ali" }] +keywords = ["simulation", "testing", "dynamical-systems", "reproducibility", "regression-testing"] +classifiers = [ + "Development Status :: 3 - Alpha", + "Environment :: Console", + "License :: OSI Approved :: Apache Software License", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX :: Linux", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Topic :: Scientific/Engineering", + "Topic :: Software Development :: Testing", +] +dependencies = [] + +[project.optional-dependencies] +dev = [ + "build==1.3.0", + "mypy==1.17.1", + "pytest==8.4.1", + "pytest-cov==6.2.1", + "ruff==0.12.7", +] +demo = ["Pillow==11.3.0"] + +[project.scripts] +phaseprobe = "phaseprobe.cli:main" + +[project.urls] +Documentation = "https://github.com/aliengineering-byte/phaseprobe#readme" +Issues = "https://github.com/aliengineering-byte/phaseprobe/issues" +Source = "https://github.com/aliengineering-byte/phaseprobe" + +[tool.hatch.build.targets.wheel] +packages = ["src/phaseprobe"] + +[tool.hatch.build.targets.sdist] +include = [ + "/.github", + "/assets", + "/src", + "/tests", + "/examples", + "/scripts", + "/README.md", + "/LICENSE", + "/AGENTS.md", + "/CHANGELOG.md", + "/CONTRIBUTING.md", + "/CODE_OF_CONDUCT.md", + "/SECURITY.md", + "/CITATION.cff", + "/ARCHITECTURE.md", + "/SCIENTIFIC_METHODS.md", + "/PRIOR_ART.md", + "/LIMITATIONS.md", + "/ROADMAP.md", + "/pyproject.toml", + "/uv.lock", +] + +[tool.pytest.ini_options] +addopts = "--strict-config --strict-markers -ra" +testpaths = ["tests"] +markers = [ + "numerical: deterministic numerical validation", + "integration: CLI, replay, packaging, or generated-test integration", +] + +[tool.coverage.run] +branch = true +source = ["phaseprobe"] + +[tool.coverage.report] +fail_under = 85 +show_missing = true + +[tool.mypy] +python_version = "3.10" +strict = true +warn_unreachable = true +mypy_path = "src" + +[tool.ruff] +target-version = "py310" +line-length = 100 +extend-exclude = [ + ".cache", + ".phaseprobe", + ".research_private", + ".tools", + ".venv", + "build", + "dist", +] + +[tool.ruff.lint] +select = ["E", "F", "I", "B", "UP", "SIM", "RUF"] +ignore = ["E501"] diff --git a/scripts/check_links.py b/scripts/check_links.py new file mode 100644 index 0000000..3659d57 --- /dev/null +++ b/scripts/check_links.py @@ -0,0 +1,58 @@ +"""Deterministic local Markdown-link and external-link inventory check.""" + +from __future__ import annotations + +import json +import re +import sys +from pathlib import Path +from urllib.parse import unquote, urlparse + +ROOT = Path(__file__).resolve().parents[1] +EXCLUDED = {".cache", ".git", ".phaseprobe", ".research_private", ".tools", ".venv"} +LINK = re.compile(r"!?\[[^\]]*\]\(([^)]+)\)") + + +def main() -> int: + issues: list[str] = [] + external: set[str] = set() + markdown_files = [ + path + for path in ROOT.rglob("*.md") + if not any(part in EXCLUDED for part in path.relative_to(ROOT).parts) + ] + for path in markdown_files: + text = path.read_text(encoding="utf-8") + for raw in LINK.findall(text): + target = raw.strip().strip("<>") + parsed = urlparse(target) + if parsed.scheme in {"http", "https"}: + external.add(target) + continue + if parsed.scheme or target.startswith("#"): + continue + local_text = unquote(target.split("#", 1)[0]) + if not local_text: + continue + local_path = (path.parent / local_text).resolve() + try: + local_path.relative_to(ROOT) + except ValueError: + issues.append(f"{path.relative_to(ROOT)}: link escapes repository: {target}") + continue + if not local_path.exists(): + issues.append(f"{path.relative_to(ROOT)}: missing local target: {target}") + result = { + "schema_version": "1.0", + "status": "PASS" if not issues else "FAIL", + "markdown_files": len(markdown_files), + "external_links_inventoried": len(external), + "note": "External links are inventoried but not fetched in CI to avoid network-flaky gates.", + "issues": issues, + } + print(json.dumps(result, indent=2, sort_keys=True)) + return 0 if not issues else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/generate_demo.py b/scripts/generate_demo.py new file mode 100644 index 0000000..4c2034d --- /dev/null +++ b/scripts/generate_demo.py @@ -0,0 +1,180 @@ +"""Run the real quick-start workflow and render repository-native terminal assets.""" + +from __future__ import annotations + +import subprocess +import sys +import tempfile +import textwrap +import time +from pathlib import Path + +from PIL import Image, ImageDraw, ImageFont + +ROOT = Path(__file__).resolve().parents[1] +ASSETS = ROOT / "assets" +CACHE = ROOT / ".cache" +WIDTH = 1200 +HEIGHT = 700 + + +def run(command: list[str], cwd: Path) -> tuple[str, float]: + started = time.perf_counter() + completed = subprocess.run( + command, + cwd=cwd, + check=False, + capture_output=True, + text=True, + timeout=90, + ) + elapsed = time.perf_counter() - started + if completed.returncode != 0: + raise RuntimeError(completed.stdout + completed.stderr) + return completed.stdout.strip(), elapsed + + +def font(size: int) -> ImageFont.FreeTypeFont | ImageFont.ImageFont: + for name in ("CascadiaMono.ttf", "consola.ttf", "DejaVuSansMono.ttf"): + try: + return ImageFont.truetype(name, size) + except OSError: + continue + return ImageFont.load_default() + + +def frame(lines: list[str], active: int) -> Image.Image: + image = Image.new("RGB", (WIDTH, HEIGHT), "#0b171d") + draw = ImageDraw.Draw(image) + draw.rounded_rectangle((24, 24, WIDTH - 24, HEIGHT - 24), radius=18, fill="#10252d") + draw.ellipse((48, 48, 64, 64), fill="#ef5b35") + draw.ellipse((74, 48, 90, 64), fill="#f2c14e") + draw.ellipse((100, 48, 116, 64), fill="#52b788") + draw.text( + (142, 45), "PhaseProbe · deterministic simulation evidence", font=font(22), fill="#d9f2ec" + ) + y = 92 + terminal_font = font(20) + for index, line in enumerate(lines[:24]): + color = "#efb366" if line.startswith("$") else "#d9f2ec" + if index == active: + draw.rounded_rectangle((42, y - 3, WIDTH - 42, y + 27), radius=5, fill="#173b46") + draw.text((52, y), line, font=terminal_font, fill=color) + y += 25 + draw.text( + (52, HEIGHT - 58), + "bounded search → replay fixture → generated pytest → pass", + font=font(19), + fill="#62c6cf", + ) + return image + + +def main() -> int: + ASSETS.mkdir(exist_ok=True) + CACHE.mkdir(exist_ok=True) + with tempfile.TemporaryDirectory(prefix="phaseprobe-demo-", dir=CACHE) as raw_temp: + workspace = Path(raw_temp) + output_root = workspace / ".phaseprobe" / "runs" + scan_output, scan_time = run( + [ + sys.executable, + "-m", + "phaseprobe", + "scan", + "--example", + "logistic", + "--output-root", + str(output_root), + ], + ROOT, + ) + run_directory = max(output_root.iterdir(), key=lambda path: path.stat().st_mtime_ns) + replay_output, replay_time = run( + [sys.executable, "-m", "phaseprobe", "replay", str(run_directory / "replay.json")], + ROOT, + ) + generated_directory = workspace / "tests" / "generated" + generate_output, generate_time = run( + [ + sys.executable, + "-m", + "phaseprobe", + "generate-test", + str(run_directory / "replay.json"), + "--output-directory", + str(generated_directory), + ], + ROOT, + ) + generated_test = next(generated_directory.glob("test_*_transition.py")) + pytest_output, pytest_time = run( + [sys.executable, "-m", "pytest", "-q", generated_test.name], + generated_directory, + ) + + transcript = "\n\n".join( + ( + "$ phaseprobe scan --example logistic\n" + scan_output, + "$ phaseprobe replay .phaseprobe/runs//replay.json\n" + replay_output, + "$ phaseprobe generate-test .phaseprobe/runs//replay.json\n" + + generate_output, + "$ python -m pytest -q tests/generated\n" + pytest_output, + ) + ) + transcript = transcript.replace(str(run_directory), ".phaseprobe/runs/") + transcript = transcript.replace(str(generated_directory), "tests/generated") + transcript = transcript.replace("\\", "/") + timing = ( + f"\n\nMeasured generation run: scan={scan_time:.3f}s, replay={replay_time:.3f}s, " + f"generate={generate_time:.3f}s, pytest={pytest_time:.3f}s." + ) + (ASSETS / "demo-session.txt").write_text(transcript + timing + "\n", encoding="utf-8") + + selected = [ + "$ phaseprobe scan --example logistic", + "QUALITATIVE TRANSITION FOUND", + "Model: logistic-map", + "Stable bracket: refined and repeated", + "Baseline regime: period-2", + "Changed regime: period-4", + "Replay: .phaseprobe/runs//replay.json", + "", + "$ phaseprobe replay .phaseprobe/runs//replay.json", + "REPLAY VERIFIED", + "baseline: classification=True, trace-hash=True", + "changed: classification=True, trace-hash=True", + "", + "$ phaseprobe generate-test .phaseprobe/runs//replay.json", + "PYTEST REGRESSION GENERATED", + "Test: tests/generated/test_logistic_map_transition.py", + "", + "$ python -m pytest -q tests/generated", + ". [100%]", + "1 passed", + ] + images = [frame(selected[: limit + 1], limit) for limit in (1, 6, 11, 15, 19)] + images[-1].save(ASSETS / "demo-static.png", optimize=True) + images[0].save( + ASSETS / "demo.gif", + save_all=True, + append_images=images[1:], + duration=[900, 1200, 1200, 1200, 2200], + loop=0, + optimize=True, + ) + print( + textwrap.dedent( + f"""\ + Demo assets generated from real commands: + {ASSETS / "demo.gif"} + {ASSETS / "demo-static.png"} + {ASSETS / "demo-session.txt"} + """ + ).strip() + ) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/hygiene.py b/scripts/hygiene.py new file mode 100644 index 0000000..7d138d5 --- /dev/null +++ b/scripts/hygiene.py @@ -0,0 +1,135 @@ +"""Repository privacy, secret, large-file, and tracked-output gate.""" + +from __future__ import annotations + +import json +import re +import subprocess +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +EXCLUDED_PARTS = { + ".cache", + ".git", + ".mypy_cache", + ".phaseprobe", + ".pytest_cache", + ".research_private", + ".ruff_cache", + ".tools", + ".venv", + "__pycache__", + "build", + "dist", + "htmlcov", +} +FORBIDDEN_TRACKED_PARTS = { + ".phaseprobe", + ".research_private", + ".tools", + ".venv", + "build", + "dist", + "htmlcov", +} +TEXT_SUFFIXES = { + ".cff", + ".css", + ".html", + ".ini", + ".json", + ".md", + ".py", + ".toml", + ".txt", + ".yaml", + ".yml", +} +PRIVATE_TERMS = ( + "Ali" + "Prime", + "AgentReliability" + "Lab", + "Resili" + "Replay", + "AI-" + "Workbench", +) +SECRET_PATTERNS = { + "private key": re.compile(r"-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----"), + "GitHub token": re.compile(r"\b(?:ghp|github_pat)_[A-Za-z0-9_]{20,}\b"), + "AWS access key": re.compile(r"\bAKIA[0-9A-Z]{16}\b"), + "assigned secret": re.compile( + r"(?i)\b(?:api[_-]?key|secret|password|token)\b\s*[:=]\s*['\"][^'\"]{8,}['\"]" + ), +} +EMAIL = re.compile(r"\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b", re.IGNORECASE) +WINDOWS_PATH = re.compile(r"\b[A-Za-z]:\\") + + +def tracked_files() -> list[Path]: + """Use Git's index when available; otherwise walk only public project content.""" + + completed = subprocess.run( + ["git", "-C", str(ROOT), "ls-files", "-z"], + check=False, + capture_output=True, + ) + if completed.returncode == 0 and completed.stdout: + return [ROOT / item.decode("utf-8") for item in completed.stdout.split(b"\0") if item] + return [ + path + for path in ROOT.rglob("*") + if path.is_file() + and not any(part in EXCLUDED_PARTS for part in path.relative_to(ROOT).parts) + ] + + +def main() -> int: + files = tracked_files() + issues: list[str] = [] + large_files: list[dict[str, object]] = [] + for path in files: + relative = path.relative_to(ROOT) + if any(part in FORBIDDEN_TRACKED_PARTS for part in relative.parts): + issues.append(f"tracked generated/private path: {relative.as_posix()}") + if path.suffix.lower() == ".pdf": + issues.append(f"tracked PDF: {relative.as_posix()}") + size = path.stat().st_size + if size > 1_000_000: + large_files.append({"path": relative.as_posix(), "bytes": size}) + if size > 5_000_000: + issues.append(f"tracked file exceeds 5 MB: {relative.as_posix()} ({size} bytes)") + if path.suffix.lower() not in TEXT_SUFFIXES and path.name not in { + ".gitignore", + "LICENSE", + }: + continue + try: + text = path.read_text(encoding="utf-8") + except UnicodeDecodeError: + issues.append(f"declared text file is not UTF-8: {relative.as_posix()}") + continue + if EMAIL.search(text): + issues.append(f"personal email-like string: {relative.as_posix()}") + if WINDOWS_PATH.search(text): + issues.append(f"absolute Windows path: {relative.as_posix()}") + for term in PRIVATE_TERMS: + if term in text: + issues.append(f"private neighboring-workspace term {term!r}: {relative.as_posix()}") + for label, pattern in SECRET_PATTERNS.items(): + if pattern.search(text): + issues.append(f"possible {label}: {relative.as_posix()}") + result = { + "schema_version": "1.0", + "status": "PASS" if not issues else "FAIL", + "files_scanned": len(files), + "author_identity_policy": "public author metadata is Ali only; no email-like strings found" + if not any("email" in issue for issue in issues) + else "failed", + "large_files_over_1mb": large_files, + "issues": issues, + } + print(json.dumps(result, indent=2, sort_keys=True)) + return 0 if not issues else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/src/phaseprobe/__init__.py b/src/phaseprobe/__init__.py new file mode 100644 index 0000000..f5cb0f6 --- /dev/null +++ b/src/phaseprobe/__init__.py @@ -0,0 +1,7 @@ +"""PhaseProbe: deterministic transition discovery for simulation regression tests.""" + +from phaseprobe.types import InvariantResult, ModelAdapter, TracePoint + +__all__ = ["InvariantResult", "ModelAdapter", "TracePoint", "__version__"] + +__version__ = "0.1.0" diff --git a/src/phaseprobe/__main__.py b/src/phaseprobe/__main__.py new file mode 100644 index 0000000..6173616 --- /dev/null +++ b/src/phaseprobe/__main__.py @@ -0,0 +1,5 @@ +"""Allow ``python -m phaseprobe``.""" + +from phaseprobe.cli import main + +raise SystemExit(main()) diff --git a/src/phaseprobe/artifacts.py b/src/phaseprobe/artifacts.py new file mode 100644 index 0000000..7932879 --- /dev/null +++ b/src/phaseprobe/artifacts.py @@ -0,0 +1,124 @@ +"""Bounded run-directory persistence and artifact manifests.""" + +from __future__ import annotations + +import hashlib +import json +from dataclasses import dataclass +from datetime import datetime, timezone +from pathlib import Path + +from phaseprobe.config import canonical_json +from phaseprobe.engine import ProbeOutcome, SimulationResult +from phaseprobe.replay import fixture_payload +from phaseprobe.reporting import html_report, json_report + + +def _sha256(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as handle: + for chunk in iter(lambda: handle.read(65_536), b""): + digest.update(chunk) + return digest.hexdigest() + + +@dataclass(frozen=True, slots=True) +class ArtifactBundle: + """Paths written for one bounded PhaseProbe execution.""" + + run_directory: Path + run_json: Path + findings_json: Path + replay_json: Path + trace_jsonl: Path + report_html: Path + manifest_json: Path + + +def _trace_lines(label: str, result: SimulationResult) -> list[str]: + lines: list[str] = [] + for point in result.trace: + payload = { + "series": label, + "step": point.step, + "time": point.time, + "state": list(point.state), + "observations": dict(point.observations), + } + lines.append(canonical_json(payload)) + return lines + + +def write_artifacts(outcome: ProbeOutcome, output_root: Path) -> ArtifactBundle: + """Write finite evidence files and a hash manifest under one run directory.""" + + output_root.mkdir(parents=True, exist_ok=True) + signature = hashlib.sha256(canonical_json(outcome.as_dict()).encode("utf-8")).hexdigest()[:8] + timestamp = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ") + run_directory = output_root / f"{timestamp}-{signature}" + suffix = 1 + while run_directory.exists(): + run_directory = output_root / f"{timestamp}-{signature}-{suffix}" + suffix += 1 + run_directory.mkdir() + + run_json = run_directory / "run.json" + findings_json = run_directory / "findings.json" + replay_json = run_directory / "replay.json" + trace_jsonl = run_directory / "trace.jsonl" + report_html = run_directory / "report.html" + manifest_json = run_directory / "manifest.json" + + data = outcome.as_dict() + run_json.write_text(json_report(data), encoding="utf-8") + findings_json.write_text( + json.dumps( + { + "schema_version": "1.0", + "status": outcome.status, + "finding": dict(outcome.finding) if outcome.finding is not None else None, + "reproducible": outcome.reproducible, + }, + allow_nan=False, + indent=2, + sort_keys=True, + ) + + "\n", + encoding="utf-8", + ) + replay_json.write_text( + json.dumps(fixture_payload(outcome), allow_nan=False, indent=2, sort_keys=True) + "\n", + encoding="utf-8", + ) + trace_lines = _trace_lines("baseline", outcome.baseline) + if outcome.changed is not None: + trace_lines.extend(_trace_lines("changed", outcome.changed)) + trace_jsonl.write_text("\n".join(trace_lines) + "\n", encoding="utf-8") + report_html.write_text(html_report(data), encoding="utf-8") + + files = [run_json, findings_json, replay_json, trace_jsonl, report_html] + manifest = { + "schema_version": "1.0", + "run_id": run_directory.name, + "bounded_trace": { + "baseline_points": len(outcome.baseline.trace), + "changed_points": len(outcome.changed.trace) if outcome.changed is not None else 0, + "configured_cap_per_series": outcome.baseline.settings.trace_cap, + }, + "files": { + path.name: {"bytes": path.stat().st_size, "sha256": _sha256(path)} for path in files + }, + } + manifest_json.write_text( + json.dumps(manifest, allow_nan=False, indent=2, sort_keys=True) + "\n", + encoding="utf-8", + ) + return ArtifactBundle( + run_directory=run_directory, + run_json=run_json, + findings_json=findings_json, + replay_json=replay_json, + trace_jsonl=trace_jsonl, + report_html=report_html, + manifest_json=manifest_json, + ) diff --git a/src/phaseprobe/cli.py b/src/phaseprobe/cli.py new file mode 100644 index 0000000..f705243 --- /dev/null +++ b/src/phaseprobe/cli.py @@ -0,0 +1,234 @@ +"""PhaseProbe command-line interface and stable exit semantics.""" + +from __future__ import annotations + +import argparse +import json +import sys +from collections.abc import Callable, Sequence +from pathlib import Path + +from phaseprobe import __version__ +from phaseprobe.artifacts import ArtifactBundle, write_artifacts +from phaseprobe.config import EXAMPLE_FILES, ProbeConfig, load_config, load_example +from phaseprobe.engine import ProbeOutcome, run_check, run_perturb, run_scan +from phaseprobe.errors import ( + ConfigurationError, + ExitCode, + IntegrityError, + NumericalFailure, + PhaseProbeError, +) +from phaseprobe.generate import generate_regression_test +from phaseprobe.replay import verify_replay +from phaseprobe.reporting import json_report, regenerate_reports, terminal_report + + +def _add_config_source(parser: argparse.ArgumentParser) -> None: + source = parser.add_mutually_exclusive_group(required=True) + source.add_argument("--config", type=Path, help="versioned JSON configuration") + source.add_argument( + "--example", choices=sorted(EXAMPLE_FILES), help="built-in deterministic example" + ) + parser.add_argument( + "--output-root", + type=Path, + default=Path(".phaseprobe") / "runs", + help="bounded run directory root (default: .phaseprobe/runs)", + ) + parser.add_argument("--json", action="store_true", help="emit versioned JSON to stdout") + + +def build_parser() -> argparse.ArgumentParser: + """Build the public CLI parser.""" + + parser = argparse.ArgumentParser( + prog="phaseprobe", + description="Find bounded simulation transitions and turn them into deterministic tests.", + ) + parser.add_argument("--version", action="version", version=f"phaseprobe {__version__}") + commands = parser.add_subparsers(dest="command", required=True) + + scan = commands.add_parser("scan", help="scan one parameter for adjacent qualitative changes") + _add_config_source(scan) + scan.add_argument( + "--fail-on-finding", + action="store_true", + help="return exit 1 when a transition is found", + ) + + perturb = commands.add_parser( + "perturb", help="search bounded initial-state perturbations with twin trajectories" + ) + _add_config_source(perturb) + perturb.add_argument( + "--fail-on-finding", + action="store_true", + help="return exit 1 when a sensitive perturbation is found", + ) + + check = commands.add_parser("check", help="run a declared policy for CI enforcement") + _add_config_source(check) + + replay = commands.add_parser("replay", help="re-execute an integrity-protected replay fixture") + replay.add_argument("fixture", type=Path) + replay.add_argument("--json", action="store_true") + + generate = commands.add_parser( + "generate-test", help="generate fixed-template pytest from validated evidence" + ) + generate.add_argument("fixture", type=Path) + generate.add_argument("--output-directory", type=Path, default=Path("tests") / "generated") + generate.add_argument("--json", action="store_true") + + report = commands.add_parser( + "report", help="regenerate terminal, JSON, and offline HTML evidence" + ) + report.add_argument("run_directory", type=Path) + report.add_argument("--format", choices=("terminal", "json", "html", "all"), default="all") + return parser + + +def _config_from_args(args: argparse.Namespace) -> ProbeConfig: + config_path = getattr(args, "config", None) + example = getattr(args, "example", None) + if isinstance(config_path, Path): + return load_config(config_path) + if isinstance(example, str): + return load_example(example) + raise ConfigurationError("a configuration or built-in example is required") + + +def _with_artifacts(outcome: ProbeOutcome, bundle: ArtifactBundle) -> dict[str, object]: + data = outcome.as_dict() + data["artifacts"] = { + "run_directory": str(bundle.run_directory), + "replay": str(bundle.replay_json), + "html_report": str(bundle.report_html), + "manifest": str(bundle.manifest_json), + } + return data + + +def _run_evidence_command( + args: argparse.Namespace, execute: Callable[[ProbeConfig], ProbeOutcome] +) -> int: + config = _config_from_args(args) + outcome = execute(config) + output_root = args.output_root + if not isinstance(output_root, Path): + raise ConfigurationError("output root must be a path") + bundle = write_artifacts(outcome, output_root) + data = _with_artifacts(outcome, bundle) + if bool(getattr(args, "json", False)): + sys.stdout.write(json_report(data)) + else: + print(terminal_report(data, replay=str(bundle.replay_json))) + print(f"HTML report: {bundle.report_html}") + if outcome.policy_failed: + return int(ExitCode.POLICY_FAILED) + if bool(getattr(args, "fail_on_finding", False)) and outcome.finding is not None: + return int(ExitCode.POLICY_FAILED) + return int(ExitCode.OK) + + +def _replay_command(args: argparse.Namespace) -> int: + fixture = args.fixture + if not isinstance(fixture, Path): + raise ConfigurationError("fixture must be a path") + verification = verify_replay(fixture) + data = verification.as_dict() + if bool(getattr(args, "json", False)): + sys.stdout.write(json_report(data)) + else: + print(str(data["status"])) + print() + print(f"Model: {verification.model}") + for comparison in verification.comparisons: + print( + f"{comparison['series']}: classification={comparison['classification_match']}, " + f"trace-hash={comparison['trace_hash_match']}, " + f"model-identity={comparison['model_identity_match']}" + ) + return int(ExitCode.OK if verification.ok else ExitCode.POLICY_FAILED) + + +def _generate_command(args: argparse.Namespace) -> int: + fixture = args.fixture + output_directory = args.output_directory + if not isinstance(fixture, Path) or not isinstance(output_directory, Path): + raise ConfigurationError("fixture and output directory must be paths") + generated = generate_regression_test(fixture, output_directory) + data: dict[str, object] = { + "schema_version": "1.0", + "status": "PYTEST REGRESSION GENERATED", + "test": str(generated.test_path), + "fixture": str(generated.fixture_path), + } + if bool(getattr(args, "json", False)): + sys.stdout.write(json_report(data)) + else: + print("PYTEST REGRESSION GENERATED") + print() + print(f"Test: {generated.test_path}") + print(f"Replay fixture: {generated.fixture_path}") + return int(ExitCode.OK) + + +def _report_command(args: argparse.Namespace) -> int: + run_directory = args.run_directory + report_format = args.format + if not isinstance(run_directory, Path) or not isinstance(report_format, str): + raise ConfigurationError("invalid report arguments") + json_path, html_path = regenerate_reports(run_directory) + parsed = json.loads((run_directory / "run.json").read_text(encoding="utf-8")) + if not isinstance(parsed, dict): + raise ConfigurationError("run.json must contain an object") + data: dict[str, object] = parsed + if report_format in {"terminal", "all"}: + print(terminal_report(data, replay=str(run_directory / "replay.json"))) + if report_format in {"json", "all"}: + if report_format == "json": + sys.stdout.write(json_report(data)) + else: + print(f"JSON report: {json_path}") + if report_format in {"html", "all"}: + print(f"HTML report: {html_path}") + return int(ExitCode.OK) + + +def main(argv: Sequence[str] | None = None) -> int: + """Run the CLI and translate expected defects into the documented exit contract.""" + + parser = build_parser() + args = parser.parse_args(argv) + try: + if args.command == "scan": + return _run_evidence_command(args, run_scan) + if args.command == "perturb": + return _run_evidence_command(args, run_perturb) + if args.command == "check": + return _run_evidence_command(args, run_check) + if args.command == "replay": + return _replay_command(args) + if args.command == "generate-test": + return _generate_command(args) + if args.command == "report": + return _report_command(args) + raise ConfigurationError(f"unknown command {args.command!r}") + except NumericalFailure as exc: + print(f"NUMERICAL FAILURE: {exc}", file=sys.stderr) + return int(ExitCode.NUMERICAL_FAILURE) + except (ConfigurationError, IntegrityError, ValueError, OSError) as exc: + print(f"INVALID INPUT: {exc}", file=sys.stderr) + return int(ExitCode.INVALID_INPUT) + except PhaseProbeError as exc: + print(f"PHASEPROBE ERROR: {exc}", file=sys.stderr) + return int(ExitCode.INTERNAL_ERROR) + except Exception as exc: # pragma: no cover - final CLI containment boundary + print(f"INTERNAL PHASEPROBE DEFECT: {type(exc).__name__}: {exc}", file=sys.stderr) + return int(ExitCode.INTERNAL_ERROR) + + +if __name__ == "__main__": # pragma: no cover + raise SystemExit(main()) diff --git a/src/phaseprobe/config.py b/src/phaseprobe/config.py new file mode 100644 index 0000000..e852354 --- /dev/null +++ b/src/phaseprobe/config.py @@ -0,0 +1,115 @@ +"""Versioned JSON configuration loading and deterministic serialization.""" + +from __future__ import annotations + +import json +from collections.abc import Mapping +from dataclasses import dataclass +from importlib import resources +from pathlib import Path +from typing import Any, cast + +from phaseprobe.errors import ConfigurationError + +CONFIG_SCHEMA_VERSION = "1.0" + + +def canonical_json(value: object) -> str: + """Serialize configuration data deterministically for hashing and replay.""" + + return json.dumps(value, allow_nan=False, separators=(",", ":"), sort_keys=True) + + +def _as_object(value: Any, context: str) -> dict[str, object]: + if not isinstance(value, dict): + raise ConfigurationError(f"{context} must be a JSON object") + return cast(dict[str, object], value) + + +@dataclass(frozen=True, slots=True) +class ProbeConfig: + """Validated top-level configuration with typed access helpers.""" + + data: Mapping[str, object] + source: str + + @property + def model(self) -> str: + value = self.data.get("model") + if not isinstance(value, str) or not value: + raise ConfigurationError("model must be a non-empty string") + return value + + @property + def seed(self) -> int: + value = self.data.get("seed", 0) + if not isinstance(value, int) or isinstance(value, bool) or value < 0: + raise ConfigurationError("seed must be a non-negative integer") + return value + + def section(self, name: str, *, required: bool = True) -> Mapping[str, object]: + value = self.data.get(name) + if value is None and not required: + return {} + if not isinstance(value, dict): + raise ConfigurationError(f"{name} must be a JSON object") + return cast(Mapping[str, object], value) + + def string(self, name: str, default: str | None = None) -> str: + value = self.data.get(name, default) + if not isinstance(value, str): + raise ConfigurationError(f"{name} must be a string") + return value + + +def parse_config(text: str, source: str) -> ProbeConfig: + """Parse and validate a versioned JSON configuration.""" + + try: + parsed: Any = json.loads(text) + except json.JSONDecodeError as exc: + raise ConfigurationError(f"invalid JSON in {source}: {exc}") from exc + data = _as_object(parsed, source) + version = data.get("schema_version") + if version != CONFIG_SCHEMA_VERSION: + raise ConfigurationError( + f"unsupported configuration schema {version!r}; expected {CONFIG_SCHEMA_VERSION!r}" + ) + config = ProbeConfig(data=data, source=source) + _ = config.model + _ = config.seed + return config + + +def load_config(path: Path) -> ProbeConfig: + """Load a UTF-8 JSON configuration from a user-selected path.""" + + try: + text = path.read_text(encoding="utf-8") + except OSError as exc: + raise ConfigurationError(f"cannot read configuration {path}: {exc}") from exc + return parse_config(text, str(path)) + + +EXAMPLE_FILES: Mapping[str, str] = { + "logistic": "logistic-scan.json", + "logistic-negative": "logistic-negative.json", + "lorenz": "lorenz-perturb.json", + "lorenz-negative": "lorenz-negative.json", + "predator-prey": "predator-prey-check.json", + "predator-prey-negative": "predator-prey-negative.json", + "toggle": "toggle-perturb.json", + "toggle-negative": "toggle-negative.json", +} + + +def load_example(name: str) -> ProbeConfig: + """Load one of the immutable examples embedded in the installed wheel.""" + + filename = EXAMPLE_FILES.get(name) + if filename is None: + choices = ", ".join(sorted(EXAMPLE_FILES)) + raise ConfigurationError(f"unknown example {name!r}; choose one of: {choices}") + package = resources.files("phaseprobe.data.examples") + text = package.joinpath(filename).read_text(encoding="utf-8") + return parse_config(text, f"built-in example {name}") diff --git a/src/phaseprobe/data/__init__.py b/src/phaseprobe/data/__init__.py new file mode 100644 index 0000000..309d8d7 --- /dev/null +++ b/src/phaseprobe/data/__init__.py @@ -0,0 +1 @@ +"""Packaged PhaseProbe data.""" diff --git a/src/phaseprobe/data/examples/__init__.py b/src/phaseprobe/data/examples/__init__.py new file mode 100644 index 0000000..14fc264 --- /dev/null +++ b/src/phaseprobe/data/examples/__init__.py @@ -0,0 +1 @@ +"""Deterministic built-in example configurations.""" diff --git a/src/phaseprobe/data/examples/logistic-negative.json b/src/phaseprobe/data/examples/logistic-negative.json new file mode 100644 index 0000000..edd9863 --- /dev/null +++ b/src/phaseprobe/data/examples/logistic-negative.json @@ -0,0 +1,27 @@ +{ + "schema_version": "1.0", + "model": "logistic-map", + "seed": 17, + "parameters": {"r": 3.2}, + "model_config": {"initial_state": {"x": 0.2}}, + "simulation": { + "steps": 128, + "burn_in": 1500, + "dt": 1.0, + "sample_every": 1, + "trace_cap": 128, + "hard_state_limit": 10.0 + }, + "tolerances": {"period": 1e-9}, + "scan": { + "parameter": "r", + "start": 3.2, + "stop": 3.4, + "points": 5, + "refine_iterations": 6, + "repeatability": 2 + }, + "classification_rule": "Finite-period tail classifier used by the positive logistic example.", + "refinement_rule": "Refine only if an adjacent qualitative class change is observed.", + "invalid_state_policy": "Abort on invalid integration." +} diff --git a/src/phaseprobe/data/examples/logistic-scan.json b/src/phaseprobe/data/examples/logistic-scan.json new file mode 100644 index 0000000..9ddce25 --- /dev/null +++ b/src/phaseprobe/data/examples/logistic-scan.json @@ -0,0 +1,27 @@ +{ + "schema_version": "1.0", + "model": "logistic-map", + "seed": 17, + "parameters": {"r": 3.448}, + "model_config": {"initial_state": {"x": 0.2}}, + "simulation": { + "steps": 256, + "burn_in": 500000, + "dt": 1.0, + "sample_every": 1, + "trace_cap": 256, + "hard_state_limit": 10.0 + }, + "tolerances": {"period": 1e-9}, + "scan": { + "parameter": "r", + "start": 3.448, + "stop": 3.452, + "points": 3, + "refine_iterations": 12, + "repeatability": 2 + }, + "classification_rule": "Smallest detected period in {1,2,4,8,16} whose retained tail repeats within absolute tolerance; otherwise unresolved/aperiodic.", + "refinement_rule": "First adjacent coarse class change, followed by up to twelve deterministic binary probes. Refinement stops rather than relabeling a midpoint that reproduces neither stable endpoint class.", + "invalid_state_policy": "Abort as invalid integration on NaN, infinity, overflow, dimension mismatch, or hard-state-limit breach." +} diff --git a/src/phaseprobe/data/examples/lorenz-negative.json b/src/phaseprobe/data/examples/lorenz-negative.json new file mode 100644 index 0000000..248aadd --- /dev/null +++ b/src/phaseprobe/data/examples/lorenz-negative.json @@ -0,0 +1,30 @@ +{ + "schema_version": "1.0", + "model": "lorenz", + "seed": 23, + "parameters": {"sigma": 10.0, "rho": 28.0, "beta": 2.6666666666666665}, + "model_config": {"initial_state": {"x": 1.0, "y": 1.0, "z": 1.0}}, + "simulation": { + "steps": 1000, + "burn_in": 0, + "dt": 0.005, + "sample_every": 2, + "trace_cap": 500, + "hard_state_limit": 1000.0 + }, + "tolerances": {"state_bound": 100.0}, + "perturb": { + "dimension": "x", + "start": 1e-8, + "stop": 0.0001, + "points": 5, + "scale": "log", + "predicate": "finite-time-divergence", + "divergence_threshold": 1000.0, + "refine_iterations": 4, + "repeatability": 2 + }, + "classification_rule": "Finite-time lobe visitation only.", + "refinement_rule": "No refinement when the declared separation threshold is not reached.", + "invalid_state_policy": "Abort on invalid integration." +} diff --git a/src/phaseprobe/data/examples/lorenz-perturb.json b/src/phaseprobe/data/examples/lorenz-perturb.json new file mode 100644 index 0000000..ebb514e --- /dev/null +++ b/src/phaseprobe/data/examples/lorenz-perturb.json @@ -0,0 +1,30 @@ +{ + "schema_version": "1.0", + "model": "lorenz", + "seed": 23, + "parameters": {"sigma": 10.0, "rho": 28.0, "beta": 2.6666666666666665}, + "model_config": {"initial_state": {"x": 1.0, "y": 1.0, "z": 1.0}}, + "simulation": { + "steps": 8000, + "burn_in": 0, + "dt": 0.005, + "sample_every": 2, + "trace_cap": 4000, + "hard_state_limit": 1000.0 + }, + "tolerances": {"state_bound": 100.0}, + "perturb": { + "dimension": "x", + "start": 1e-8, + "stop": 0.0001, + "points": 5, + "scale": "log", + "predicate": "finite-time-divergence", + "divergence_threshold": 1.0, + "refine_iterations": 8, + "repeatability": 2 + }, + "classification_rule": "Report retained finite-time lobe visitation; the finding predicate is Euclidean twin-trajectory separation, not a qualitative-class change.", + "refinement_rule": "Evaluate ascending logarithmic perturbations; refine a non-trigger/trigger bracket when one exists and repeat the smallest trigger.", + "invalid_state_policy": "Abort on NaN, infinity, overflow, or declared hard-state-limit breach." +} diff --git a/src/phaseprobe/data/examples/predator-prey-check.json b/src/phaseprobe/data/examples/predator-prey-check.json new file mode 100644 index 0000000..043497b --- /dev/null +++ b/src/phaseprobe/data/examples/predator-prey-check.json @@ -0,0 +1,25 @@ +{ + "schema_version": "1.0", + "model": "predator-prey", + "seed": 31, + "parameters": {"alpha": 1.1, "beta": 0.4, "delta": 0.1, "gamma": 0.4}, + "model_config": {"initial_state": {"prey": 10.0, "predator": 5.0}}, + "simulation": { + "steps": 4000, + "burn_in": 0, + "dt": 0.005, + "sample_every": 1, + "trace_cap": 4000, + "hard_state_limit": 1000.0 + }, + "tolerances": {"invariant_drift": 1e-7}, + "check": {"analysis": "invariants"}, + "policy": { + "forbid_findings": true, + "require_finding": false, + "require_invariants": true + }, + "classification_rule": "Both retained populations remain strictly positive, classified as a bounded positive oscillation.", + "refinement_rule": "Fixed-step RK4 at dt=0.005; compare the analytic first integral against its initial retained value.", + "invalid_state_policy": "Abort on invalid integration; fail policy on invariant drift or non-positive population." +} diff --git a/src/phaseprobe/data/examples/predator-prey-negative.json b/src/phaseprobe/data/examples/predator-prey-negative.json new file mode 100644 index 0000000..68cd6c3 --- /dev/null +++ b/src/phaseprobe/data/examples/predator-prey-negative.json @@ -0,0 +1,25 @@ +{ + "schema_version": "1.0", + "model": "predator-prey", + "seed": 31, + "parameters": {"alpha": 1.1, "beta": 0.4, "delta": 0.1, "gamma": 0.4}, + "model_config": {"initial_state": {"prey": 10.0, "predator": 5.0}}, + "simulation": { + "steps": 80, + "burn_in": 0, + "dt": 0.25, + "sample_every": 1, + "trace_cap": 80, + "hard_state_limit": 1000.0 + }, + "tolerances": {"invariant_drift": 1e-7}, + "check": {"analysis": "invariants"}, + "policy": { + "forbid_findings": true, + "require_finding": false, + "require_invariants": true + }, + "classification_rule": "Same boundedness classifier as the refined-solver positive control.", + "refinement_rule": "Deliberately coarse fixed step demonstrates the invariant-drift policy control.", + "invalid_state_policy": "Abort on invalid integration; otherwise fail the declared invariant policy." +} diff --git a/src/phaseprobe/data/examples/toggle-negative.json b/src/phaseprobe/data/examples/toggle-negative.json new file mode 100644 index 0000000..51d4b3a --- /dev/null +++ b/src/phaseprobe/data/examples/toggle-negative.json @@ -0,0 +1,30 @@ +{ + "schema_version": "1.0", + "model": "genetic-toggle", + "seed": 47, + "parameters": {"alpha_u": 3.0, "alpha_v": 3.0, "hill_u": 2.0, "hill_v": 2.0}, + "model_config": {"initial_state": {"u": 3.0, "v": 0.2}}, + "simulation": { + "steps": 1500, + "burn_in": 0, + "dt": 0.02, + "sample_every": 2, + "trace_cap": 750, + "hard_state_limit": 100.0 + }, + "tolerances": {"dominance": 0.05, "state_bound": 10.0}, + "perturb": { + "dimension": "v", + "start": 0.01, + "stop": 0.5, + "points": 6, + "scale": "linear", + "predicate": "classification-change", + "divergence_threshold": 1.0, + "refine_iterations": 6, + "repeatability": 2 + }, + "classification_rule": "Same terminal dominance classifier as the positive toggle example.", + "refinement_rule": "No refinement if all bounded perturbations retain the baseline qualitative class.", + "invalid_state_policy": "Abort on invalid integration." +} diff --git a/src/phaseprobe/data/examples/toggle-perturb.json b/src/phaseprobe/data/examples/toggle-perturb.json new file mode 100644 index 0000000..1049856 --- /dev/null +++ b/src/phaseprobe/data/examples/toggle-perturb.json @@ -0,0 +1,31 @@ +{ + "schema_version": "1.0", + "model": "genetic-toggle", + "seed": 47, + "parameters": {"alpha_u": 3.0, "alpha_v": 3.0, "hill_u": 2.0, "hill_v": 2.0}, + "model_config": {"initial_state": {"u": 3.0, "v": 0.2}}, + "simulation": { + "steps": 1500, + "burn_in": 0, + "dt": 0.02, + "sample_every": 2, + "trace_cap": 750, + "hard_state_limit": 100.0 + }, + "tolerances": {"dominance": 0.05, "state_bound": 10.0}, + "perturb": { + "dimension": "v", + "start": 0.1, + "stop": 4.0, + "points": 9, + "scale": "linear", + "predicate": "classification-change", + "target_classification": "v-dominant", + "divergence_threshold": 1.0, + "refine_iterations": 8, + "repeatability": 2 + }, + "classification_rule": "Classify the terminal state as u-dominant, v-dominant, or balanced using a declared absolute dominance tolerance.", + "refinement_rule": "Ascending bounded v perturbations, then bracketed binary refinement and exact repeatability confirmation.", + "invalid_state_policy": "Abort on invalid integration or concentration outside the hard state limit." +} diff --git a/src/phaseprobe/engine.py b/src/phaseprobe/engine.py new file mode 100644 index 0000000..72c5463 --- /dev/null +++ b/src/phaseprobe/engine.py @@ -0,0 +1,641 @@ +"""Deterministic simulation, bounded search, refinement, and policy evaluation.""" + +from __future__ import annotations + +import hashlib +import math +from collections import deque +from collections.abc import Mapping, Sequence +from dataclasses import dataclass + +from phaseprobe.config import ProbeConfig, canonical_json +from phaseprobe.errors import ConfigurationError, NumericalFailure +from phaseprobe.models import get_model +from phaseprobe.types import InvariantResult, ModelAdapter, State, TracePoint + + +def _number(values: Mapping[str, object], name: str, default: float | None = None) -> float: + value = values.get(name, default) + if not isinstance(value, int | float) or isinstance(value, bool): + raise ConfigurationError(f"{name} must be a number") + result = float(value) + if not math.isfinite(result): + raise ConfigurationError(f"{name} must be finite") + return result + + +def _integer(values: Mapping[str, object], name: str, default: int | None = None) -> int: + value = values.get(name, default) + if not isinstance(value, int) or isinstance(value, bool): + raise ConfigurationError(f"{name} must be an integer") + return value + + +def _boolean(values: Mapping[str, object], name: str, default: bool) -> bool: + value = values.get(name, default) + if not isinstance(value, bool): + raise ConfigurationError(f"{name} must be a boolean") + return value + + +def _string(values: Mapping[str, object], name: str, default: str | None = None) -> str: + value = values.get(name, default) + if not isinstance(value, str): + raise ConfigurationError(f"{name} must be a string") + return value + + +def _numeric_mapping(values: Mapping[str, object], context: str) -> dict[str, float]: + result: dict[str, float] = {} + for name, raw in values.items(): + if not isinstance(raw, int | float) or isinstance(raw, bool): + raise ConfigurationError(f"{context}.{name} must be a number") + number = float(raw) + if not math.isfinite(number): + raise ConfigurationError(f"{context}.{name} must be finite") + result[name] = number + return result + + +@dataclass(frozen=True, slots=True) +class SimulationSettings: + """Bounded execution settings shared by every adapter.""" + + steps: int + burn_in: int + dt: float + sample_every: int + trace_cap: int + hard_state_limit: float + + @classmethod + def from_config(cls, values: Mapping[str, object]) -> SimulationSettings: + settings = cls( + steps=_integer(values, "steps"), + burn_in=_integer(values, "burn_in", 0), + dt=_number(values, "dt", 1.0), + sample_every=_integer(values, "sample_every", 1), + trace_cap=_integer(values, "trace_cap", 2048), + hard_state_limit=_number(values, "hard_state_limit", 1e100), + ) + if settings.steps <= 0 or settings.burn_in < 0: + raise ConfigurationError("simulation steps must be positive and burn_in non-negative") + if settings.dt <= 0.0 or settings.sample_every <= 0: + raise ConfigurationError("simulation dt and sample_every must be positive") + if settings.trace_cap < 16 or settings.trace_cap > 100_000: + raise ConfigurationError("simulation trace_cap must be between 16 and 100000") + if settings.hard_state_limit <= 0.0: + raise ConfigurationError("simulation hard_state_limit must be positive") + return settings + + def as_dict(self) -> dict[str, object]: + return { + "steps": self.steps, + "burn_in": self.burn_in, + "dt": self.dt, + "sample_every": self.sample_every, + "trace_cap": self.trace_cap, + "hard_state_limit": self.hard_state_limit, + } + + +def _invariant_dict(result: InvariantResult) -> dict[str, object]: + return { + "name": result.name, + "passed": result.passed, + "measured": result.measured, + "tolerance": result.tolerance, + "detail": result.detail, + } + + +def _point_dict(point: TracePoint) -> dict[str, object]: + return { + "step": point.step, + "time": point.time, + "state": list(point.state), + "observations": dict(point.observations), + } + + +def trace_hash(trace: Sequence[TracePoint]) -> str: + """Hash the retained canonical trace exactly for deterministic replay.""" + + payload = [_point_dict(point) for point in trace] + return hashlib.sha256(canonical_json(payload).encode("utf-8")).hexdigest() + + +@dataclass(frozen=True, slots=True) +class SimulationResult: + """One bounded deterministic execution.""" + + model: str + model_identity: str + seed: int + parameters: Mapping[str, float] + initial_state: State + final_state: State + settings: SimulationSettings + tolerances: Mapping[str, float] + classification: str + invariants: tuple[InvariantResult, ...] + trace: tuple[TracePoint, ...] + trace_sha256: str + + @property + def invariant_violations(self) -> int: + return sum(not result.passed for result in self.invariants) + + def as_dict(self, *, include_trace: bool = False) -> dict[str, object]: + payload: dict[str, object] = { + "model": self.model, + "model_identity": self.model_identity, + "seed": self.seed, + "parameters": dict(self.parameters), + "initial_state": list(self.initial_state), + "final_state": list(self.final_state), + "simulation": self.settings.as_dict(), + "tolerances": dict(self.tolerances), + "classification": self.classification, + "invariants": [_invariant_dict(result) for result in self.invariants], + "invariant_violations": self.invariant_violations, + "trace_points_retained": len(self.trace), + "trace_sha256": self.trace_sha256, + } + if include_trace: + payload["trace"] = [_point_dict(point) for point in self.trace] + return payload + + +@dataclass(frozen=True, slots=True) +class ProbeOutcome: + """Serializable result of a scan, perturbation search, or declared check.""" + + command: str + status: str + config: ProbeConfig + baseline: SimulationResult + changed: SimulationResult | None + finding: Mapping[str, object] | None + history: tuple[Mapping[str, object], ...] + reproducible: bool + policy_failed: bool = False + + def as_dict(self) -> dict[str, object]: + return { + "schema_version": "1.0", + "command": self.command, + "status": self.status, + "model": self.baseline.model, + "source": self.config.source, + "configuration": dict(self.config.data), + "baseline": self.baseline.as_dict(), + "changed": self.changed.as_dict() if self.changed is not None else None, + "finding": dict(self.finding) if self.finding is not None else None, + "history": [dict(item) for item in self.history], + "reproducible": self.reproducible, + "policy_failed": self.policy_failed, + } + + +def _validate_state( + model: ModelAdapter, state: State, settings: SimulationSettings, step: int +) -> None: + if len(state) != len(model.dimensions): + raise NumericalFailure( + f"solver failure at step {step}: adapter returned {len(state)} dimensions; " + f"expected {len(model.dimensions)}" + ) + for dimension, value in zip(model.dimensions, state, strict=False): + if not math.isfinite(value): + raise NumericalFailure( + f"invalid integration at step {step}: {dimension} is NaN or infinite" + ) + if abs(value) > settings.hard_state_limit: + raise NumericalFailure( + f"invalid integration at step {step}: {dimension} exceeded hard_state_limit" + ) + + +def simulate( + config: ProbeConfig, + *, + parameters_override: Mapping[str, float] | None = None, + initial_override: State | None = None, +) -> SimulationResult: + """Execute one model with explicit seed, parameters, retention, and tolerances.""" + + model = get_model(config.model) + parameters = _numeric_mapping(config.section("parameters"), "parameters") + if parameters_override is not None: + parameters.update(parameters_override) + tolerances = _numeric_mapping(config.section("tolerances", required=False), "tolerances") + settings = SimulationSettings.from_config(config.section("simulation")) + model_config = config.section("model_config", required=False) + state = ( + initial_override + if initial_override is not None + else model.initial_state(model_config, config.seed) + ) + initial = tuple(state) + _validate_state(model, initial, settings, 0) + retained: deque[TracePoint] = deque(maxlen=settings.trace_cap) + total_steps = settings.burn_in + settings.steps + for step in range(1, total_steps + 1): + try: + state = model.step(state, parameters, settings.dt) + except (ArithmeticError, ValueError) as exc: + raise NumericalFailure(f"solver failure at step {step}: {exc}") from exc + _validate_state(model, state, settings, step) + if step > settings.burn_in and (step - settings.burn_in) % settings.sample_every == 0: + retained.append( + TracePoint( + step=step, + time=step * settings.dt, + state=tuple(state), + observations=dict(model.observe(state)), + ) + ) + trace = tuple(retained) + if not trace: + raise ConfigurationError("simulation retention settings produced an empty trace") + classification = model.classify(trace, tolerances) + invariants = tuple(model.invariants(trace, parameters, tolerances)) + return SimulationResult( + model=model.name, + model_identity=model.identity, + seed=config.seed, + parameters=parameters, + initial_state=initial, + final_state=tuple(state), + settings=settings, + tolerances=tolerances, + classification=classification, + invariants=invariants, + trace=trace, + trace_sha256=trace_hash(trace), + ) + + +def _linspace(start: float, stop: float, points: int) -> list[float]: + if points < 2: + raise ConfigurationError("search points must be at least 2") + return [start + (stop - start) * index / (points - 1) for index in range(points)] + + +def _logspace(start: float, stop: float, points: int) -> list[float]: + if start <= 0.0 or stop <= 0.0: + raise ConfigurationError("logarithmic search bounds must be positive") + log_start = math.log(start) + log_stop = math.log(stop) + return [math.exp(value) for value in _linspace(log_start, log_stop, points)] + + +def run_scan(config: ProbeConfig) -> ProbeOutcome: + """Scan a one-dimensional parameter and refine the first adjacent class change.""" + + scan = config.section("scan") + parameter_name = _string(scan, "parameter") + start = _number(scan, "start") + stop = _number(scan, "stop") + points = _integer(scan, "points") + refinements = _integer(scan, "refine_iterations", 12) + repeatability = _integer(scan, "repeatability", 2) + if stop <= start or refinements < 0 or repeatability < 1: + raise ConfigurationError("scan requires stop > start and non-negative refinement") + + history: list[Mapping[str, object]] = [] + runs: list[SimulationResult] = [] + values = _linspace(start, stop, points) + for value in values: + run = simulate(config, parameters_override={parameter_name: value}) + runs.append(run) + history.append({"phase": "coarse", "value": value, "classification": run.classification}) + + transition_index: int | None = None + for index in range(len(runs) - 1): + if runs[index].classification != runs[index + 1].classification: + transition_index = index + break + if transition_index is None: + return ProbeOutcome( + command="scan", + status="NO QUALITATIVE TRANSITION FOUND", + config=config, + baseline=runs[0], + changed=None, + finding=None, + history=tuple(history), + reproducible=True, + ) + + coarse_left = values[transition_index] + coarse_right = values[transition_index + 1] + low = coarse_left + high = coarse_right + left_run = runs[transition_index] + right_run = runs[transition_index + 1] + left_class = left_run.classification + right_class = right_run.classification + unresolved_midpoint: float | None = None + for _ in range(refinements): + midpoint = (low + high) / 2.0 + midpoint_run = simulate(config, parameters_override={parameter_name: midpoint}) + history.append( + { + "phase": "refine", + "low": low, + "high": high, + "value": midpoint, + "classification": midpoint_run.classification, + } + ) + if midpoint_run.classification == left_class: + low = midpoint + left_run = midpoint_run + elif midpoint_run.classification == right_class: + high = midpoint + right_run = midpoint_run + else: + unresolved_midpoint = midpoint + history.append( + { + "phase": "refine-stopped", + "value": midpoint, + "classification": midpoint_run.classification, + "reason": "midpoint did not reproduce either stable endpoint class", + } + ) + break + + reproducible = True + confirmations: list[dict[str, object]] = [] + for side, _expected in (("baseline", left_run), ("changed", right_run)): + hashes: list[str] = [] + classes: list[str] = [] + for _ in range(repeatability): + value = low if side == "baseline" else high + confirmed = simulate(config, parameters_override={parameter_name: value}) + hashes.append(confirmed.trace_sha256) + classes.append(confirmed.classification) + stable = len(set(hashes)) == 1 and len(set(classes)) == 1 + reproducible = reproducible and stable + confirmations.append( + {"side": side, "runs": repeatability, "stable": stable, "trace_sha256": hashes[0]} + ) + finding: dict[str, object] = { + "kind": "qualitative-regime-change", + "parameter": parameter_name, + "coarse_bracket": [coarse_left, coarse_right], + "stable_bracket": [low, high], + "bracket_width": high - low, + "smallest_reproducible_change_found": high - low, + "baseline_regime": left_run.classification, + "changed_regime": right_run.classification, + "classification_rule": config.string("classification_rule"), + "refinement_rule": config.string("refinement_rule"), + "repeatability": confirmations, + "unresolved_midpoint": unresolved_midpoint, + "minimality_statement": "Smallest reproducible separation found by the declared bounded scan and binary refinement; not a proof of a globally minimal perturbation or exact bifurcation point.", + } + return ProbeOutcome( + command="scan", + status="QUALITATIVE TRANSITION FOUND", + config=config, + baseline=left_run, + changed=right_run, + finding=finding, + history=tuple(history), + reproducible=reproducible, + ) + + +def _distance(left: State, right: State) -> float: + return math.sqrt(sum((a - b) ** 2 for a, b in zip(left, right, strict=False))) + + +def _pair_metrics( + baseline: SimulationResult, changed: SimulationResult, delta: float +) -> dict[str, object]: + count = min(len(baseline.trace), len(changed.trace)) + if count == 0: + raise NumericalFailure("cannot compare empty twin traces") + left = baseline.trace[-count:] + right = changed.trace[-count:] + distances = [_distance(a.state, b.state) for a, b in zip(left, right, strict=False)] + max_index = max(range(count), key=distances.__getitem__) + max_distance = distances[max_index] + elapsed = max(right[max_index].time - right[0].time, changed.settings.dt) + initial_separation = abs(delta) + rate: float | None = None + if initial_separation > 0.0 and max_distance > 0.0: + rate = math.log(max_distance / initial_separation) / elapsed + return { + "initial_separation": initial_separation, + "max_trajectory_distance": max_distance, + "terminal_trajectory_distance": distances[-1], + "finite_time_divergence_rate": rate, + "rate_observation_window": elapsed, + } + + +def run_perturb(config: ProbeConfig) -> ProbeOutcome: + """Search bounded initial-state perturbations using a deterministic predicate.""" + + search = config.section("perturb") + model = get_model(config.model) + dimension = _string(search, "dimension") + try: + dimension_index = model.dimensions.index(dimension) + except ValueError as exc: + raise ConfigurationError( + f"unknown perturbation dimension {dimension!r}; choose one of {model.dimensions}" + ) from exc + start = _number(search, "start") + stop = _number(search, "stop") + points = _integer(search, "points") + scale = _string(search, "scale", "linear") + predicate = _string(search, "predicate", "classification-change") + target_classification_raw = search.get("target_classification") + target_classification = ( + _string(search, "target_classification") if target_classification_raw is not None else None + ) + threshold = _number(search, "divergence_threshold", 1.0) + refinements = _integer(search, "refine_iterations", 10) + repeatability = _integer(search, "repeatability", 2) + if stop <= start or start < 0.0 or refinements < 0 or repeatability < 1: + raise ConfigurationError("perturb requires stop > start >= 0 and valid refinement counts") + if predicate not in {"classification-change", "finite-time-divergence"}: + raise ConfigurationError( + "perturb predicate must be classification-change or finite-time-divergence" + ) + deltas = _logspace(start, stop, points) if scale == "log" else _linspace(start, stop, points) + if scale not in {"linear", "log"}: + raise ConfigurationError("perturb scale must be linear or log") + + baseline = simulate(config) + initial = baseline.initial_state + history: list[Mapping[str, object]] = [] + previous_delta = 0.0 + previous_triggered = False + found_delta: float | None = None + found_run: SimulationResult | None = None + found_metrics: dict[str, object] | None = None + + def evaluate(delta: float, phase: str) -> tuple[bool, SimulationResult, dict[str, object]]: + candidate_state = list(initial) + candidate_state[dimension_index] += delta + changed = simulate(config, initial_override=tuple(candidate_state)) + metrics = _pair_metrics(baseline, changed, delta) + if predicate == "classification-change": + triggered = ( + changed.classification == target_classification + if target_classification is not None + else changed.classification != baseline.classification + ) + else: + distance = metrics["max_trajectory_distance"] + if not isinstance(distance, float): + raise NumericalFailure("internal distance metric type failure") + triggered = distance >= threshold + history.append( + { + "phase": phase, + "delta": delta, + "classification": changed.classification, + "triggered": triggered, + **metrics, + } + ) + return triggered, changed, metrics + + for delta in deltas: + triggered, changed, metrics = evaluate(delta, "coarse") + if triggered: + found_delta, found_run, found_metrics = delta, changed, metrics + break + previous_delta = delta + previous_triggered = triggered + + if found_delta is None or found_run is None or found_metrics is None: + last_delta = deltas[-1] + _, last_run, _ = evaluate(last_delta, "negative-control-confirmation") + return ProbeOutcome( + command="perturb", + status="NO SENSITIVE PERTURBATION FOUND", + config=config, + baseline=baseline, + changed=last_run, + finding=None, + history=tuple(history), + reproducible=True, + ) + + low = previous_delta + high = found_delta + if low > 0.0 and not previous_triggered: + for _ in range(refinements): + midpoint = (low + high) / 2.0 + triggered, changed, metrics = evaluate(midpoint, "refine") + if triggered: + high, found_run, found_metrics = midpoint, changed, metrics + else: + low = midpoint + + hashes: list[str] = [] + classes: list[str] = [] + triggered_confirmations: list[bool] = [] + for _ in range(repeatability): + triggered, confirmed, _ = evaluate(high, "repeatability") + hashes.append(confirmed.trace_sha256) + classes.append(confirmed.classification) + triggered_confirmations.append(triggered) + reproducible = len(set(hashes)) == 1 and len(set(classes)) == 1 and all(triggered_confirmations) + finding = { + "kind": predicate, + "dimension": dimension, + "search_bounds": [start, stop], + "stable_bracket": [low, high], + "smallest_reproducible_change_found": high, + "baseline_regime": baseline.classification, + "changed_regime": found_run.classification, + "target_classification": target_classification, + "metrics": found_metrics, + "classification_rule": config.string("classification_rule"), + "refinement_rule": config.string("refinement_rule"), + "repeatability": { + "runs": repeatability, + "stable": reproducible, + "trace_sha256": hashes[0], + }, + "minimality_statement": "Smallest reproducible perturbation found within the declared finite search; not a proof of global minimality.", + } + status = ( + "FINITE-TIME TRAJECTORY DIVERGENCE FOUND" + if predicate == "finite-time-divergence" + else "QUALITATIVE STATE SWITCH FOUND" + ) + return ProbeOutcome( + command="perturb", + status=status, + config=config, + baseline=baseline, + changed=found_run, + finding=finding, + history=tuple(history), + reproducible=reproducible, + ) + + +def run_check(config: ProbeConfig) -> ProbeOutcome: + """Execute a declared CI policy and mark only policy violations as failure.""" + + check = config.section("check") + analysis = _string(check, "analysis", "invariants") + policy = config.section("policy") + forbid_findings = _boolean(policy, "forbid_findings", False) + require_finding = _boolean(policy, "require_finding", False) + require_invariants = _boolean(policy, "require_invariants", True) + + if analysis == "scan": + outcome = run_scan(config) + elif analysis == "perturb": + outcome = run_perturb(config) + elif analysis == "invariants": + baseline = simulate(config) + violations = [_invariant_dict(item) for item in baseline.invariants if not item.passed] + finding: Mapping[str, object] | None = None + if violations: + finding = {"kind": "invariant-violation", "violations": violations} + outcome = ProbeOutcome( + command="check", + status="CHECK EVIDENCE COLLECTED", + config=config, + baseline=baseline, + changed=None, + finding=finding, + history=(), + reproducible=True, + ) + else: + raise ConfigurationError("check.analysis must be invariants, scan, or perturb") + + finding_present = outcome.finding is not None + policy_failed = (forbid_findings and finding_present) or ( + require_finding and not finding_present + ) + if require_invariants: + policy_failed = policy_failed or outcome.baseline.invariant_violations > 0 + if outcome.changed is not None: + policy_failed = policy_failed or outcome.changed.invariant_violations > 0 + return ProbeOutcome( + command="check", + status="CHECK POLICY FAILED" if policy_failed else "CHECK POLICY PASSED", + config=config, + baseline=outcome.baseline, + changed=outcome.changed, + finding=outcome.finding, + history=outcome.history, + reproducible=outcome.reproducible, + policy_failed=policy_failed, + ) diff --git a/src/phaseprobe/errors.py b/src/phaseprobe/errors.py new file mode 100644 index 0000000..e70b141 --- /dev/null +++ b/src/phaseprobe/errors.py @@ -0,0 +1,31 @@ +"""PhaseProbe exception hierarchy and documented process exit codes.""" + +from __future__ import annotations + +from enum import IntEnum + + +class ExitCode(IntEnum): + """Stable CLI exit-code contract.""" + + OK = 0 + POLICY_FAILED = 1 + INVALID_INPUT = 2 + NUMERICAL_FAILURE = 3 + INTERNAL_ERROR = 4 + + +class PhaseProbeError(Exception): + """Base class for expected PhaseProbe failures.""" + + +class ConfigurationError(PhaseProbeError): + """Raised when a configuration or user input is invalid.""" + + +class NumericalFailure(PhaseProbeError): + """Raised when integration produces an invalid state or solver failure.""" + + +class IntegrityError(PhaseProbeError): + """Raised when a replay fixture fails integrity validation.""" diff --git a/src/phaseprobe/generate.py b/src/phaseprobe/generate.py new file mode 100644 index 0000000..2398f83 --- /dev/null +++ b/src/phaseprobe/generate.py @@ -0,0 +1,57 @@ +"""Safe deterministic pytest generation from validated replay fixtures.""" + +from __future__ import annotations + +import re +import shutil +from dataclasses import dataclass +from pathlib import Path + +from phaseprobe.replay import validate_fixture, verify_replay + + +@dataclass(frozen=True, slots=True) +class GeneratedTest: + """Generated fixed-template test and copied integrity-protected fixture.""" + + test_path: Path + fixture_path: Path + + +def _safe_name(value: object) -> str: + candidate = re.sub(r"[^a-z0-9]+", "_", str(value).lower()).strip("_") + return candidate[:64] or "model" + + +def generate_regression_test(fixture: Path, output_directory: Path) -> GeneratedTest: + """Validate evidence, copy its fixture, and emit a non-extensible pytest template.""" + + payload = validate_fixture(fixture) + verification = verify_replay(fixture) + if not verification.ok: + raise ValueError( + "replay fixture does not reproduce; refusing to generate a regression test" + ) + model_name = _safe_name(payload.get("model")) + output_directory.mkdir(parents=True, exist_ok=True) + fixture_directory = output_directory / "fixtures" + fixture_directory.mkdir(exist_ok=True) + copied_fixture = fixture_directory / f"{model_name}-replay.json" + shutil.copyfile(fixture, copied_fixture) + test_path = output_directory / f"test_{model_name}_transition.py" + source = f'''"""Generated by PhaseProbe from a validated replay fixture.""" + +from pathlib import Path + +from phaseprobe.replay import verify_replay + +FIXTURE = Path(__file__).parent / "fixtures" / "{model_name}-replay.json" + + +def test_{model_name}_transition_replays() -> None: + """Re-execute model/config/seed and verify class and exact retained trace hashes.""" + result = verify_replay(FIXTURE) + assert result.ok, result.as_dict() +''' + test_path.write_text(source, encoding="utf-8", newline="\n") + return GeneratedTest(test_path=test_path, fixture_path=copied_fixture) diff --git a/src/phaseprobe/models/__init__.py b/src/phaseprobe/models/__init__.py new file mode 100644 index 0000000..2948416 --- /dev/null +++ b/src/phaseprobe/models/__init__.py @@ -0,0 +1,36 @@ +"""Built-in deterministic model adapters.""" + +from __future__ import annotations + +from collections.abc import Mapping +from typing import cast + +from phaseprobe.errors import ConfigurationError +from phaseprobe.models.logistic import LogisticMapAdapter +from phaseprobe.models.lorenz import LorenzAdapter +from phaseprobe.models.predator_prey import PredatorPreyAdapter +from phaseprobe.models.toggle import GeneticToggleAdapter +from phaseprobe.types import ModelAdapter + +_MODELS: Mapping[str, ModelAdapter] = { + "logistic-map": cast(ModelAdapter, LogisticMapAdapter()), + "lorenz": cast(ModelAdapter, LorenzAdapter()), + "predator-prey": cast(ModelAdapter, PredatorPreyAdapter()), + "genetic-toggle": cast(ModelAdapter, GeneticToggleAdapter()), +} + + +def get_model(name: str) -> ModelAdapter: + """Return a built-in model adapter by stable identity.""" + + model = _MODELS.get(name) + if model is None: + choices = ", ".join(sorted(_MODELS)) + raise ConfigurationError(f"unknown model {name!r}; choose one of: {choices}") + return model + + +def model_names() -> tuple[str, ...]: + """Return stable built-in model names.""" + + return tuple(sorted(_MODELS)) diff --git a/src/phaseprobe/models/_common.py b/src/phaseprobe/models/_common.py new file mode 100644 index 0000000..41c64fb --- /dev/null +++ b/src/phaseprobe/models/_common.py @@ -0,0 +1,56 @@ +"""Shared validation and fixed-step integration helpers for built-in adapters.""" + +from __future__ import annotations + +from collections.abc import Callable, Mapping, Sequence + +from phaseprobe.errors import ConfigurationError +from phaseprobe.types import State + +Derivative = Callable[[State], State] + + +def float_value(values: Mapping[str, object], name: str, default: float | None = None) -> float: + """Read a finite-number-shaped value; runtime finite checks happen in the engine.""" + + value = values.get(name, default) + if not isinstance(value, int | float) or isinstance(value, bool): + raise ConfigurationError(f"{name} must be a number") + return float(value) + + +def initial_state( + config: Mapping[str, object], dimensions: Sequence[str], defaults: State +) -> State: + """Read an optional named initial-state object.""" + + raw = config.get("initial_state") + if raw is None: + return defaults + if not isinstance(raw, dict): + raise ConfigurationError("model.initial_state must be an object") + values = raw + return tuple(float_value(values, dimension) for dimension in dimensions) + + +def parameter(parameters: Mapping[str, float], name: str, default: float) -> float: + """Read a model parameter with a documented default.""" + + return float(parameters.get(name, default)) + + +def rk4_step(state: State, dt: float, derivative: Derivative) -> State: + """Advance an autonomous ODE with a deterministic classical RK4 step.""" + + k1 = derivative(state) + k2 = derivative( + tuple(value + 0.5 * dt * slope for value, slope in zip(state, k1, strict=False)) + ) + k3 = derivative( + tuple(value + 0.5 * dt * slope for value, slope in zip(state, k2, strict=False)) + ) + k4 = derivative(tuple(value + dt * slope for value, slope in zip(state, k3, strict=False))) + return tuple( + value + (dt / 6.0) * (a + 2.0 * b + 2.0 * c + d) + for value, a, b, c, d in zip(state, k1, k2, k3, k4, strict=False) + ) diff --git a/src/phaseprobe/models/logistic.py b/src/phaseprobe/models/logistic.py new file mode 100644 index 0000000..e665bc9 --- /dev/null +++ b/src/phaseprobe/models/logistic.py @@ -0,0 +1,63 @@ +"""Logistic-map adapter and finite-period classifier.""" + +from __future__ import annotations + +import math +from collections.abc import Mapping, Sequence + +from phaseprobe.models._common import initial_state, parameter +from phaseprobe.types import InvariantResult, Parameters, State, Tolerances, TracePoint + + +class LogisticMapAdapter: + """The discrete logistic map ``x[n+1] = r*x[n]*(1-x[n])``.""" + + name = "logistic-map" + identity = "phaseprobe.builtin.logistic-map:v1" + dimensions = ("x",) + + def initial_state(self, config: Mapping[str, object], seed: int) -> State: + del seed + return initial_state(config, self.dimensions, (0.2,)) + + def step(self, state: State, parameters: Parameters, dt: float) -> State: + del dt + r = parameter(parameters, "r", 3.44) + x = state[0] + return (r * x * (1.0 - x),) + + def observe(self, state: State) -> Mapping[str, float]: + return {"x": state[0]} + + def classify(self, trace: Sequence[TracePoint], tolerances: Tolerances) -> str: + values = [point.state[0] for point in trace] + tolerance = tolerances.get("period", 1e-8) + for period in (1, 2, 4, 8, 16): + if len(values) < 4 * period: + continue + tail = values[-4 * period :] + error = max( + abs(tail[index] - tail[index - period]) for index in range(period, len(tail)) + ) + if error <= tolerance: + return f"period-{period}" + return "aperiodic-or-unresolved" + + def invariants( + self, + trace: Sequence[TracePoint], + parameters: Parameters, + tolerances: Tolerances, + ) -> list[InvariantResult]: + del parameters, tolerances + values = [point.state[0] for point in trace] + violation = max((max(-value, value - 1.0, 0.0) for value in values), default=0.0) + return [ + InvariantResult( + name="unit-interval-boundedness", + passed=math.isfinite(violation) and violation == 0.0, + measured=violation, + tolerance=0.0, + detail="For 0 <= r <= 4 and 0 <= x <= 1, retained states should remain in [0, 1].", + ) + ] diff --git a/src/phaseprobe/models/lorenz.py b/src/phaseprobe/models/lorenz.py new file mode 100644 index 0000000..8ca7f26 --- /dev/null +++ b/src/phaseprobe/models/lorenz.py @@ -0,0 +1,64 @@ +"""Lorenz-system adapter for finite-time trajectory-divergence evidence.""" + +from __future__ import annotations + +import math +from collections.abc import Mapping, Sequence + +from phaseprobe.models._common import initial_state, parameter, rk4_step +from phaseprobe.types import InvariantResult, Parameters, State, Tolerances, TracePoint + + +class LorenzAdapter: + """Classical three-variable Lorenz equations integrated with fixed-step RK4.""" + + name = "lorenz" + identity = "phaseprobe.builtin.lorenz:v1" + dimensions = ("x", "y", "z") + + def initial_state(self, config: Mapping[str, object], seed: int) -> State: + del seed + return initial_state(config, self.dimensions, (1.0, 1.0, 1.0)) + + def step(self, state: State, parameters: Parameters, dt: float) -> State: + sigma = parameter(parameters, "sigma", 10.0) + rho = parameter(parameters, "rho", 28.0) + beta = parameter(parameters, "beta", 8.0 / 3.0) + + def derivative(current: State) -> State: + x, y, z = current + return (sigma * (y - x), x * (rho - z) - y, x * y - beta * z) + + return rk4_step(state, dt, derivative) + + def observe(self, state: State) -> Mapping[str, float]: + x, y, z = state + return {"x": x, "y": y, "z": z, "radius": math.sqrt(x * x + y * y + z * z)} + + def classify(self, trace: Sequence[TracePoint], tolerances: Tolerances) -> str: + del tolerances + signs = {point.state[0] >= 0.0 for point in trace} + if len(signs) > 1: + return "two-lobe-finite-time-trajectory" + if trace and trace[-1].state[0] >= 0.0: + return "positive-lobe-finite-time-trajectory" + return "negative-lobe-finite-time-trajectory" + + def invariants( + self, + trace: Sequence[TracePoint], + parameters: Parameters, + tolerances: Tolerances, + ) -> list[InvariantResult]: + del parameters + bound = tolerances.get("state_bound", 100.0) + measured = max((abs(value) for point in trace for value in point.state), default=0.0) + return [ + InvariantResult( + name="declared-finite-state-bound", + passed=math.isfinite(measured) and measured <= bound, + measured=measured, + tolerance=bound, + detail="A diagnostic finite-time bound, not a mathematical invariant of the Lorenz system.", + ) + ] diff --git a/src/phaseprobe/models/predator_prey.py b/src/phaseprobe/models/predator_prey.py new file mode 100644 index 0000000..6724627 --- /dev/null +++ b/src/phaseprobe/models/predator_prey.py @@ -0,0 +1,86 @@ +"""Lotka-Volterra predator-prey adapter with a conserved-quantity diagnostic.""" + +from __future__ import annotations + +import math +from collections.abc import Mapping, Sequence + +from phaseprobe.models._common import initial_state, parameter, rk4_step +from phaseprobe.types import InvariantResult, Parameters, State, Tolerances, TracePoint + + +class PredatorPreyAdapter: + """Conservative two-species Lotka-Volterra equations.""" + + name = "predator-prey" + identity = "phaseprobe.builtin.predator-prey:v1" + dimensions = ("prey", "predator") + + def initial_state(self, config: Mapping[str, object], seed: int) -> State: + del seed + return initial_state(config, self.dimensions, (10.0, 5.0)) + + def step(self, state: State, parameters: Parameters, dt: float) -> State: + alpha = parameter(parameters, "alpha", 1.1) + beta = parameter(parameters, "beta", 0.4) + delta = parameter(parameters, "delta", 0.1) + gamma = parameter(parameters, "gamma", 0.4) + + def derivative(current: State) -> State: + prey, predator = current + return ( + alpha * prey - beta * prey * predator, + delta * prey * predator - gamma * predator, + ) + + return rk4_step(state, dt, derivative) + + def observe(self, state: State) -> Mapping[str, float]: + return {"prey": state[0], "predator": state[1]} + + def classify(self, trace: Sequence[TracePoint], tolerances: Tolerances) -> str: + del tolerances + if not trace: + return "unresolved" + minima = [min(point.state[index] for point in trace) for index in (0, 1)] + return "bounded-positive-oscillation" if min(minima) > 0.0 else "invalid-population" + + def invariants( + self, + trace: Sequence[TracePoint], + parameters: Parameters, + tolerances: Tolerances, + ) -> list[InvariantResult]: + alpha = parameter(parameters, "alpha", 1.1) + beta = parameter(parameters, "beta", 0.4) + delta = parameter(parameters, "delta", 0.1) + gamma = parameter(parameters, "gamma", 0.4) + tolerance = tolerances.get("invariant_drift", 1e-5) + + def conserved(state: State) -> float: + prey, predator = state + if prey <= 0.0 or predator <= 0.0: + return math.inf + return ( + delta * prey - gamma * math.log(prey) + beta * predator - alpha * math.log(predator) + ) + + values = [conserved(point.state) for point in trace] + drift = max((abs(value - values[0]) for value in values), default=0.0) + min_population = min((value for point in trace for value in point.state), default=0.0) + return [ + InvariantResult( + name="lotka-volterra-conserved-quantity", + passed=math.isfinite(drift) and drift <= tolerance, + measured=drift, + tolerance=tolerance, + detail="Maximum retained drift from the initial value of the analytic first integral.", + ), + InvariantResult( + name="positive-populations", + passed=min_population > 0.0, + measured=min_population, + tolerance=0.0, + detail="Both populations must remain strictly positive over the retained trajectory.", + ), + ] diff --git a/src/phaseprobe/models/toggle.py b/src/phaseprobe/models/toggle.py new file mode 100644 index 0000000..63d3699 --- /dev/null +++ b/src/phaseprobe/models/toggle.py @@ -0,0 +1,73 @@ +"""Small mutually repressing genetic-toggle adapter.""" + +from __future__ import annotations + +import math +from collections.abc import Mapping, Sequence + +from phaseprobe.models._common import initial_state, parameter, rk4_step +from phaseprobe.types import InvariantResult, Parameters, State, Tolerances, TracePoint + + +class GeneticToggleAdapter: + """Dimensionless two-gene mutual-repression model.""" + + name = "genetic-toggle" + identity = "phaseprobe.builtin.genetic-toggle:v1" + dimensions = ("u", "v") + + def initial_state(self, config: Mapping[str, object], seed: int) -> State: + del seed + return initial_state(config, self.dimensions, (3.0, 0.2)) + + def step(self, state: State, parameters: Parameters, dt: float) -> State: + alpha_u = parameter(parameters, "alpha_u", 3.0) + alpha_v = parameter(parameters, "alpha_v", 3.0) + hill_u = parameter(parameters, "hill_u", 2.0) + hill_v = parameter(parameters, "hill_v", 2.0) + + def derivative(current: State) -> State: + u, v = current + return ( + alpha_u / (1.0 + v**hill_v) - u, + alpha_v / (1.0 + u**hill_u) - v, + ) + + return rk4_step(state, dt, derivative) + + def observe(self, state: State) -> Mapping[str, float]: + u, v = state + return {"u": u, "v": v, "difference": u - v} + + def classify(self, trace: Sequence[TracePoint], tolerances: Tolerances) -> str: + if not trace: + return "unresolved" + threshold = tolerances.get("dominance", 0.05) + difference = trace[-1].state[0] - trace[-1].state[1] + if difference > threshold: + return "u-dominant" + if difference < -threshold: + return "v-dominant" + return "balanced" + + def invariants( + self, + trace: Sequence[TracePoint], + parameters: Parameters, + tolerances: Tolerances, + ) -> list[InvariantResult]: + del parameters + bound = tolerances.get("state_bound", 10.0) + values = [value for point in trace for value in point.state] + low = min(values, default=0.0) + high = max(values, default=0.0) + violation = max(-low, high - bound, 0.0) + return [ + InvariantResult( + name="nonnegative-declared-bound", + passed=math.isfinite(violation) and violation == 0.0, + measured=violation, + tolerance=0.0, + detail=f"Concentrations must remain in the declared interval [0, {bound:g}].", + ) + ] diff --git a/src/phaseprobe/replay.py b/src/phaseprobe/replay.py new file mode 100644 index 0000000..e2c9061 --- /dev/null +++ b/src/phaseprobe/replay.py @@ -0,0 +1,183 @@ +"""Versioned replay fixtures, integrity validation, and deterministic re-execution.""" + +from __future__ import annotations + +import hashlib +import json +from collections.abc import Mapping +from dataclasses import dataclass +from pathlib import Path +from typing import Any, cast + +from phaseprobe import __version__ +from phaseprobe.config import canonical_json, parse_config +from phaseprobe.engine import ProbeOutcome, SimulationResult, simulate +from phaseprobe.errors import ConfigurationError, IntegrityError + +REPLAY_SCHEMA_VERSION = "1.0" + + +def _execution_payload(result: SimulationResult) -> dict[str, object]: + return { + "parameters": dict(result.parameters), + "initial_state": list(result.initial_state), + "classification": result.classification, + "trace_sha256": result.trace_sha256, + "invariant_violations": result.invariant_violations, + } + + +def fixture_payload(outcome: ProbeOutcome) -> dict[str, object]: + """Create an integrity-protected replay fixture from validated evidence.""" + + payload: dict[str, object] = { + "schema_version": REPLAY_SCHEMA_VERSION, + "created_by": f"phaseprobe {__version__}", + "model": outcome.baseline.model, + "model_identity": outcome.baseline.model_identity, + "seed": outcome.baseline.seed, + "configuration": dict(outcome.config.data), + "baseline": _execution_payload(outcome.baseline), + "changed": _execution_payload(outcome.changed) if outcome.changed is not None else None, + "finding": dict(outcome.finding) if outcome.finding is not None else None, + "reproducible": outcome.reproducible, + } + integrity = hashlib.sha256(canonical_json(payload).encode("utf-8")).hexdigest() + payload["integrity_sha256"] = integrity + return payload + + +def _load_fixture(path: Path) -> dict[str, object]: + try: + parsed: Any = json.loads(path.read_text(encoding="utf-8")) + except OSError as exc: + raise ConfigurationError(f"cannot read replay fixture {path}: {exc}") from exc + except json.JSONDecodeError as exc: + raise IntegrityError(f"invalid replay JSON in {path}: {exc}") from exc + if not isinstance(parsed, dict): + raise IntegrityError("replay fixture must be a JSON object") + return cast(dict[str, object], parsed) + + +def validate_fixture(path: Path) -> dict[str, object]: + """Validate replay schema and SHA-256 integrity before any execution.""" + + payload = _load_fixture(path) + if payload.get("schema_version") != REPLAY_SCHEMA_VERSION: + raise IntegrityError( + f"unsupported replay schema {payload.get('schema_version')!r}; " + f"expected {REPLAY_SCHEMA_VERSION!r}" + ) + expected = payload.get("integrity_sha256") + if not isinstance(expected, str): + raise IntegrityError("replay fixture has no integrity_sha256") + unsigned = dict(payload) + del unsigned["integrity_sha256"] + actual = hashlib.sha256(canonical_json(unsigned).encode("utf-8")).hexdigest() + if actual != expected: + raise IntegrityError( + f"replay fixture integrity mismatch: expected {expected}, got {actual}" + ) + return payload + + +def _mapping(value: object, context: str) -> Mapping[str, object]: + if not isinstance(value, dict): + raise IntegrityError(f"replay {context} must be an object") + return cast(Mapping[str, object], value) + + +def _float_mapping(value: object, context: str) -> dict[str, float]: + values = _mapping(value, context) + result: dict[str, float] = {} + for name, raw in values.items(): + if not isinstance(raw, int | float) or isinstance(raw, bool): + raise IntegrityError(f"replay {context}.{name} must be numeric") + result[name] = float(raw) + return result + + +def _state(value: object, context: str) -> tuple[float, ...]: + if not isinstance(value, list): + raise IntegrityError(f"replay {context} must be an array") + state: list[float] = [] + for raw in value: + if not isinstance(raw, int | float) or isinstance(raw, bool): + raise IntegrityError(f"replay {context} contains a non-number") + state.append(float(raw)) + return tuple(state) + + +@dataclass(frozen=True, slots=True) +class ReplayVerification: + """Deterministic replay verdict with comparisons suitable for CI diagnostics.""" + + ok: bool + model: str + baseline: SimulationResult + changed: SimulationResult | None + comparisons: tuple[Mapping[str, object], ...] + + def as_dict(self) -> dict[str, object]: + return { + "schema_version": "1.0", + "status": "REPLAY VERIFIED" if self.ok else "REPLAY MISMATCH", + "model": self.model, + "ok": self.ok, + "comparisons": [dict(item) for item in self.comparisons], + "baseline": self.baseline.as_dict(), + "changed": self.changed.as_dict() if self.changed is not None else None, + } + + +def verify_replay(path: Path) -> ReplayVerification: + """Re-execute the recorded model/config/seed and compare exact trace evidence.""" + + fixture = validate_fixture(path) + configuration = fixture.get("configuration") + if not isinstance(configuration, dict): + raise IntegrityError("replay configuration must be an object") + config = parse_config(canonical_json(configuration), f"replay fixture {path.name}") + expected_model = fixture.get("model") + if config.model != expected_model: + raise IntegrityError("replay model does not match embedded configuration") + expected_identity = fixture.get("model_identity") + + comparisons: list[Mapping[str, object]] = [] + + def execute(label: str, raw: object) -> SimulationResult: + expected = _mapping(raw, label) + run = simulate( + config, + parameters_override=_float_mapping(expected.get("parameters"), f"{label}.parameters"), + initial_override=_state(expected.get("initial_state"), f"{label}.initial_state"), + ) + classification_match = run.classification == expected.get("classification") + hash_match = run.trace_sha256 == expected.get("trace_sha256") + identity_match = run.model_identity == expected_identity + comparisons.append( + { + "series": label, + "classification_match": classification_match, + "trace_hash_match": hash_match, + "model_identity_match": identity_match, + "expected_trace_sha256": expected.get("trace_sha256"), + "actual_trace_sha256": run.trace_sha256, + } + ) + return run + + baseline = execute("baseline", fixture.get("baseline")) + changed_raw = fixture.get("changed") + changed = execute("changed", changed_raw) if changed_raw is not None else None + ok = all( + all(value is True for key, value in item.items() if key.endswith("_match")) + for item in comparisons + ) + return ReplayVerification( + ok=ok, + model=config.model, + baseline=baseline, + changed=changed, + comparisons=tuple(comparisons), + ) diff --git a/src/phaseprobe/reporting.py b/src/phaseprobe/reporting.py new file mode 100644 index 0000000..22936b9 --- /dev/null +++ b/src/phaseprobe/reporting.py @@ -0,0 +1,172 @@ +"""Offline terminal, JSON, and self-contained HTML evidence reports.""" + +from __future__ import annotations + +import html +import json +from collections.abc import Mapping, Sequence +from pathlib import Path +from typing import cast + + +def _mapping(value: object) -> Mapping[str, object]: + return cast(Mapping[str, object], value) if isinstance(value, dict) else {} + + +def _sequence(value: object) -> Sequence[object]: + return cast(Sequence[object], value) if isinstance(value, list) else () + + +def _format_float(value: object) -> str: + return f"{value:.10g}" if isinstance(value, float) else str(value) + + +def _int_value(value: object) -> int: + return value if isinstance(value, int) and not isinstance(value, bool) else 0 + + +def terminal_report( + data: Mapping[str, object], + *, + replay: str | None = None, + generated_test: str | None = None, +) -> str: + """Lead with the result and present compact, scientifically qualified evidence.""" + + status = str(data.get("status", "PHASEPROBE RESULT")) + model = str(data.get("model", "unknown")) + baseline = _mapping(data.get("baseline")) + changed = _mapping(data.get("changed")) + finding = _mapping(data.get("finding")) + lines = [status, "", f"Model: {model}"] + kind = finding.get("kind") + if kind is not None: + lines.append(f"Evidence: {kind}") + parameter = finding.get("parameter") or finding.get("dimension") + if parameter is not None: + lines.append(f"Search dimension: {parameter}") + bracket = _sequence(finding.get("stable_bracket")) + if len(bracket) == 2: + lines.append(f"Stable bracket: {_format_float(bracket[0])} .. {_format_float(bracket[1])}") + smallest = finding.get("smallest_reproducible_change_found") + if smallest is not None: + lines.append(f"Smallest reproducible change found: {_format_float(smallest)}") + lines.extend( + [ + f"Baseline regime: {baseline.get('classification', 'n/a')}", + f"Changed regime: {changed.get('classification', 'n/a') if changed else 'n/a'}", + ] + ) + violations = _int_value(baseline.get("invariant_violations", 0)) + if changed: + violations += _int_value(changed.get("invariant_violations", 0)) + lines.append(f"Invariant violations: {violations}") + lines.append(f"Repeatable: {str(bool(data.get('reproducible', False))).lower()}") + if replay is not None: + lines.append(f"Replay: {replay}") + if generated_test is not None: + lines.append(f"Generated test: {generated_test}") + minimality = finding.get("minimality_statement") + if minimality is not None: + lines.extend(["", f"Scope: {minimality}"]) + return "\n".join(lines) + + +def json_report(data: Mapping[str, object]) -> str: + """Render the complete versioned JSON evidence document.""" + + return json.dumps(data, allow_nan=False, indent=2, sort_keys=True) + "\n" + + +def html_report(data: Mapping[str, object]) -> str: + """Render a standalone offline report with no scripts, fonts, or CDN assets.""" + + status = html.escape(str(data.get("status", "PhaseProbe result"))) + model = html.escape(str(data.get("model", "unknown"))) + baseline = _mapping(data.get("baseline")) + changed = _mapping(data.get("changed")) + finding = _mapping(data.get("finding")) + history = _sequence(data.get("history")) + config = _mapping(data.get("configuration")) + + history_rows: list[str] = [] + for item in history: + row = _mapping(item) + phase = html.escape(str(row.get("phase", ""))) + value = row.get("value", row.get("delta", "")) + classification = html.escape(str(row.get("classification", ""))) + interval = "" + if "low" in row and "high" in row: + interval = f"{_format_float(row['low'])} .. {_format_float(row['high'])}" + history_rows.append( + "" + f"{phase}{html.escape(_format_float(value))}" + f"{classification}{html.escape(interval)}" + "" + ) + if not history_rows: + history_rows.append('No refinement history for this check.') + + finding_json = html.escape(json.dumps(finding, allow_nan=False, indent=2, sort_keys=True)) + config_json = html.escape(json.dumps(config, allow_nan=False, indent=2, sort_keys=True)) + limitations = ( + "This report contains bounded numerical evidence. It does not prove global minimality, " + "an exact bifurcation point, chaos, a formal Lyapunov exponent, or scientific validity " + "outside the declared model, integration settings, search space, and tolerances." + ) + return f""" + + + + +PhaseProbe — {status} + + +
+

PhaseProbe evidence report · schema {html.escape(str(data.get("schema_version", "unknown")))}

+

{status}

Model: {model}

+
+

Baseline

{html.escape(str(baseline.get("classification", "n/a")))}

Trace SHA-256: {html.escape(str(baseline.get("trace_sha256", "n/a")))}

+

Changed

{html.escape(str(changed.get("classification", "n/a")))}

Trace SHA-256: {html.escape(str(changed.get("trace_sha256", "n/a")))}

+
+

Finding

{finding_json}
+

Search and refinement history

+{"".join(history_rows)}
PhaseValue / deltaClassificationBracket
+

Configuration

{config_json}
+

Replay and generated test

The run directory contains a versioned replay.json fixture. The generate-test command validates and copies that fixture into a fixed pytest template.

+

Scientific limitations

{html.escape(limitations)}

+
+""" + + +def regenerate_reports(run_directory: Path) -> tuple[Path, Path]: + """Regenerate JSON and HTML evidence from a bounded run directory.""" + + run_path = run_directory / "run.json" + try: + parsed = json.loads(run_path.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError) as exc: + raise ValueError(f"cannot load run evidence from {run_path}: {exc}") from exc + if not isinstance(parsed, dict): + raise ValueError(f"run evidence in {run_path} is not a JSON object") + data = cast(dict[str, object], parsed) + json_path = run_directory / "report.json" + html_path = run_directory / "report.html" + json_path.write_text(json_report(data), encoding="utf-8") + html_path.write_text(html_report(data), encoding="utf-8") + return json_path, html_path diff --git a/src/phaseprobe/types.py b/src/phaseprobe/types.py new file mode 100644 index 0000000..88dea81 --- /dev/null +++ b/src/phaseprobe/types.py @@ -0,0 +1,68 @@ +"""Typed public model-adapter and result interfaces.""" + +from __future__ import annotations + +from collections.abc import Mapping, Sequence +from dataclasses import dataclass +from typing import Protocol, TypeAlias, runtime_checkable + +Scalar: TypeAlias = float | int | str +State: TypeAlias = tuple[float, ...] +Parameters: TypeAlias = Mapping[str, float] +ModelConfig: TypeAlias = Mapping[str, object] +Tolerances: TypeAlias = Mapping[str, float] + + +@dataclass(frozen=True, slots=True) +class TracePoint: + """One retained point from a bounded simulation trace.""" + + step: int + time: float + state: State + observations: Mapping[str, Scalar] + + +@dataclass(frozen=True, slots=True) +class InvariantResult: + """Result of evaluating a declared model invariant or boundedness rule.""" + + name: str + passed: bool + measured: float + tolerance: float + detail: str + + +@runtime_checkable +class ModelAdapter(Protocol): + """Small interface implemented by built-in and downstream simulation adapters. + + The explicit state tuple makes perturbation, serialization, and finite-value validation + deterministic. Classifiers and invariants receive declared tolerances rather than hiding + thresholds inside the engine. + """ + + name: str + identity: str + dimensions: tuple[str, ...] + + def initial_state(self, config: ModelConfig, seed: int) -> State: + """Return a deterministic initial state for ``config`` and ``seed``.""" + + def step(self, state: State, parameters: Parameters, dt: float) -> State: + """Advance exactly one declared step.""" + + def observe(self, state: State) -> Mapping[str, Scalar]: + """Expose bounded scalar observables used in reports and classification.""" + + def classify(self, trace: Sequence[TracePoint], tolerances: Tolerances) -> str: + """Return a qualitative class using only retained trace evidence.""" + + def invariants( + self, + trace: Sequence[TracePoint], + parameters: Parameters, + tolerances: Tolerances, + ) -> list[InvariantResult]: + """Evaluate declared invariants or boundedness conditions.""" diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..6d1e545 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,15 @@ +"""Shared deterministic outcomes for the numerical test suite.""" + +from __future__ import annotations + +import pytest + +from phaseprobe.config import load_example +from phaseprobe.engine import ProbeOutcome, run_scan + + +@pytest.fixture(scope="session") +def logistic_outcome() -> ProbeOutcome: + """Run the slower critical-transition example once per test session.""" + + return run_scan(load_example("logistic")) diff --git a/tests/generated/fixtures/logistic_map-replay.json b/tests/generated/fixtures/logistic_map-replay.json new file mode 100644 index 0000000..59ff405 --- /dev/null +++ b/tests/generated/fixtures/logistic_map-replay.json @@ -0,0 +1,100 @@ +{ + "baseline": { + "classification": "period-2", + "initial_state": [ + 0.2 + ], + "invariant_violations": 0, + "parameters": { + "r": 3.4494775390625003 + }, + "trace_sha256": "59fad16c37082ac14eb62b9e11fc7e6695c80896ea99bb1889d6e7a207271e4b" + }, + "changed": { + "classification": "period-4", + "initial_state": [ + 0.2 + ], + "invariant_violations": 0, + "parameters": { + "r": 3.4494780273437504 + }, + "trace_sha256": "0564695f9706df0e839e4aee48e720d82d02e17498da8db597e1f78508f7b3f0" + }, + "configuration": { + "classification_rule": "Smallest detected period in {1,2,4,8,16} whose retained tail repeats within absolute tolerance; otherwise unresolved/aperiodic.", + "invalid_state_policy": "Abort as invalid integration on NaN, infinity, overflow, dimension mismatch, or hard-state-limit breach.", + "model": "logistic-map", + "model_config": { + "initial_state": { + "x": 0.2 + } + }, + "parameters": { + "r": 3.448 + }, + "refinement_rule": "First adjacent coarse class change, followed by up to twelve deterministic binary probes. Refinement stops rather than relabeling a midpoint that reproduces neither stable endpoint class.", + "scan": { + "parameter": "r", + "points": 3, + "refine_iterations": 12, + "repeatability": 2, + "start": 3.448, + "stop": 3.452 + }, + "schema_version": "1.0", + "seed": 17, + "simulation": { + "burn_in": 500000, + "dt": 1.0, + "hard_state_limit": 10.0, + "sample_every": 1, + "steps": 256, + "trace_cap": 256 + }, + "tolerances": { + "period": 1e-09 + } + }, + "created_by": "phaseprobe 0.1.0", + "finding": { + "baseline_regime": "period-2", + "bracket_width": 4.88281250099476e-07, + "changed_regime": "period-4", + "classification_rule": "Smallest detected period in {1,2,4,8,16} whose retained tail repeats within absolute tolerance; otherwise unresolved/aperiodic.", + "coarse_bracket": [ + 3.448, + 3.45 + ], + "kind": "qualitative-regime-change", + "minimality_statement": "Smallest reproducible separation found by the declared bounded scan and binary refinement; not a proof of a globally minimal perturbation or exact bifurcation point.", + "parameter": "r", + "refinement_rule": "First adjacent coarse class change, followed by up to twelve deterministic binary probes. Refinement stops rather than relabeling a midpoint that reproduces neither stable endpoint class.", + "repeatability": [ + { + "runs": 2, + "side": "baseline", + "stable": true, + "trace_sha256": "59fad16c37082ac14eb62b9e11fc7e6695c80896ea99bb1889d6e7a207271e4b" + }, + { + "runs": 2, + "side": "changed", + "stable": true, + "trace_sha256": "0564695f9706df0e839e4aee48e720d82d02e17498da8db597e1f78508f7b3f0" + } + ], + "smallest_reproducible_change_found": 4.88281250099476e-07, + "stable_bracket": [ + 3.4494775390625003, + 3.4494780273437504 + ], + "unresolved_midpoint": null + }, + "integrity_sha256": "8e6d9a8ad624fe5339848168c8a329aafc5abf79a4b90e4e2b29e46bfcee7d20", + "model": "logistic-map", + "model_identity": "phaseprobe.builtin.logistic-map:v1", + "reproducible": true, + "schema_version": "1.0", + "seed": 17 +} diff --git a/tests/generated/test_logistic_map_transition.py b/tests/generated/test_logistic_map_transition.py new file mode 100644 index 0000000..e613935 --- /dev/null +++ b/tests/generated/test_logistic_map_transition.py @@ -0,0 +1,13 @@ +"""Generated by PhaseProbe from a validated replay fixture.""" + +from pathlib import Path + +from phaseprobe.replay import verify_replay + +FIXTURE = Path(__file__).parent / "fixtures" / "logistic_map-replay.json" + + +def test_logistic_map_transition_replays() -> None: + """Re-execute model/config/seed and verify class and exact retained trace hashes.""" + result = verify_replay(FIXTURE) + assert result.ok, result.as_dict() diff --git a/tests/test_artifacts_replay.py b/tests/test_artifacts_replay.py new file mode 100644 index 0000000..2ba1875 --- /dev/null +++ b/tests/test_artifacts_replay.py @@ -0,0 +1,86 @@ +"""Artifact, integrity, replay, report, and generated-test integration tests.""" + +from __future__ import annotations + +import hashlib +import json +import os +import subprocess +import sys +from pathlib import Path + +import pytest + +from phaseprobe.artifacts import write_artifacts +from phaseprobe.config import load_example +from phaseprobe.engine import run_check +from phaseprobe.errors import IntegrityError +from phaseprobe.generate import generate_regression_test +from phaseprobe.replay import validate_fixture, verify_replay +from phaseprobe.reporting import regenerate_reports + + +@pytest.fixture +def artifact_run(tmp_path: Path) -> Path: + outcome = run_check(load_example("predator-prey")) + return write_artifacts(outcome, tmp_path / "runs").run_directory + + +def test_artifact_manifest_hashes_every_evidence_file(artifact_run: Path) -> None: + manifest = json.loads((artifact_run / "manifest.json").read_text(encoding="utf-8")) + assert manifest["bounded_trace"]["baseline_points"] == 4000 + for name, evidence in manifest["files"].items(): + digest = hashlib.sha256((artifact_run / name).read_bytes()).hexdigest() + assert digest == evidence["sha256"] + + +@pytest.mark.integration +def test_replay_reexecutes_exact_trace(artifact_run: Path) -> None: + result = verify_replay(artifact_run / "replay.json") + assert result.ok + assert result.comparisons[0]["trace_hash_match"] is True + + +def test_replay_rejects_tampering(artifact_run: Path) -> None: + fixture = artifact_run / "replay.json" + payload = json.loads(fixture.read_text(encoding="utf-8")) + payload["seed"] = 999 + fixture.write_text(json.dumps(payload), encoding="utf-8") + with pytest.raises(IntegrityError, match="integrity mismatch"): + validate_fixture(fixture) + + +@pytest.mark.integration +def test_generated_pytest_genuinely_executes(artifact_run: Path, tmp_path: Path) -> None: + generated = generate_regression_test(artifact_run / "replay.json", tmp_path / "generated") + environment = { + name: value + for name, value in os.environ.items() + if not name.startswith("COV_CORE") and name != "COVERAGE_PROCESS_START" + } + completed = subprocess.run( + [sys.executable, "-m", "pytest", "-q", generated.test_path.name], + check=False, + capture_output=True, + cwd=generated.test_path.parent, + env=environment, + text=True, + timeout=60, + ) + assert completed.returncode == 0, completed.stdout + completed.stderr + assert "1 passed" in completed.stdout + + +def test_html_report_is_self_contained_and_offline(artifact_run: Path) -> None: + report = (artifact_run / "report.html").read_text(encoding="utf-8") + assert "" in report + assert "Scientific limitations" in report + assert "cdn" not in report.lower() + assert " None: + json_path, html_path = regenerate_reports(artifact_run) + assert json_path.exists() + assert html_path.exists() + assert json.loads(json_path.read_text(encoding="utf-8"))["schema_version"] == "1.0" diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 0000000..ffabb2f --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,165 @@ +"""CLI integration and documented exit-code tests.""" + +from __future__ import annotations + +import json +from pathlib import Path + +import pytest + +from phaseprobe.artifacts import write_artifacts +from phaseprobe.cli import main +from phaseprobe.config import load_example +from phaseprobe.engine import run_check +from phaseprobe.errors import ExitCode + + +@pytest.fixture +def cli_artifact(tmp_path: Path) -> Path: + outcome = run_check(load_example("predator-prey")) + return write_artifacts(outcome, tmp_path / "artifact-runs").run_directory + + +def test_scan_success_is_zero(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None: + code = main( + [ + "scan", + "--example", + "logistic-negative", + "--output-root", + str(tmp_path / "runs"), + ] + ) + assert code == ExitCode.OK + assert "NO QUALITATIVE TRANSITION FOUND" in capsys.readouterr().out + + +def test_check_policy_failure_is_one(tmp_path: Path) -> None: + code = main( + [ + "check", + "--example", + "predator-prey-negative", + "--output-root", + str(tmp_path / "runs"), + ] + ) + assert code == ExitCode.POLICY_FAILED + + +def test_invalid_configuration_is_two(tmp_path: Path) -> None: + path = tmp_path / "invalid.json" + path.write_text("{}", encoding="utf-8") + code = main(["scan", "--config", str(path), "--output-root", str(tmp_path / "runs")]) + assert code == ExitCode.INVALID_INPUT + + +def test_numerical_failure_is_three(tmp_path: Path) -> None: + data = { + "schema_version": "1.0", + "model": "logistic-map", + "seed": 1, + "parameters": {"r": 1e50}, + "model_config": {"initial_state": {"x": 0.2}}, + "simulation": { + "steps": 20, + "burn_in": 0, + "dt": 1, + "sample_every": 1, + "trace_cap": 20, + "hard_state_limit": 10, + }, + "tolerances": {"period": 1e-6}, + "scan": {"parameter": "r", "start": 1e50, "stop": 2e50, "points": 2}, + "classification_rule": "test", + "refinement_rule": "test", + "invalid_state_policy": "abort", + } + path = tmp_path / "numerical.json" + path.write_text(json.dumps(data), encoding="utf-8") + code = main(["scan", "--config", str(path), "--output-root", str(tmp_path / "runs")]) + assert code == ExitCode.NUMERICAL_FAILURE + + +def test_json_output_is_versioned(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None: + code = main( + [ + "check", + "--example", + "predator-prey", + "--output-root", + str(tmp_path / "runs"), + "--json", + ] + ) + assert code == ExitCode.OK + payload = json.loads(capsys.readouterr().out) + assert payload["schema_version"] == "1.0" + assert payload["artifacts"]["replay"].endswith("replay.json") + + +def test_replay_generate_and_report_commands( + cli_artifact: Path, tmp_path: Path, capsys: pytest.CaptureFixture[str] +) -> None: + replay_code = main(["replay", str(cli_artifact / "replay.json")]) + assert replay_code == ExitCode.OK + assert "REPLAY VERIFIED" in capsys.readouterr().out + + generated_directory = tmp_path / "generated-tests" + generate_code = main( + [ + "generate-test", + str(cli_artifact / "replay.json"), + "--output-directory", + str(generated_directory), + "--json", + ] + ) + assert generate_code == ExitCode.OK + generated_payload = json.loads(capsys.readouterr().out) + assert Path(generated_payload["test"]).exists() + + report_code = main(["report", str(cli_artifact), "--format", "all"]) + assert report_code == ExitCode.OK + report_output = capsys.readouterr().out + assert "CHECK POLICY PASSED" in report_output + assert (cli_artifact / "report.json").exists() + + +def test_fail_on_finding_is_one(tmp_path: Path) -> None: + code = main( + [ + "perturb", + "--example", + "toggle", + "--output-root", + str(tmp_path / "runs"), + "--fail-on-finding", + ] + ) + assert code == ExitCode.POLICY_FAILED + + +def test_tampered_replay_is_invalid_input(cli_artifact: Path) -> None: + fixture = cli_artifact / "replay.json" + payload = json.loads(fixture.read_text(encoding="utf-8")) + payload["seed"] = 999 + fixture.write_text(json.dumps(payload), encoding="utf-8") + assert main(["replay", str(fixture)]) == ExitCode.INVALID_INPUT + + +def test_internal_defect_is_four(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + def broken_execute(_config: object) -> object: + raise RuntimeError("synthetic containment test") + + monkeypatch.setattr("phaseprobe.cli.run_scan", broken_execute) + code = main( + [ + "scan", + "--example", + "logistic-negative", + "--output-root", + str(tmp_path / "runs"), + ] + ) + assert code == ExitCode.INTERNAL_ERROR diff --git a/tests/test_config.py b/tests/test_config.py new file mode 100644 index 0000000..ea2c168 --- /dev/null +++ b/tests/test_config.py @@ -0,0 +1,69 @@ +"""Configuration and registry unit tests.""" + +from __future__ import annotations + +import json +from pathlib import Path + +import pytest + +from phaseprobe.config import canonical_json, load_config, load_example, parse_config +from phaseprobe.errors import ConfigurationError +from phaseprobe.models import get_model, model_names +from phaseprobe.types import ModelAdapter + + +def test_canonical_json_is_stable() -> None: + assert canonical_json({"b": 1, "a": [3, 2]}) == '{"a":[3,2],"b":1}' + + +def test_parse_rejects_wrong_schema() -> None: + with pytest.raises(ConfigurationError, match="unsupported configuration schema"): + parse_config('{"schema_version":"9","model":"lorenz"}', "test") + + +def test_parse_rejects_non_object() -> None: + with pytest.raises(ConfigurationError, match="must be a JSON object"): + parse_config("[]", "test") + + +def test_parse_rejects_bad_seed() -> None: + with pytest.raises(ConfigurationError, match="seed"): + parse_config('{"schema_version":"1.0","model":"lorenz","seed":-1}', "test") + + +def test_load_config_reports_missing_file(tmp_path: Path) -> None: + with pytest.raises(ConfigurationError, match="cannot read"): + load_config(tmp_path / "missing.json") + + +def test_all_built_in_examples_are_versioned_json() -> None: + for name in ( + "logistic", + "logistic-negative", + "lorenz", + "lorenz-negative", + "predator-prey", + "predator-prey-negative", + "toggle", + "toggle-negative", + ): + config = load_example(name) + assert config.data["schema_version"] == "1.0" + json.loads(canonical_json(config.data)) + + +def test_unknown_example_is_actionable() -> None: + with pytest.raises(ConfigurationError, match="unknown example"): + load_example("missing") + + +def test_registry_models_implement_protocol() -> None: + assert model_names() == ("genetic-toggle", "logistic-map", "lorenz", "predator-prey") + for name in model_names(): + assert isinstance(get_model(name), ModelAdapter) + + +def test_unknown_model_is_actionable() -> None: + with pytest.raises(ConfigurationError, match="unknown model"): + get_model("missing") diff --git a/tests/test_engine.py b/tests/test_engine.py new file mode 100644 index 0000000..b19d4d3 --- /dev/null +++ b/tests/test_engine.py @@ -0,0 +1,124 @@ +"""Numerical validation, positive cases, and negative controls.""" + +from __future__ import annotations + +import math + +import pytest + +from phaseprobe.config import canonical_json, load_example, parse_config +from phaseprobe.engine import ProbeOutcome, run_check, run_perturb, run_scan, simulate +from phaseprobe.errors import ConfigurationError, NumericalFailure + + +@pytest.mark.numerical +def test_logistic_transition_is_refined_and_repeatable(logistic_outcome: ProbeOutcome) -> None: + finding = logistic_outcome.finding + assert finding is not None + bracket = finding["stable_bracket"] + assert isinstance(bracket, list) + assert 3.449 < bracket[0] < bracket[1] < 3.45 + assert logistic_outcome.baseline.classification == "period-2" + assert logistic_outcome.changed is not None + assert logistic_outcome.changed.classification == "period-4" + assert logistic_outcome.reproducible + + +@pytest.mark.numerical +def test_logistic_negative_control_has_no_transition() -> None: + outcome = run_scan(load_example("logistic-negative")) + assert outcome.finding is None + assert outcome.status == "NO QUALITATIVE TRANSITION FOUND" + + +@pytest.mark.numerical +def test_lorenz_reports_finite_time_divergence_without_class_change() -> None: + outcome = run_perturb(load_example("lorenz")) + assert outcome.finding is not None + assert outcome.finding["kind"] == "finite-time-divergence" + metrics = outcome.finding["metrics"] + assert isinstance(metrics, dict) + assert metrics["max_trajectory_distance"] >= 1.0 + assert isinstance(metrics["finite_time_divergence_rate"], float) + assert outcome.changed is not None + assert outcome.baseline.classification == outcome.changed.classification + + +@pytest.mark.numerical +def test_lorenz_short_window_negative_control() -> None: + outcome = run_perturb(load_example("lorenz-negative")) + assert outcome.finding is None + assert outcome.status == "NO SENSITIVE PERTURBATION FOUND" + + +@pytest.mark.numerical +def test_predator_prey_refined_solver_preserves_declared_invariant() -> None: + outcome = run_check(load_example("predator-prey")) + assert not outcome.policy_failed + assert outcome.baseline.classification == "bounded-positive-oscillation" + assert outcome.baseline.invariant_violations == 0 + drift = outcome.baseline.invariants[0] + assert drift.measured <= drift.tolerance + + +@pytest.mark.numerical +def test_predator_prey_coarse_solver_is_a_policy_failure() -> None: + outcome = run_check(load_example("predator-prey-negative")) + assert outcome.policy_failed + assert outcome.finding is not None + assert outcome.finding["kind"] == "invariant-violation" + + +@pytest.mark.numerical +def test_toggle_perturbation_switches_stable_state() -> None: + outcome = run_perturb(load_example("toggle")) + assert outcome.finding is not None + assert outcome.baseline.classification == "u-dominant" + assert outcome.changed is not None + assert outcome.changed.classification == "v-dominant" + assert outcome.reproducible + + +@pytest.mark.numerical +def test_toggle_negative_control_stays_in_baseline_basin() -> None: + outcome = run_perturb(load_example("toggle-negative")) + assert outcome.finding is None + assert outcome.changed is not None + assert outcome.changed.classification == "u-dominant" + + +def test_simulation_is_exactly_repeatable() -> None: + config = load_example("predator-prey") + first = simulate(config) + second = simulate(config) + assert first.trace_sha256 == second.trace_sha256 + assert first.final_state == second.final_state + + +def test_trace_retention_is_bounded() -> None: + result = simulate(load_example("lorenz-negative")) + assert len(result.trace) == result.settings.trace_cap == 500 + + +def test_invalid_integration_is_diagnostic() -> None: + base = load_example("logistic-negative") + data = dict(base.data) + data["parameters"] = {"r": 1e50} + data["simulation"] = dict(base.section("simulation"), hard_state_limit=10.0) + config = parse_config(canonical_json(data), "invalid-state-test") + with pytest.raises(NumericalFailure, match="invalid integration"): + simulate(config) + + +def test_invalid_search_bounds_are_rejected() -> None: + base = load_example("logistic-negative") + data = dict(base.data) + data["scan"] = dict(base.section("scan"), stop=3.0) + config = parse_config(canonical_json(data), "bad-scan") + with pytest.raises(ConfigurationError, match="stop > start"): + run_scan(config) + + +def test_every_retained_scalar_is_finite() -> None: + result = simulate(load_example("toggle")) + assert all(math.isfinite(value) for point in result.trace for value in point.state) diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..9f9f8a4 --- /dev/null +++ b/uv.lock @@ -0,0 +1,522 @@ +version = 1 +revision = 3 +requires-python = ">=3.10" + +[[package]] +name = "build" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "os_name == 'nt'" }, + { name = "importlib-metadata", marker = "python_full_version < '3.10.2'" }, + { name = "packaging" }, + { name = "pyproject-hooks" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/25/1c/23e33405a7c9eac261dff640926b8b5adaed6a6eb3e1767d441ed611d0c0/build-1.3.0.tar.gz", hash = "sha256:698edd0ea270bde950f53aed21f3a0135672206f3911e0176261a31e0e07b397", size = 48544, upload-time = "2025-08-01T21:27:09.268Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/8c/2b30c12155ad8de0cf641d76a8b396a16d2c36bc6d50b621a62b7c4567c1/build-1.3.0-py3-none-any.whl", hash = "sha256:7145f0b5061ba90a1500d60bd1b13ca0a8a4cebdd0cc16ed8adf1c0e739f43b4", size = 23382, upload-time = "2025-08-01T21:27:07.844Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "coverage" +version = "7.15.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/76/d0/55fe630f4cf94e3fcba868240fad8c8cdd1f764e2a932f8926347e6ec4cd/coverage-7.15.2.tar.gz", hash = "sha256:3df60dc267f0a2ca23cb7a9ab1109c62b9335ffbf519fcfe167157c28c09b81d", size = 927741, upload-time = "2026-07-15T18:56:19.558Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/03/060ce69008ac97bbc01b1411b3e55b61f6f015659400b46749b662107831/coverage-7.15.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9b5bd92ff1ec22e535eab0de75fa6db021992791f461a2aceb7822c625a1187d", size = 221284, upload-time = "2026-07-15T18:53:29.52Z" }, + { url = "https://files.pythonhosted.org/packages/fc/a3/d936e8b53edd9684100a6aefaf3fcabaa54728fe33324436c8d279c047aa/coverage-7.15.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:44826758cfe73fcd0e6af5deb4ba6d5417cc1d13df3acb35c93484a11160f846", size = 221799, upload-time = "2026-07-15T18:53:31.708Z" }, + { url = "https://files.pythonhosted.org/packages/ae/a3/ca234b06aec7ee28226f11d39a696b4481fe5eddfce8e03bf39979bb8ffb/coverage-7.15.2-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:09f5c6ec5901f667bd97dd140b5b9a2586b10efec66f46fb1e6d8135f8b95bdf", size = 248544, upload-time = "2026-07-15T18:53:33.212Z" }, + { url = "https://files.pythonhosted.org/packages/2b/89/dda79527bb7573ba91828b2fb91b3105d87378d6a2749ca0c0924ce0addd/coverage-7.15.2-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1d16e3a7104ea84f03e614611b3edbf6fb6892554b3ab0fe7fbb3f2b2ef04376", size = 250374, upload-time = "2026-07-15T18:53:34.683Z" }, + { url = "https://files.pythonhosted.org/packages/67/c6/c33755a34572f81f49a8c0cdf6b622f35ccb3238b136e1909daf0cdd4319/coverage-7.15.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d46e62cb35d91e6e2589fda6d28074426b0e276422b5d2ebef2c6b11dc60dbfd", size = 252239, upload-time = "2026-07-15T18:53:36.205Z" }, + { url = "https://files.pythonhosted.org/packages/b9/6f/dc341741b375be53a5baeee5b4bf0f0e525d38caed428f7932d23bb7bcb1/coverage-7.15.2-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dfd3db045e95960ae3683059571e597fda7cc610106a8916f77c5839048c1deb", size = 254150, upload-time = "2026-07-15T18:53:37.863Z" }, + { url = "https://files.pythonhosted.org/packages/e9/8d/966a18a5b195cb4e77b14c53f5f3dce22b5da05e6de7fafd1e08f2d2067a/coverage-7.15.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:affd532502d34c0472d0cdb181325c89f1d2c44992fef0c17e88e7b1576259a1", size = 249234, upload-time = "2026-07-15T18:53:39.394Z" }, + { url = "https://files.pythonhosted.org/packages/c5/8b/8b2e367496ab48484d48e79984fec76cdc1b7cb5d3a00ee799a5602e3ec9/coverage-7.15.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d17d7512151fedfcc64c1821a8977fc9be0dbf495754669afcab7b57abc98ae9", size = 250276, upload-time = "2026-07-15T18:53:41.027Z" }, + { url = "https://files.pythonhosted.org/packages/63/92/1199318a200eb6c8c6ce0192c892c8710ac791abbe0f35099294620bbfda/coverage-7.15.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e26ff680768b8095e8874aabe0e9d3a47a2a9f176a8340d05f8604c56457c23a", size = 248283, upload-time = "2026-07-15T18:53:42.557Z" }, + { url = "https://files.pythonhosted.org/packages/56/da/be284a55c5619bda891a89c27dfd59324a2c6a14d755cf6aac6960ceebeb/coverage-7.15.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:7e8f27131dc7cd53de2c137dd207b3720919320b3c20d499dc30aa9ee6173287", size = 252093, upload-time = "2026-07-15T18:53:44.271Z" }, + { url = "https://files.pythonhosted.org/packages/d4/53/ee112da833ddd77b73c6d781a98029b45b584b136615b4900ed0569f887e/coverage-7.15.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:728a33676d4c3f0db977990a4bd421dcaa3be3e53b5b6273036fff6666008e89", size = 248552, upload-time = "2026-07-15T18:53:45.7Z" }, + { url = "https://files.pythonhosted.org/packages/82/6a/802cfc802e9113494c80bf3f284cd4d72faeb1f24e244f61046af364f2ca/coverage-7.15.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:29c052f7c83ccfcc5c577eaae025d2e4a9bb80daf03c0ac31c996e83b000ce88", size = 249154, upload-time = "2026-07-15T18:53:47.256Z" }, + { url = "https://files.pythonhosted.org/packages/2c/65/529808e91d651147edae408fd9e894abc3b8cad7f3e594bbc36719a3e13a/coverage-7.15.2-cp310-cp310-win32.whl", hash = "sha256:1268ac8fb9ddcd783d3948dbabaf80a5d53bfdaa0575e873e2139a692f797443", size = 223334, upload-time = "2026-07-15T18:53:48.768Z" }, + { url = "https://files.pythonhosted.org/packages/68/0f/0e1829d7001130876dfbc0b4e1c737ea7c155b809e3e4a98a0aa268e2369/coverage-7.15.2-cp310-cp310-win_amd64.whl", hash = "sha256:9f4432898c4bf2fba0435bbe35dd4437d7264565e5a88a21f5b49d8662a6b629", size = 223959, upload-time = "2026-07-15T18:53:50.429Z" }, + { url = "https://files.pythonhosted.org/packages/7d/3a/54536704f507d4573bf9161c4d0dd3dd59b6d85e48c664e901b6844d8e33/coverage-7.15.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f1ec6f304b156669cfde653b4e9a953f5de87e247ea02ac599bce0ab2744036", size = 221414, upload-time = "2026-07-15T18:53:51.941Z" }, + { url = "https://files.pythonhosted.org/packages/b6/d9/8ba925d29743e3577b21e4d8c11a702b76bc93c41e7fdfd1177af63d4b8d/coverage-7.15.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4d3361879d736f469f45723c11ea1a5bbdaf1f6928f0e632c940378b5aa9b660", size = 221913, upload-time = "2026-07-15T18:53:53.682Z" }, + { url = "https://files.pythonhosted.org/packages/09/54/a855f3aa0187f2b431ade4e4791b77b56282cfb5d201c83ec26a31b5b36a/coverage-7.15.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c6a98d698f9e2c8008d0370ec7fc452ebfcc530002ae2d0061170d768b992589", size = 252332, upload-time = "2026-07-15T18:53:55.467Z" }, + { url = "https://files.pythonhosted.org/packages/8e/d3/13ac97b4370640ba3452fc8559b06cc2f479ce3ba4a0b632a73e44c38a7d/coverage-7.15.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d50dd325e18ec25bfcc10cd7f99b04df1ab9ec76b0918c260e60817ad0643dee", size = 254243, upload-time = "2026-07-15T18:53:57.055Z" }, + { url = "https://files.pythonhosted.org/packages/88/83/5eca144942d8d0659d3f55176517f4a59cdc65eefd17146a0770935a3ebd/coverage-7.15.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67d7602480a47bdf5b675635403625553ebaa70d5a62a657c035149fd401cea0", size = 256352, upload-time = "2026-07-15T18:53:58.83Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ba/d3db2e01a50fc88cdb4c0f19542bcf6f61489e34dc9aa3538413e2459a38/coverage-7.15.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cee0f89f4767a6057c8fbf168f8135f18be651300496086bd873e3189fed0487", size = 258313, upload-time = "2026-07-15T18:54:00.497Z" }, + { url = "https://files.pythonhosted.org/packages/78/b3/aba83416e9177df28e5186d856c19158c59fc0e7e814aaa61a4a2354ad1b/coverage-7.15.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a29ec5305a7335aacee2d799e3422e91e1c8a12474986e2b3b07e315c91be82f", size = 252449, upload-time = "2026-07-15T18:54:02.456Z" }, + { url = "https://files.pythonhosted.org/packages/6a/a5/4b00ecac0194431ab451b0f6710f8e2517d04cef60f821b14dec4637d575/coverage-7.15.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:48ccc6395958eda89093ecdc35644c86f23a8b23a7f4d44958812b721aad67c1", size = 254043, upload-time = "2026-07-15T18:54:04.072Z" }, + { url = "https://files.pythonhosted.org/packages/75/b6/cfa209b4313ee7f1b34da47efcd789ea51c024ad35af390e00f5a3c10a2e/coverage-7.15.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:81f382c5a94b434ec1f6da607edb904c76d7212e618cd4d1bc9f97bed4120ef5", size = 252107, upload-time = "2026-07-15T18:54:06.745Z" }, + { url = "https://files.pythonhosted.org/packages/36/67/e8cac5a6954038c98d7fe7eb9802afe7ab3ecb637bb7cc00e69b4148b56d/coverage-7.15.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bbc808daf4f5cd567af8075ecc72d21c6dfef9a254709a621a84c217c935ebc0", size = 255873, upload-time = "2026-07-15T18:54:08.48Z" }, + { url = "https://files.pythonhosted.org/packages/2c/92/395cca9f330a86c3fe3471d73e2c102116c4c58fdc619dbbc125c6e93a54/coverage-7.15.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a4c46b247b5d4b78f613bd89fea926d32b25c6cc61a50bd1e99ba310348f3dad", size = 251826, upload-time = "2026-07-15T18:54:10.083Z" }, + { url = "https://files.pythonhosted.org/packages/51/60/3e91b20295439652424f426b7086ec5bf4fbe3f604c73eda22b986c4fd6b/coverage-7.15.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:094dd37f3ef7b2da8b068b583d1f4c40f91c65197e16c52a71962d5d537fc5db", size = 252735, upload-time = "2026-07-15T18:54:11.878Z" }, + { url = "https://files.pythonhosted.org/packages/a5/eb/8c07839005e5e3c6b3877d3a6e2a80ce766589f31dd2b6882b78d59a7b8c/coverage-7.15.2-cp311-cp311-win32.whl", hash = "sha256:a63b9e190711134d581c4d703df5df09851b1acf99792c7aacbbe9f41f0283c9", size = 223500, upload-time = "2026-07-15T18:54:13.525Z" }, + { url = "https://files.pythonhosted.org/packages/2e/98/59d83c257cd59f0fbaf9d9ddb26b744a576760dfd1ae16e516408894a02b/coverage-7.15.2-cp311-cp311-win_amd64.whl", hash = "sha256:8bb9f4b4279187560796a4cdaca3b0a93dd97e48ee667df005f4ed9a97403688", size = 223973, upload-time = "2026-07-15T18:54:15.163Z" }, + { url = "https://files.pythonhosted.org/packages/ea/09/2d285c8bef5c4f695d120c1c96dc11715638aa8e134069f210bb6a62a9fe/coverage-7.15.2-cp311-cp311-win_arm64.whl", hash = "sha256:8c726b232659cbd2ae57ade46509eb068c9bd7a06df9fcbff6fe484870006934", size = 223519, upload-time = "2026-07-15T18:54:16.803Z" }, + { url = "https://files.pythonhosted.org/packages/6a/50/eb5bf42e531611a9f8d272556b1ed4de503f84a91413584094487cf69f8f/coverage-7.15.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1adac78e5abc7c5438f7a209c9ca69d06542f0bf481d728b6989ea80b813fdf9", size = 221587, upload-time = "2026-07-15T18:54:18.439Z" }, + { url = "https://files.pythonhosted.org/packages/06/d1/da99af464c335d4e023a6efcd7ec30f63b88a43c93745154ab74ffb31cea/coverage-7.15.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b868acc62aa5de3be7a9d05c2333bf8359ca987e43f9cb30ff8fbda6a024ab73", size = 221943, upload-time = "2026-07-15T18:54:20.062Z" }, + { url = "https://files.pythonhosted.org/packages/5b/8a/13c42723d61ca447eafa18732e8141dd6a63f2732e1c7e1502c182dd88d7/coverage-7.15.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6f6966fc30e6f06ca8f98fb0ce51eda6b111b3ee8d066a8b1ec9e77fa06ab55d", size = 253450, upload-time = "2026-07-15T18:54:21.765Z" }, + { url = "https://files.pythonhosted.org/packages/d7/29/99021303f98fbdcb63504b4d07bea4cc025b9b2dd907c4f07c85d50a0dab/coverage-7.15.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:68af907f595ab01a78f794932ff3bdf929c316d3000810d38dbc247129e26f8b", size = 256187, upload-time = "2026-07-15T18:54:23.4Z" }, + { url = "https://files.pythonhosted.org/packages/f9/a8/fd503715ed6ca9c5d742923aa5209257340b367a867b2ced0c7d4ba8a0b9/coverage-7.15.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:afa29e2eff3d5729267e2cb2fd4ce9d61c952932fb2694e34ccb5d9540c6a296", size = 257301, upload-time = "2026-07-15T18:54:25.183Z" }, + { url = "https://files.pythonhosted.org/packages/da/40/3f4b8fb409810036ebc2857d36adc0498c6e957b5df0290c5036b2e143f1/coverage-7.15.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bbf44513ceb1589e31948e20eafbde9deaface90e1a1afa5f5f77b4423d17ce6", size = 259562, upload-time = "2026-07-15T18:54:27.204Z" }, + { url = "https://files.pythonhosted.org/packages/0b/8a/9bdffbef47db77cce3d6b02a28f7e919b19f0106c4b080c2c2246040f885/coverage-7.15.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9deddf09eecb717b7f980414b43d90a5b22ff3967d2949ab29cb0aa83d9e9098", size = 253841, upload-time = "2026-07-15T18:54:29.134Z" }, + { url = "https://files.pythonhosted.org/packages/1b/1e/9031efde019d31a06646261fce6dfc5c3c74e951e27a71e5c9a424563178/coverage-7.15.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ae901f7e55ba405c84ee1cab3d3e962e4e871e4a2bcb9c90911adbd69b42ac5a", size = 255221, upload-time = "2026-07-15T18:54:31.142Z" }, + { url = "https://files.pythonhosted.org/packages/56/db/787acde872389fc84a9ef9d8cd1ccc658e391ab4cb5b28092a714426a394/coverage-7.15.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a0f47002c6eeb7c280228467a4cb0cc15ca2103a8421b986b2d3ec04a0f9bd8b", size = 253366, upload-time = "2026-07-15T18:54:32.886Z" }, + { url = "https://files.pythonhosted.org/packages/2f/9b/6f57bc4b93c842eef1695f8cdaf2318e35e7ba54f5ba80d84be213ab7858/coverage-7.15.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1cd7a5beb7af3e864a13b1f0fb26efd3695da43ef0daf71e586adfffaf34d5b2", size = 257434, upload-time = "2026-07-15T18:54:34.7Z" }, + { url = "https://files.pythonhosted.org/packages/88/26/b3186a21b2acc83e451118978905c81c7072c3333707804db09a78c096a2/coverage-7.15.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:97a5c5457a9fb1d6c4e06cfb5dc835871fbfb6a6a51addc9e925bdeff5ef7440", size = 252935, upload-time = "2026-07-15T18:54:36.548Z" }, + { url = "https://files.pythonhosted.org/packages/20/c2/c9f3376b2e717ea69ed7a6e9a5fcab968fb0b290db6cf4bd9a1fc7541b75/coverage-7.15.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0901cfe6c13bcd2302da4f83e884555d2a22bda6e4c476f09ef204ba20ca536e", size = 254807, upload-time = "2026-07-15T18:54:38.296Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e1/dfc15401f4a8aaeb486e1ba3e9e3c40522a6e38bd0ecf0b3f29cb8082957/coverage-7.15.2-cp312-cp312-win32.whl", hash = "sha256:b171bdd71cb7ff792bf32e376173b0ace7e7963e7e57c58dfc42063a6a7174cd", size = 223641, upload-time = "2026-07-15T18:54:40.103Z" }, + { url = "https://files.pythonhosted.org/packages/91/40/81b6d809d320cd366ec5bdf8176575e897dcb8efe7fb4b489ef9e93e4d13/coverage-7.15.2-cp312-cp312-win_amd64.whl", hash = "sha256:582edc45c2040543fef83341be23c43024a3ab3ae0c2d8bc498a06282905ad40", size = 224172, upload-time = "2026-07-15T18:54:41.882Z" }, + { url = "https://files.pythonhosted.org/packages/ef/28/9f14ec438149f7de557f45518f09b4a7917b795cc37083aa7db482693f8c/coverage-7.15.2-cp312-cp312-win_arm64.whl", hash = "sha256:a638db90c61cd219aeee65e83a24fdaa57269a741ae0cf773309208ac862cee3", size = 223556, upload-time = "2026-07-15T18:54:43.674Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d5/f8c838e6b7282976f7c918884b792df7a0c42c5bba5d99c60ad2d221d56d/coverage-7.15.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1121caa19159a38b5463eaae4b1e1fde81e525b15ecc5e000cd5b1a108f743a8", size = 221606, upload-time = "2026-07-15T18:54:45.448Z" }, + { url = "https://files.pythonhosted.org/packages/bf/37/97c926376364f66298cc44893b89cdf17b8bc406376497c4061ae4b8a8ff/coverage-7.15.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a300c6934e0989c327b9e8a1e110329da4641149f872bbe9f70168be66da76c1", size = 221982, upload-time = "2026-07-15T18:54:47.341Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/a36050a6e83c2135ee0776f452ca3948224befc6d7f26acecc082d0c106a/coverage-7.15.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2617f8799d268fabdeef42a7e89ac3a23e1deee9025427db2df970f99a89a578", size = 252972, upload-time = "2026-07-15T18:54:49.2Z" }, + { url = "https://files.pythonhosted.org/packages/31/d3/06b5f1daf95f0f15ab05bd75f26ba5f3c8b33d0bb72f3aaa3cf41d1bad3a/coverage-7.15.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7dc2950a2992cd676d35c20ae63522836deeb034f08874699d14068710af3dc1", size = 255569, upload-time = "2026-07-15T18:54:51.098Z" }, + { url = "https://files.pythonhosted.org/packages/81/1c/9afb3f8de2b8d36960391c48559a2e3ff96594b58099f115921549ea8d0d/coverage-7.15.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9e36686f7a442185db2400b3df171aac520869faf9deb59df687d28659eda2a6", size = 256806, upload-time = "2026-07-15T18:54:53.145Z" }, + { url = "https://files.pythonhosted.org/packages/64/d8/b989f96061a5e32d82fddd1b1b9ff48a7c8f8ae7606f0e80fd9de54b1e33/coverage-7.15.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d29ca7bd67af6e12e74632d65f026eabc1364da5c254494cd914446a28a3ef7", size = 258936, upload-time = "2026-07-15T18:54:55.015Z" }, + { url = "https://files.pythonhosted.org/packages/b8/fa/f99771f5110457c7b511c1935ca49ddf288218eaa84322e028b9334146ae/coverage-7.15.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:db9c8438057e5b0f6a22a0af99c0c1d26b57fbbdbd1be5861ddb8f897fcc3a2d", size = 253178, upload-time = "2026-07-15T18:54:57.527Z" }, + { url = "https://files.pythonhosted.org/packages/f6/96/c098a6044d119c751ceede7be91035fa8310170ec24a6523aff72f0a5793/coverage-7.15.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:63022c4c8dec1d0342f05c3ede99842fe3d007689acc45e86f123a1746e4a026", size = 254934, upload-time = "2026-07-15T18:54:59.41Z" }, + { url = "https://files.pythonhosted.org/packages/b2/a2/1457b3a7a50c8d77500103b97a046db863e2f59a1cf6d2f814595f349885/coverage-7.15.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6c0be82b4d4aa5b2704e08518e2252f3e3d110164bcca826816801052e48a7aa", size = 252898, upload-time = "2026-07-15T18:55:01.338Z" }, + { url = "https://files.pythonhosted.org/packages/6c/0e/76958874c471ecfcdde0d2b2747bb2c61bdbf34a40636f4ce9db9923e643/coverage-7.15.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4510fb9cdf6bb02dfa6af0be4a534b8102d086e22e4a33f8836df663da3d660d", size = 257056, upload-time = "2026-07-15T18:55:03.243Z" }, + { url = "https://files.pythonhosted.org/packages/7c/7c/3d7c4e3bf58baa40327dc7edc2272b17cf02299366d52763db1b0ca1556a/coverage-7.15.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:42ec3d989421b174a2ab607c1539f24127ad362757b7f1c0c0d7a2993f7eb37b", size = 252718, upload-time = "2026-07-15T18:55:05.029Z" }, + { url = "https://files.pythonhosted.org/packages/c8/b8/1cecffed9ce14fb25be9ba42d37b6bb61485c9a3ddd43cd3dde36b6087d8/coverage-7.15.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e8f91bce78e32343af184c3b7fa28fcf5a9e2641f4b6623d392038f804939188", size = 254490, upload-time = "2026-07-15T18:55:06.889Z" }, + { url = "https://files.pythonhosted.org/packages/6c/2c/42984561bc7f4c045dca67516a0c50ee5ef8d84352dbeb5559dc86c4823e/coverage-7.15.2-cp313-cp313-win32.whl", hash = "sha256:434e68d531858205895eb0d74b73d20b84260de426387d53c422a5acda2cf050", size = 223647, upload-time = "2026-07-15T18:55:08.941Z" }, + { url = "https://files.pythonhosted.org/packages/41/9f/39c7c9245efc583beddf89a87683574e663ed93637f3afb6cd7b88405676/coverage-7.15.2-cp313-cp313-win_amd64.whl", hash = "sha256:26c3b04a6377fd7c09800921fa934e3a17c0020439cd59df73e73ae1d4b6a78c", size = 224190, upload-time = "2026-07-15T18:55:10.789Z" }, + { url = "https://files.pythonhosted.org/packages/c7/de/3a2883cf8a213659280ef4b403059e17a9acaeb7fc7fd4105e1226ff2e6d/coverage-7.15.2-cp313-cp313-win_arm64.whl", hash = "sha256:3ed010aa1b69cda8e827aabfca9866216c980e2dca82ab9a78c5f83689964c8b", size = 223583, upload-time = "2026-07-15T18:55:12.678Z" }, + { url = "https://files.pythonhosted.org/packages/81/5f/aed265fd7a3551a394f36dfe41868aee709b7f95db4052205b4ad1563ac3/coverage-7.15.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:40f633c5c5fc783732f6312280122e859538fa24461235597c13d803ea9a108a", size = 221650, upload-time = "2026-07-15T18:55:14.527Z" }, + { url = "https://files.pythonhosted.org/packages/6b/2c/222ba12a545189017120f8eddfc1a0bd4616b47d5d4a8d99421edb2fe4c6/coverage-7.15.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:075560438765b7a2ef43bf7aa7758661b53d889df47f062a31bda6c1ade553a2", size = 221988, upload-time = "2026-07-15T18:55:16.674Z" }, + { url = "https://files.pythonhosted.org/packages/aa/38/304b5877ab46e6c290b4292cfcf3fe28245f0e5597cad7f6acc91fc7e0a4/coverage-7.15.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:25fd15dd40a0a2c51a500d664ca29053c09c3259d998407bf982b6e114696138", size = 253029, upload-time = "2026-07-15T18:55:18.856Z" }, + { url = "https://files.pythonhosted.org/packages/6c/58/821b533b8db9e44cf1d8a97bd525149ced40dde1d0093da02cb78e715244/coverage-7.15.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b9a6367e4aff723e8ee8190836836124284e8fcd4265e307c844010cfa074f3f", size = 255536, upload-time = "2026-07-15T18:55:21.027Z" }, + { url = "https://files.pythonhosted.org/packages/f1/f2/7aa06604c389d32ea7f0a6a988359a7eafc3cd3f8e7bc2e88cd2fdf0b877/coverage-7.15.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9854ca62c152874b2060772503535be2e8f53f70b8aaa7686b094888d872f984", size = 256881, upload-time = "2026-07-15T18:55:23.125Z" }, + { url = "https://files.pythonhosted.org/packages/a2/4f/1ef342339c7916d0096bc5888cc0f653882cc7bc8f897d5cb89143287c9b/coverage-7.15.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:913b6c56e110da40e035bbd168353bf7aaa2544a5eaccea5d98a4629aac156c7", size = 259196, upload-time = "2026-07-15T18:55:25.099Z" }, + { url = "https://files.pythonhosted.org/packages/fe/f4/7ed055d7a9c5ec13b161773a115a5ccc6b0081d568c31fad830806306cc7/coverage-7.15.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aaccad4129d735a8a4d526f26929894c9a4e8ef7034566f210b176749d6906e3", size = 253036, upload-time = "2026-07-15T18:55:27.018Z" }, + { url = "https://files.pythonhosted.org/packages/14/79/ea82cca18c242a3a38b6c017da39726aa62dcb64aa635abf79b92009975c/coverage-7.15.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a164b50081fc7357331c4024ef4d17b78ba325f8380d05f5a69599a7e05257ee", size = 254887, upload-time = "2026-07-15T18:55:29.084Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ba/a136db3c0d9562b00e10b72540dbf3a33cd3bc5b95060c9308e247494623/coverage-7.15.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:bfd341ccf78128e72c094bc70cc25b3ef309c33c7c2c66ba3ed4309549e02de1", size = 252852, upload-time = "2026-07-15T18:55:31.184Z" }, + { url = "https://files.pythonhosted.org/packages/17/17/ea334246b16b7d059953fad6fdefa11e33c68efbd3fe37b1098120a1fac2/coverage-7.15.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:1473b3ba8e7ee0f076117b1a72c23f579a2b9e2bb742f48a8d86ea27ca93f91a", size = 257128, upload-time = "2026-07-15T18:55:33.163Z" }, + { url = "https://files.pythonhosted.org/packages/ed/c3/074fb66d46d607855f710876b117cbda562c5ab08363528e78820449f937/coverage-7.15.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:17c432b5f73ad52ef46fb06019f6fa7c66ce381961cf0f7dfd1d3a4bd3a98145", size = 252668, upload-time = "2026-07-15T18:55:35.063Z" }, + { url = "https://files.pythonhosted.org/packages/e1/c1/f620850ada9b36435921c9a3a8057013422b1d964eb4bf37fe138724d192/coverage-7.15.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:77f0ef5011df53a4bd1b35211ab122287f8d9b8d7aa1c4553e5c2deb24b1d446", size = 254325, upload-time = "2026-07-15T18:55:37.125Z" }, + { url = "https://files.pythonhosted.org/packages/cc/31/a729ca3689404493af82ef8e6ff70bd88bdda8da89aeef6ca9b387aeb2b4/coverage-7.15.2-cp314-cp314-win32.whl", hash = "sha256:f653e5d7248c1191ec988a85c72edeab46c3ff44f90639a4ed4874ec0be90243", size = 223844, upload-time = "2026-07-15T18:55:39.078Z" }, + { url = "https://files.pythonhosted.org/packages/c6/83/5d809dc808fb1698c671f3e372259bb9158e64b7ea526fc6ab7de64de9fe/coverage-7.15.2-cp314-cp314-win_amd64.whl", hash = "sha256:9911f31aad8906abe337c271343485cf20df5e70df5d2f57f9f136e7b55f26bc", size = 224331, upload-time = "2026-07-15T18:55:41.346Z" }, + { url = "https://files.pythonhosted.org/packages/16/4e/35e488548e952795829e129995c4174df33bf432b591d1aa42c8d9e4e7ad/coverage-7.15.2-cp314-cp314-win_arm64.whl", hash = "sha256:e38def96ad59853824c97953fdcd2c320a84ba3ce99b417db78af8bb6c3db635", size = 223760, upload-time = "2026-07-15T18:55:43.518Z" }, + { url = "https://files.pythonhosted.org/packages/ed/49/dd2c86cd6374038f6e415fb5bfb86db5218553209c081384a020369dee79/coverage-7.15.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:835ec4e20b45f0a7f63ed78f94065aca00de033403df8377bfe8b9c6abc0a7be", size = 222384, upload-time = "2026-07-15T18:55:45.569Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/173ff17a1c0808e5a438f549f6f145d5ac7528f2791310b63523e3200ac7/coverage-7.15.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7466cc7ab6dc0db871d264bf99e8779f0917ee63d40730af0552f71535a6e072", size = 222647, upload-time = "2026-07-15T18:55:47.544Z" }, + { url = "https://files.pythonhosted.org/packages/84/f8/b8cba872162356fb44ac79c10309d987206a4461e32072fc29228dad7331/coverage-7.15.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e370c12133095ff18432de8c044962be85a5a96d90c6fcbce8e17e76236d2328", size = 264013, upload-time = "2026-07-15T18:55:49.768Z" }, + { url = "https://files.pythonhosted.org/packages/ee/67/a807a7586d0b8cae485308ddd55756f0806c92f8e0b411bacbf23c48edf3/coverage-7.15.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fe41909c9515c3bfdb5f02c4d1f857dba322d9a9a1178069b91eea77889df63a", size = 266135, upload-time = "2026-07-15T18:55:51.941Z" }, + { url = "https://files.pythonhosted.org/packages/ce/67/cd78771dc985f7e4ebdcc82b1a96d9a932af9e806f01f2f91a89f4c72e80/coverage-7.15.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6aa28cfb6488e5453b5b762d65f73aa586380f6693a04d58078ce228a29b06c0", size = 268555, upload-time = "2026-07-15T18:55:54.065Z" }, + { url = "https://files.pythonhosted.org/packages/18/3e/10134cf81275188c58568f324fc74aedff32c63ca4d5bbc513a91944a6f0/coverage-7.15.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bcc0aae933921d03096f53b0b03eeb702129fd406dee59f08d2efacc68681fa5", size = 269674, upload-time = "2026-07-15T18:55:56.066Z" }, + { url = "https://files.pythonhosted.org/packages/75/4a/771b77de446cba985dc414bbc5844bd21604da05dbc044286df8318a48a7/coverage-7.15.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7c63387e21ab21f512c69c9756a8c7dadd322c7275edb064064433c9a09c3743", size = 263101, upload-time = "2026-07-15T18:55:58.107Z" }, + { url = "https://files.pythonhosted.org/packages/5f/b5/70a7011da15f4071943361183aefa27847f3e3aec4fd335f1cb3d3a622b1/coverage-7.15.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e55510bc98ae943cece9e667a6c0fe94c6a92913720dea34243657a17993d0c", size = 266007, upload-time = "2026-07-15T18:56:00.468Z" }, + { url = "https://files.pythonhosted.org/packages/b4/0d/f9547e804ce7ad49646ffeffac26699510efbe6c0f751b66fdc960c4e825/coverage-7.15.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:2ff08701be2d1556fc78b326c80a3e8042da09352ecb3819105f8e386c8a3071", size = 263611, upload-time = "2026-07-15T18:56:02.615Z" }, + { url = "https://files.pythonhosted.org/packages/ac/59/f576a396659c0efd351f5c1544f67c3560e89c7761cabf7f65e412beeda5/coverage-7.15.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:38c9518b7103826c403a461544e3c2e77151e8676d06eaed85911a97e962584a", size = 267344, upload-time = "2026-07-15T18:56:04.622Z" }, + { url = "https://files.pythonhosted.org/packages/7c/5d/c2e4fce3579c0cb635024293f1a32bbe26df101b3e3a69f22243d1352b6c/coverage-7.15.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:dee88b1ed88587abd8c0269a1fc1f4cc77f7750d1dfde2869e2a123af420e67d", size = 262456, upload-time = "2026-07-15T18:56:06.641Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/956287d69436b66094bc4b57ac2da71e43bfd2a5524e958900b9f582fcf8/coverage-7.15.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2fbeeeecea279727f8ac16c8e1133ddfeee793e985c86ae343d6a5ce744eef8c", size = 264771, upload-time = "2026-07-15T18:56:08.795Z" }, + { url = "https://files.pythonhosted.org/packages/2c/5a/6f979530c2734c575de77cf58f5f28d51f7123a94b5030fd9156fe5f363c/coverage-7.15.2-cp314-cp314t-win32.whl", hash = "sha256:cb0fddaa6884be6aae36ced9544b5e90f7d5f03845a2853bf47a14953a4e8688", size = 224151, upload-time = "2026-07-15T18:56:10.856Z" }, + { url = "https://files.pythonhosted.org/packages/54/7e/27f6b2a74d484742f4017553e710b01e396b23d809df3e95ca0bb9a2824b/coverage-7.15.2-cp314-cp314t-win_amd64.whl", hash = "sha256:77f091ea3a9cc611cd29f433565476bc1936c084ac8eee00ea0e7e70c27e4199", size = 224981, upload-time = "2026-07-15T18:56:12.928Z" }, + { url = "https://files.pythonhosted.org/packages/b1/48/284863423aa474240f6842bd00d680da22f4e6ea2e466618ef7c9c9e69a9/coverage-7.15.2-cp314-cp314t-win_arm64.whl", hash = "sha256:6fc448c377d6eeb00a47c673494bd9bae29280ca53987e1869e67ebedfe20658", size = 224294, upload-time = "2026-07-15T18:56:15.156Z" }, + { url = "https://files.pythonhosted.org/packages/ec/82/32e3bd191d498e64f6f911ad55d14006a0861e54869d2d32452326399e65/coverage-7.15.2-py3-none-any.whl", hash = "sha256:eb6bcae8d1a9d305351ecb108232441d11c5cfe9de840a04388ba5d2db8d735c", size = 213375, upload-time = "2026-07-15T18:56:17.305Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "9.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/01/15bb152d77b21318514a96f43af312635eb2500c96b55398d020c93d86ea/importlib_metadata-9.0.0.tar.gz", hash = "sha256:a4f57ab599e6a2e3016d7595cfd72eb4661a5106e787a95bcc90c7105b831efc", size = 56405, upload-time = "2026-03-20T06:42:56.999Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/3d/2d244233ac4f76e38533cfcb2991c9eb4c7bf688ae0a036d30725b8faafe/importlib_metadata-9.0.0-py3-none-any.whl", hash = "sha256:2d21d1cc5a017bd0559e36150c21c830ab1dc304dedd1b7ea85d20f45ef3edd7", size = 27789, upload-time = "2026-03-20T06:42:55.665Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "mypy" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8e/22/ea637422dedf0bf36f3ef238eab4e455e2a0dcc3082b5cc067615347ab8e/mypy-1.17.1.tar.gz", hash = "sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01", size = 3352570, upload-time = "2025-07-31T07:54:19.204Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/a9/3d7aa83955617cdf02f94e50aab5c830d205cfa4320cf124ff64acce3a8e/mypy-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3fbe6d5555bf608c47203baa3e72dbc6ec9965b3d7c318aa9a4ca76f465bd972", size = 11003299, upload-time = "2025-07-31T07:54:06.425Z" }, + { url = "https://files.pythonhosted.org/packages/83/e8/72e62ff837dd5caaac2b4a5c07ce769c8e808a00a65e5d8f94ea9c6f20ab/mypy-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80ef5c058b7bce08c83cac668158cb7edea692e458d21098c7d3bce35a5d43e7", size = 10125451, upload-time = "2025-07-31T07:53:52.974Z" }, + { url = "https://files.pythonhosted.org/packages/7d/10/f3f3543f6448db11881776f26a0ed079865926b0c841818ee22de2c6bbab/mypy-1.17.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a580f8a70c69e4a75587bd925d298434057fe2a428faaf927ffe6e4b9a98df", size = 11916211, upload-time = "2025-07-31T07:53:18.879Z" }, + { url = "https://files.pythonhosted.org/packages/06/bf/63e83ed551282d67bb3f7fea2cd5561b08d2bb6eb287c096539feb5ddbc5/mypy-1.17.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd86bb649299f09d987a2eebb4d52d10603224500792e1bee18303bbcc1ce390", size = 12652687, upload-time = "2025-07-31T07:53:30.544Z" }, + { url = "https://files.pythonhosted.org/packages/69/66/68f2eeef11facf597143e85b694a161868b3b006a5fbad50e09ea117ef24/mypy-1.17.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a76906f26bd8d51ea9504966a9c25419f2e668f012e0bdf3da4ea1526c534d94", size = 12896322, upload-time = "2025-07-31T07:53:50.74Z" }, + { url = "https://files.pythonhosted.org/packages/a3/87/8e3e9c2c8bd0d7e071a89c71be28ad088aaecbadf0454f46a540bda7bca6/mypy-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:e79311f2d904ccb59787477b7bd5d26f3347789c06fcd7656fa500875290264b", size = 9507962, upload-time = "2025-07-31T07:53:08.431Z" }, + { url = "https://files.pythonhosted.org/packages/46/cf/eadc80c4e0a70db1c08921dcc220357ba8ab2faecb4392e3cebeb10edbfa/mypy-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad37544be07c5d7fba814eb370e006df58fed8ad1ef33ed1649cb1889ba6ff58", size = 10921009, upload-time = "2025-07-31T07:53:23.037Z" }, + { url = "https://files.pythonhosted.org/packages/5d/c1/c869d8c067829ad30d9bdae051046561552516cfb3a14f7f0347b7d973ee/mypy-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:064e2ff508e5464b4bd807a7c1625bc5047c5022b85c70f030680e18f37273a5", size = 10047482, upload-time = "2025-07-31T07:53:26.151Z" }, + { url = "https://files.pythonhosted.org/packages/98/b9/803672bab3fe03cee2e14786ca056efda4bb511ea02dadcedde6176d06d0/mypy-1.17.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70401bbabd2fa1aa7c43bb358f54037baf0586f41e83b0ae67dd0534fc64edfd", size = 11832883, upload-time = "2025-07-31T07:53:47.948Z" }, + { url = "https://files.pythonhosted.org/packages/88/fb/fcdac695beca66800918c18697b48833a9a6701de288452b6715a98cfee1/mypy-1.17.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e92bdc656b7757c438660f775f872a669b8ff374edc4d18277d86b63edba6b8b", size = 12566215, upload-time = "2025-07-31T07:54:04.031Z" }, + { url = "https://files.pythonhosted.org/packages/7f/37/a932da3d3dace99ee8eb2043b6ab03b6768c36eb29a02f98f46c18c0da0e/mypy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c1fdf4abb29ed1cb091cf432979e162c208a5ac676ce35010373ff29247bcad5", size = 12751956, upload-time = "2025-07-31T07:53:36.263Z" }, + { url = "https://files.pythonhosted.org/packages/8c/cf/6438a429e0f2f5cab8bc83e53dbebfa666476f40ee322e13cac5e64b79e7/mypy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:ff2933428516ab63f961644bc49bc4cbe42bbffb2cd3b71cc7277c07d16b1a8b", size = 9507307, upload-time = "2025-07-31T07:53:59.734Z" }, + { url = "https://files.pythonhosted.org/packages/17/a2/7034d0d61af8098ec47902108553122baa0f438df8a713be860f7407c9e6/mypy-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:69e83ea6553a3ba79c08c6e15dbd9bfa912ec1e493bf75489ef93beb65209aeb", size = 11086295, upload-time = "2025-07-31T07:53:28.124Z" }, + { url = "https://files.pythonhosted.org/packages/14/1f/19e7e44b594d4b12f6ba8064dbe136505cec813549ca3e5191e40b1d3cc2/mypy-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b16708a66d38abb1e6b5702f5c2c87e133289da36f6a1d15f6a5221085c6403", size = 10112355, upload-time = "2025-07-31T07:53:21.121Z" }, + { url = "https://files.pythonhosted.org/packages/5b/69/baa33927e29e6b4c55d798a9d44db5d394072eef2bdc18c3e2048c9ed1e9/mypy-1.17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89e972c0035e9e05823907ad5398c5a73b9f47a002b22359b177d40bdaee7056", size = 11875285, upload-time = "2025-07-31T07:53:55.293Z" }, + { url = "https://files.pythonhosted.org/packages/90/13/f3a89c76b0a41e19490b01e7069713a30949d9a6c147289ee1521bcea245/mypy-1.17.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341", size = 12737895, upload-time = "2025-07-31T07:53:43.623Z" }, + { url = "https://files.pythonhosted.org/packages/23/a1/c4ee79ac484241301564072e6476c5a5be2590bc2e7bfd28220033d2ef8f/mypy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c837b896b37cd103570d776bda106eabb8737aa6dd4f248451aecf53030cdbeb", size = 12931025, upload-time = "2025-07-31T07:54:17.125Z" }, + { url = "https://files.pythonhosted.org/packages/89/b8/7409477be7919a0608900e6320b155c72caab4fef46427c5cc75f85edadd/mypy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:665afab0963a4b39dff7c1fa563cc8b11ecff7910206db4b2e64dd1ba25aed19", size = 9584664, upload-time = "2025-07-31T07:54:12.842Z" }, + { url = "https://files.pythonhosted.org/packages/5b/82/aec2fc9b9b149f372850291827537a508d6c4d3664b1750a324b91f71355/mypy-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93378d3203a5c0800c6b6d850ad2f19f7a3cdf1a3701d3416dbf128805c6a6a7", size = 11075338, upload-time = "2025-07-31T07:53:38.873Z" }, + { url = "https://files.pythonhosted.org/packages/07/ac/ee93fbde9d2242657128af8c86f5d917cd2887584cf948a8e3663d0cd737/mypy-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15d54056f7fe7a826d897789f53dd6377ec2ea8ba6f776dc83c2902b899fee81", size = 10113066, upload-time = "2025-07-31T07:54:14.707Z" }, + { url = "https://files.pythonhosted.org/packages/5a/68/946a1e0be93f17f7caa56c45844ec691ca153ee8b62f21eddda336a2d203/mypy-1.17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:209a58fed9987eccc20f2ca94afe7257a8f46eb5df1fb69958650973230f91e6", size = 11875473, upload-time = "2025-07-31T07:53:14.504Z" }, + { url = "https://files.pythonhosted.org/packages/9f/0f/478b4dce1cb4f43cf0f0d00fba3030b21ca04a01b74d1cd272a528cf446f/mypy-1.17.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:099b9a5da47de9e2cb5165e581f158e854d9e19d2e96b6698c0d64de911dd849", size = 12744296, upload-time = "2025-07-31T07:53:03.896Z" }, + { url = "https://files.pythonhosted.org/packages/ca/70/afa5850176379d1b303f992a828de95fc14487429a7139a4e0bdd17a8279/mypy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ffadfbe6994d724c5a1bb6123a7d27dd68fc9c059561cd33b664a79578e14", size = 12914657, upload-time = "2025-07-31T07:54:08.576Z" }, + { url = "https://files.pythonhosted.org/packages/53/f9/4a83e1c856a3d9c8f6edaa4749a4864ee98486e9b9dbfbc93842891029c2/mypy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:9a2b7d9180aed171f033c9f2fc6c204c1245cf60b0cb61cf2e7acc24eea78e0a", size = 9593320, upload-time = "2025-07-31T07:53:01.341Z" }, + { url = "https://files.pythonhosted.org/packages/38/56/79c2fac86da57c7d8c48622a05873eaab40b905096c33597462713f5af90/mypy-1.17.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:15a83369400454c41ed3a118e0cc58bd8123921a602f385cb6d6ea5df050c733", size = 11040037, upload-time = "2025-07-31T07:54:10.942Z" }, + { url = "https://files.pythonhosted.org/packages/4d/c3/adabe6ff53638e3cad19e3547268482408323b1e68bf082c9119000cd049/mypy-1.17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:55b918670f692fc9fba55c3298d8a3beae295c5cded0a55dccdc5bbead814acd", size = 10131550, upload-time = "2025-07-31T07:53:41.307Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c5/2e234c22c3bdeb23a7817af57a58865a39753bde52c74e2c661ee0cfc640/mypy-1.17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62761474061feef6f720149d7ba876122007ddc64adff5ba6f374fda35a018a0", size = 11872963, upload-time = "2025-07-31T07:53:16.878Z" }, + { url = "https://files.pythonhosted.org/packages/ab/26/c13c130f35ca8caa5f2ceab68a247775648fdcd6c9a18f158825f2bc2410/mypy-1.17.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c49562d3d908fd49ed0938e5423daed8d407774a479b595b143a3d7f87cdae6a", size = 12710189, upload-time = "2025-07-31T07:54:01.962Z" }, + { url = "https://files.pythonhosted.org/packages/82/df/c7d79d09f6de8383fe800521d066d877e54d30b4fb94281c262be2df84ef/mypy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:397fba5d7616a5bc60b45c7ed204717eaddc38f826e3645402c426057ead9a91", size = 12900322, upload-time = "2025-07-31T07:53:10.551Z" }, + { url = "https://files.pythonhosted.org/packages/b8/98/3d5a48978b4f708c55ae832619addc66d677f6dc59f3ebad71bae8285ca6/mypy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:9d6b20b97d373f41617bd0708fd46aa656059af57f2ef72aa8c7d6a2b73b74ed", size = 9751879, upload-time = "2025-07-31T07:52:56.683Z" }, + { url = "https://files.pythonhosted.org/packages/1d/f3/8fcd2af0f5b806f6cf463efaffd3c9548a28f84220493ecd38d127b6b66d/mypy-1.17.1-py3-none-any.whl", hash = "sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9", size = 2283411, upload-time = "2025-07-31T07:53:24.664Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "pathspec" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, +] + +[[package]] +name = "phaseprobe" +version = "0.1.0" +source = { editable = "." } + +[package.optional-dependencies] +demo = [ + { name = "pillow" }, +] +dev = [ + { name = "build" }, + { name = "mypy" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "build", marker = "extra == 'dev'", specifier = "==1.3.0" }, + { name = "mypy", marker = "extra == 'dev'", specifier = "==1.17.1" }, + { name = "pillow", marker = "extra == 'demo'", specifier = "==11.3.0" }, + { name = "pytest", marker = "extra == 'dev'", specifier = "==8.4.1" }, + { name = "pytest-cov", marker = "extra == 'dev'", specifier = "==6.2.1" }, + { name = "ruff", marker = "extra == 'dev'", specifier = "==0.12.7" }, +] +provides-extras = ["dev", "demo"] + +[[package]] +name = "pillow" +version = "11.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069, upload-time = "2025-07-01T09:16:30.666Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/5d/45a3553a253ac8763f3561371432a90bdbe6000fbdcf1397ffe502aa206c/pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860", size = 5316554, upload-time = "2025-07-01T09:13:39.342Z" }, + { url = "https://files.pythonhosted.org/packages/7c/c8/67c12ab069ef586a25a4a79ced553586748fad100c77c0ce59bb4983ac98/pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad", size = 4686548, upload-time = "2025-07-01T09:13:41.835Z" }, + { url = "https://files.pythonhosted.org/packages/2f/bd/6741ebd56263390b382ae4c5de02979af7f8bd9807346d068700dd6d5cf9/pillow-11.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7107195ddc914f656c7fc8e4a5e1c25f32e9236ea3ea860f257b0436011fddd0", size = 5859742, upload-time = "2025-07-03T13:09:47.439Z" }, + { url = "https://files.pythonhosted.org/packages/ca/0b/c412a9e27e1e6a829e6ab6c2dca52dd563efbedf4c9c6aa453d9a9b77359/pillow-11.3.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc3e831b563b3114baac7ec2ee86819eb03caa1a2cef0b481a5675b59c4fe23b", size = 7633087, upload-time = "2025-07-03T13:09:51.796Z" }, + { url = "https://files.pythonhosted.org/packages/59/9d/9b7076aaf30f5dd17e5e5589b2d2f5a5d7e30ff67a171eb686e4eecc2adf/pillow-11.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1f182ebd2303acf8c380a54f615ec883322593320a9b00438eb842c1f37ae50", size = 5963350, upload-time = "2025-07-01T09:13:43.865Z" }, + { url = "https://files.pythonhosted.org/packages/f0/16/1a6bf01fb622fb9cf5c91683823f073f053005c849b1f52ed613afcf8dae/pillow-11.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4445fa62e15936a028672fd48c4c11a66d641d2c05726c7ec1f8ba6a572036ae", size = 6631840, upload-time = "2025-07-01T09:13:46.161Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e6/6ff7077077eb47fde78739e7d570bdcd7c10495666b6afcd23ab56b19a43/pillow-11.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:71f511f6b3b91dd543282477be45a033e4845a40278fa8dcdbfdb07109bf18f9", size = 6074005, upload-time = "2025-07-01T09:13:47.829Z" }, + { url = "https://files.pythonhosted.org/packages/c3/3a/b13f36832ea6d279a697231658199e0a03cd87ef12048016bdcc84131601/pillow-11.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:040a5b691b0713e1f6cbe222e0f4f74cd233421e105850ae3b3c0ceda520f42e", size = 6708372, upload-time = "2025-07-01T09:13:52.145Z" }, + { url = "https://files.pythonhosted.org/packages/6c/e4/61b2e1a7528740efbc70b3d581f33937e38e98ef3d50b05007267a55bcb2/pillow-11.3.0-cp310-cp310-win32.whl", hash = "sha256:89bd777bc6624fe4115e9fac3352c79ed60f3bb18651420635f26e643e3dd1f6", size = 6277090, upload-time = "2025-07-01T09:13:53.915Z" }, + { url = "https://files.pythonhosted.org/packages/a9/d3/60c781c83a785d6afbd6a326ed4d759d141de43aa7365725cbcd65ce5e54/pillow-11.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:19d2ff547c75b8e3ff46f4d9ef969a06c30ab2d4263a9e287733aa8b2429ce8f", size = 6985988, upload-time = "2025-07-01T09:13:55.699Z" }, + { url = "https://files.pythonhosted.org/packages/9f/28/4f4a0203165eefb3763939c6789ba31013a2e90adffb456610f30f613850/pillow-11.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:819931d25e57b513242859ce1876c58c59dc31587847bf74cfe06b2e0cb22d2f", size = 2422899, upload-time = "2025-07-01T09:13:57.497Z" }, + { url = "https://files.pythonhosted.org/packages/db/26/77f8ed17ca4ffd60e1dcd220a6ec6d71210ba398cfa33a13a1cd614c5613/pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722", size = 5316531, upload-time = "2025-07-01T09:13:59.203Z" }, + { url = "https://files.pythonhosted.org/packages/cb/39/ee475903197ce709322a17a866892efb560f57900d9af2e55f86db51b0a5/pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288", size = 4686560, upload-time = "2025-07-01T09:14:01.101Z" }, + { url = "https://files.pythonhosted.org/packages/d5/90/442068a160fd179938ba55ec8c97050a612426fae5ec0a764e345839f76d/pillow-11.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1aa4de119a0ecac0a34a9c8bde33f34022e2e8f99104e47a3ca392fd60e37d", size = 5870978, upload-time = "2025-07-03T13:09:55.638Z" }, + { url = "https://files.pythonhosted.org/packages/13/92/dcdd147ab02daf405387f0218dcf792dc6dd5b14d2573d40b4caeef01059/pillow-11.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:91da1d88226663594e3f6b4b8c3c8d85bd504117d043740a8e0ec449087cc494", size = 7641168, upload-time = "2025-07-03T13:10:00.37Z" }, + { url = "https://files.pythonhosted.org/packages/6e/db/839d6ba7fd38b51af641aa904e2960e7a5644d60ec754c046b7d2aee00e5/pillow-11.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:643f189248837533073c405ec2f0bb250ba54598cf80e8c1e043381a60632f58", size = 5973053, upload-time = "2025-07-01T09:14:04.491Z" }, + { url = "https://files.pythonhosted.org/packages/f2/2f/d7675ecae6c43e9f12aa8d58b6012683b20b6edfbdac7abcb4e6af7a3784/pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f", size = 6640273, upload-time = "2025-07-01T09:14:06.235Z" }, + { url = "https://files.pythonhosted.org/packages/45/ad/931694675ede172e15b2ff03c8144a0ddaea1d87adb72bb07655eaffb654/pillow-11.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd8ff254faf15591e724dc7c4ddb6bf4793efcbe13802a4ae3e863cd300b493e", size = 6082043, upload-time = "2025-07-01T09:14:07.978Z" }, + { url = "https://files.pythonhosted.org/packages/3a/04/ba8f2b11fc80d2dd462d7abec16351b45ec99cbbaea4387648a44190351a/pillow-11.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:932c754c2d51ad2b2271fd01c3d121daaa35e27efae2a616f77bf164bc0b3e94", size = 6715516, upload-time = "2025-07-01T09:14:10.233Z" }, + { url = "https://files.pythonhosted.org/packages/48/59/8cd06d7f3944cc7d892e8533c56b0acb68399f640786313275faec1e3b6f/pillow-11.3.0-cp311-cp311-win32.whl", hash = "sha256:b4b8f3efc8d530a1544e5962bd6b403d5f7fe8b9e08227c6b255f98ad82b4ba0", size = 6274768, upload-time = "2025-07-01T09:14:11.921Z" }, + { url = "https://files.pythonhosted.org/packages/f1/cc/29c0f5d64ab8eae20f3232da8f8571660aa0ab4b8f1331da5c2f5f9a938e/pillow-11.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac", size = 6986055, upload-time = "2025-07-01T09:14:13.623Z" }, + { url = "https://files.pythonhosted.org/packages/c6/df/90bd886fabd544c25addd63e5ca6932c86f2b701d5da6c7839387a076b4a/pillow-11.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:30807c931ff7c095620fe04448e2c2fc673fcbb1ffe2a7da3fb39613489b1ddd", size = 2423079, upload-time = "2025-07-01T09:14:15.268Z" }, + { url = "https://files.pythonhosted.org/packages/40/fe/1bc9b3ee13f68487a99ac9529968035cca2f0a51ec36892060edcc51d06a/pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4", size = 5278800, upload-time = "2025-07-01T09:14:17.648Z" }, + { url = "https://files.pythonhosted.org/packages/2c/32/7e2ac19b5713657384cec55f89065fb306b06af008cfd87e572035b27119/pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69", size = 4686296, upload-time = "2025-07-01T09:14:19.828Z" }, + { url = "https://files.pythonhosted.org/packages/8e/1e/b9e12bbe6e4c2220effebc09ea0923a07a6da1e1f1bfbc8d7d29a01ce32b/pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d", size = 5871726, upload-time = "2025-07-03T13:10:04.448Z" }, + { url = "https://files.pythonhosted.org/packages/8d/33/e9200d2bd7ba00dc3ddb78df1198a6e80d7669cce6c2bdbeb2530a74ec58/pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6", size = 7644652, upload-time = "2025-07-03T13:10:10.391Z" }, + { url = "https://files.pythonhosted.org/packages/41/f1/6f2427a26fc683e00d985bc391bdd76d8dd4e92fac33d841127eb8fb2313/pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7", size = 5977787, upload-time = "2025-07-01T09:14:21.63Z" }, + { url = "https://files.pythonhosted.org/packages/e4/c9/06dd4a38974e24f932ff5f98ea3c546ce3f8c995d3f0985f8e5ba48bba19/pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024", size = 6645236, upload-time = "2025-07-01T09:14:23.321Z" }, + { url = "https://files.pythonhosted.org/packages/40/e7/848f69fb79843b3d91241bad658e9c14f39a32f71a301bcd1d139416d1be/pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809", size = 6086950, upload-time = "2025-07-01T09:14:25.237Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1a/7cff92e695a2a29ac1958c2a0fe4c0b2393b60aac13b04a4fe2735cad52d/pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d", size = 6723358, upload-time = "2025-07-01T09:14:27.053Z" }, + { url = "https://files.pythonhosted.org/packages/26/7d/73699ad77895f69edff76b0f332acc3d497f22f5d75e5360f78cbcaff248/pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149", size = 6275079, upload-time = "2025-07-01T09:14:30.104Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ce/e7dfc873bdd9828f3b6e5c2bbb74e47a98ec23cc5c74fc4e54462f0d9204/pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d", size = 6986324, upload-time = "2025-07-01T09:14:31.899Z" }, + { url = "https://files.pythonhosted.org/packages/16/8f/b13447d1bf0b1f7467ce7d86f6e6edf66c0ad7cf44cf5c87a37f9bed9936/pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542", size = 2423067, upload-time = "2025-07-01T09:14:33.709Z" }, + { url = "https://files.pythonhosted.org/packages/1e/93/0952f2ed8db3a5a4c7a11f91965d6184ebc8cd7cbb7941a260d5f018cd2d/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd", size = 2128328, upload-time = "2025-07-01T09:14:35.276Z" }, + { url = "https://files.pythonhosted.org/packages/4b/e8/100c3d114b1a0bf4042f27e0f87d2f25e857e838034e98ca98fe7b8c0a9c/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8", size = 2170652, upload-time = "2025-07-01T09:14:37.203Z" }, + { url = "https://files.pythonhosted.org/packages/aa/86/3f758a28a6e381758545f7cdb4942e1cb79abd271bea932998fc0db93cb6/pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f", size = 2227443, upload-time = "2025-07-01T09:14:39.344Z" }, + { url = "https://files.pythonhosted.org/packages/01/f4/91d5b3ffa718df2f53b0dc109877993e511f4fd055d7e9508682e8aba092/pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c", size = 5278474, upload-time = "2025-07-01T09:14:41.843Z" }, + { url = "https://files.pythonhosted.org/packages/f9/0e/37d7d3eca6c879fbd9dba21268427dffda1ab00d4eb05b32923d4fbe3b12/pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd", size = 4686038, upload-time = "2025-07-01T09:14:44.008Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b0/3426e5c7f6565e752d81221af9d3676fdbb4f352317ceafd42899aaf5d8a/pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e", size = 5864407, upload-time = "2025-07-03T13:10:15.628Z" }, + { url = "https://files.pythonhosted.org/packages/fc/c1/c6c423134229f2a221ee53f838d4be9d82bab86f7e2f8e75e47b6bf6cd77/pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1", size = 7639094, upload-time = "2025-07-03T13:10:21.857Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c9/09e6746630fe6372c67c648ff9deae52a2bc20897d51fa293571977ceb5d/pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805", size = 5973503, upload-time = "2025-07-01T09:14:45.698Z" }, + { url = "https://files.pythonhosted.org/packages/d5/1c/a2a29649c0b1983d3ef57ee87a66487fdeb45132df66ab30dd37f7dbe162/pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8", size = 6642574, upload-time = "2025-07-01T09:14:47.415Z" }, + { url = "https://files.pythonhosted.org/packages/36/de/d5cc31cc4b055b6c6fd990e3e7f0f8aaf36229a2698501bcb0cdf67c7146/pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2", size = 6084060, upload-time = "2025-07-01T09:14:49.636Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ea/502d938cbaeec836ac28a9b730193716f0114c41325db428e6b280513f09/pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b", size = 6721407, upload-time = "2025-07-01T09:14:51.962Z" }, + { url = "https://files.pythonhosted.org/packages/45/9c/9c5e2a73f125f6cbc59cc7087c8f2d649a7ae453f83bd0362ff7c9e2aee2/pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3", size = 6273841, upload-time = "2025-07-01T09:14:54.142Z" }, + { url = "https://files.pythonhosted.org/packages/23/85/397c73524e0cd212067e0c969aa245b01d50183439550d24d9f55781b776/pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51", size = 6978450, upload-time = "2025-07-01T09:14:56.436Z" }, + { url = "https://files.pythonhosted.org/packages/17/d2/622f4547f69cd173955194b78e4d19ca4935a1b0f03a302d655c9f6aae65/pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580", size = 2423055, upload-time = "2025-07-01T09:14:58.072Z" }, + { url = "https://files.pythonhosted.org/packages/dd/80/a8a2ac21dda2e82480852978416cfacd439a4b490a501a288ecf4fe2532d/pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e", size = 5281110, upload-time = "2025-07-01T09:14:59.79Z" }, + { url = "https://files.pythonhosted.org/packages/44/d6/b79754ca790f315918732e18f82a8146d33bcd7f4494380457ea89eb883d/pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d", size = 4689547, upload-time = "2025-07-01T09:15:01.648Z" }, + { url = "https://files.pythonhosted.org/packages/49/20/716b8717d331150cb00f7fdd78169c01e8e0c219732a78b0e59b6bdb2fd6/pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced", size = 5901554, upload-time = "2025-07-03T13:10:27.018Z" }, + { url = "https://files.pythonhosted.org/packages/74/cf/a9f3a2514a65bb071075063a96f0a5cf949c2f2fce683c15ccc83b1c1cab/pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c", size = 7669132, upload-time = "2025-07-03T13:10:33.01Z" }, + { url = "https://files.pythonhosted.org/packages/98/3c/da78805cbdbee9cb43efe8261dd7cc0b4b93f2ac79b676c03159e9db2187/pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8", size = 6005001, upload-time = "2025-07-01T09:15:03.365Z" }, + { url = "https://files.pythonhosted.org/packages/6c/fa/ce044b91faecf30e635321351bba32bab5a7e034c60187fe9698191aef4f/pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59", size = 6668814, upload-time = "2025-07-01T09:15:05.655Z" }, + { url = "https://files.pythonhosted.org/packages/7b/51/90f9291406d09bf93686434f9183aba27b831c10c87746ff49f127ee80cb/pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe", size = 6113124, upload-time = "2025-07-01T09:15:07.358Z" }, + { url = "https://files.pythonhosted.org/packages/cd/5a/6fec59b1dfb619234f7636d4157d11fb4e196caeee220232a8d2ec48488d/pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c", size = 6747186, upload-time = "2025-07-01T09:15:09.317Z" }, + { url = "https://files.pythonhosted.org/packages/49/6b/00187a044f98255225f172de653941e61da37104a9ea60e4f6887717e2b5/pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788", size = 6277546, upload-time = "2025-07-01T09:15:11.311Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5c/6caaba7e261c0d75bab23be79f1d06b5ad2a2ae49f028ccec801b0e853d6/pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31", size = 6985102, upload-time = "2025-07-01T09:15:13.164Z" }, + { url = "https://files.pythonhosted.org/packages/f3/7e/b623008460c09a0cb38263c93b828c666493caee2eb34ff67f778b87e58c/pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e", size = 2424803, upload-time = "2025-07-01T09:15:15.695Z" }, + { url = "https://files.pythonhosted.org/packages/73/f4/04905af42837292ed86cb1b1dabe03dce1edc008ef14c473c5c7e1443c5d/pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12", size = 5278520, upload-time = "2025-07-01T09:15:17.429Z" }, + { url = "https://files.pythonhosted.org/packages/41/b0/33d79e377a336247df6348a54e6d2a2b85d644ca202555e3faa0cf811ecc/pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a", size = 4686116, upload-time = "2025-07-01T09:15:19.423Z" }, + { url = "https://files.pythonhosted.org/packages/49/2d/ed8bc0ab219ae8768f529597d9509d184fe8a6c4741a6864fea334d25f3f/pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632", size = 5864597, upload-time = "2025-07-03T13:10:38.404Z" }, + { url = "https://files.pythonhosted.org/packages/b5/3d/b932bb4225c80b58dfadaca9d42d08d0b7064d2d1791b6a237f87f661834/pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673", size = 7638246, upload-time = "2025-07-03T13:10:44.987Z" }, + { url = "https://files.pythonhosted.org/packages/09/b5/0487044b7c096f1b48f0d7ad416472c02e0e4bf6919541b111efd3cae690/pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027", size = 5973336, upload-time = "2025-07-01T09:15:21.237Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2d/524f9318f6cbfcc79fbc004801ea6b607ec3f843977652fdee4857a7568b/pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77", size = 6642699, upload-time = "2025-07-01T09:15:23.186Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d2/a9a4f280c6aefedce1e8f615baaa5474e0701d86dd6f1dede66726462bbd/pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874", size = 6083789, upload-time = "2025-07-01T09:15:25.1Z" }, + { url = "https://files.pythonhosted.org/packages/fe/54/86b0cd9dbb683a9d5e960b66c7379e821a19be4ac5810e2e5a715c09a0c0/pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a", size = 6720386, upload-time = "2025-07-01T09:15:27.378Z" }, + { url = "https://files.pythonhosted.org/packages/e7/95/88efcaf384c3588e24259c4203b909cbe3e3c2d887af9e938c2022c9dd48/pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214", size = 6370911, upload-time = "2025-07-01T09:15:29.294Z" }, + { url = "https://files.pythonhosted.org/packages/2e/cc/934e5820850ec5eb107e7b1a72dd278140731c669f396110ebc326f2a503/pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635", size = 7117383, upload-time = "2025-07-01T09:15:31.128Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e9/9c0a616a71da2a5d163aa37405e8aced9a906d574b4a214bede134e731bc/pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6", size = 2511385, upload-time = "2025-07-01T09:15:33.328Z" }, + { url = "https://files.pythonhosted.org/packages/1a/33/c88376898aff369658b225262cd4f2659b13e8178e7534df9e6e1fa289f6/pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae", size = 5281129, upload-time = "2025-07-01T09:15:35.194Z" }, + { url = "https://files.pythonhosted.org/packages/1f/70/d376247fb36f1844b42910911c83a02d5544ebd2a8bad9efcc0f707ea774/pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653", size = 4689580, upload-time = "2025-07-01T09:15:37.114Z" }, + { url = "https://files.pythonhosted.org/packages/eb/1c/537e930496149fbac69efd2fc4329035bbe2e5475b4165439e3be9cb183b/pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6", size = 5902860, upload-time = "2025-07-03T13:10:50.248Z" }, + { url = "https://files.pythonhosted.org/packages/bd/57/80f53264954dcefeebcf9dae6e3eb1daea1b488f0be8b8fef12f79a3eb10/pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36", size = 7670694, upload-time = "2025-07-03T13:10:56.432Z" }, + { url = "https://files.pythonhosted.org/packages/70/ff/4727d3b71a8578b4587d9c276e90efad2d6fe0335fd76742a6da08132e8c/pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b", size = 6005888, upload-time = "2025-07-01T09:15:39.436Z" }, + { url = "https://files.pythonhosted.org/packages/05/ae/716592277934f85d3be51d7256f3636672d7b1abfafdc42cf3f8cbd4b4c8/pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477", size = 6670330, upload-time = "2025-07-01T09:15:41.269Z" }, + { url = "https://files.pythonhosted.org/packages/e7/bb/7fe6cddcc8827b01b1a9766f5fdeb7418680744f9082035bdbabecf1d57f/pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50", size = 6114089, upload-time = "2025-07-01T09:15:43.13Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f5/06bfaa444c8e80f1a8e4bff98da9c83b37b5be3b1deaa43d27a0db37ef84/pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b", size = 6748206, upload-time = "2025-07-01T09:15:44.937Z" }, + { url = "https://files.pythonhosted.org/packages/f0/77/bc6f92a3e8e6e46c0ca78abfffec0037845800ea38c73483760362804c41/pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12", size = 6377370, upload-time = "2025-07-01T09:15:46.673Z" }, + { url = "https://files.pythonhosted.org/packages/4a/82/3a721f7d69dca802befb8af08b7c79ebcab461007ce1c18bd91a5d5896f9/pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db", size = 7121500, upload-time = "2025-07-01T09:15:48.512Z" }, + { url = "https://files.pythonhosted.org/packages/89/c7/5572fa4a3f45740eaab6ae86fcdf7195b55beac1371ac8c619d880cfe948/pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa", size = 2512835, upload-time = "2025-07-01T09:15:50.399Z" }, + { url = "https://files.pythonhosted.org/packages/6f/8b/209bd6b62ce8367f47e68a218bffac88888fdf2c9fcf1ecadc6c3ec1ebc7/pillow-11.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3cee80663f29e3843b68199b9d6f4f54bd1d4a6b59bdd91bceefc51238bcb967", size = 5270556, upload-time = "2025-07-01T09:16:09.961Z" }, + { url = "https://files.pythonhosted.org/packages/2e/e6/231a0b76070c2cfd9e260a7a5b504fb72da0a95279410fa7afd99d9751d6/pillow-11.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b5f56c3f344f2ccaf0dd875d3e180f631dc60a51b314295a3e681fe8cf851fbe", size = 4654625, upload-time = "2025-07-01T09:16:11.913Z" }, + { url = "https://files.pythonhosted.org/packages/13/f4/10cf94fda33cb12765f2397fc285fa6d8eb9c29de7f3185165b702fc7386/pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e67d793d180c9df62f1f40aee3accca4829d3794c95098887edc18af4b8b780c", size = 4874207, upload-time = "2025-07-03T13:11:10.201Z" }, + { url = "https://files.pythonhosted.org/packages/72/c9/583821097dc691880c92892e8e2d41fe0a5a3d6021f4963371d2f6d57250/pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d000f46e2917c705e9fb93a3606ee4a819d1e3aa7a9b442f6444f07e77cf5e25", size = 6583939, upload-time = "2025-07-03T13:11:15.68Z" }, + { url = "https://files.pythonhosted.org/packages/3b/8e/5c9d410f9217b12320efc7c413e72693f48468979a013ad17fd690397b9a/pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:527b37216b6ac3a12d7838dc3bd75208ec57c1c6d11ef01902266a5a0c14fc27", size = 4957166, upload-time = "2025-07-01T09:16:13.74Z" }, + { url = "https://files.pythonhosted.org/packages/62/bb/78347dbe13219991877ffb3a91bf09da8317fbfcd4b5f9140aeae020ad71/pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be5463ac478b623b9dd3937afd7fb7ab3d79dd290a28e2b6df292dc75063eb8a", size = 5581482, upload-time = "2025-07-01T09:16:16.107Z" }, + { url = "https://files.pythonhosted.org/packages/d9/28/1000353d5e61498aaeaaf7f1e4b49ddb05f2c6575f9d4f9f914a3538b6e1/pillow-11.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8dc70ca24c110503e16918a658b869019126ecfe03109b754c402daff12b3d9f", size = 6984596, upload-time = "2025-07-01T09:16:18.07Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e3/6fa84033758276fb31da12e5fb66ad747ae83b93c67af17f8c6ff4cc8f34/pillow-11.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c8ec7a017ad1bd562f93dbd8505763e688d388cde6e4a010ae1486916e713e6", size = 5270566, upload-time = "2025-07-01T09:16:19.801Z" }, + { url = "https://files.pythonhosted.org/packages/5b/ee/e8d2e1ab4892970b561e1ba96cbd59c0d28cf66737fc44abb2aec3795a4e/pillow-11.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9ab6ae226de48019caa8074894544af5b53a117ccb9d3b3dcb2871464c829438", size = 4654618, upload-time = "2025-07-01T09:16:21.818Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6d/17f80f4e1f0761f02160fc433abd4109fa1548dcfdca46cfdadaf9efa565/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe27fb049cdcca11f11a7bfda64043c37b30e6b91f10cb5bab275806c32f6ab3", size = 4874248, upload-time = "2025-07-03T13:11:20.738Z" }, + { url = "https://files.pythonhosted.org/packages/de/5f/c22340acd61cef960130585bbe2120e2fd8434c214802f07e8c03596b17e/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:465b9e8844e3c3519a983d58b80be3f668e2a7a5db97f2784e7079fbc9f9822c", size = 6583963, upload-time = "2025-07-03T13:11:26.283Z" }, + { url = "https://files.pythonhosted.org/packages/31/5e/03966aedfbfcbb4d5f8aa042452d3361f325b963ebbadddac05b122e47dd/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5418b53c0d59b3824d05e029669efa023bbef0f3e92e75ec8428f3799487f361", size = 4957170, upload-time = "2025-07-01T09:16:23.762Z" }, + { url = "https://files.pythonhosted.org/packages/cc/2d/e082982aacc927fc2cab48e1e731bdb1643a1406acace8bed0900a61464e/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7", size = 5581505, upload-time = "2025-07-01T09:16:25.593Z" }, + { url = "https://files.pythonhosted.org/packages/34/e7/ae39f538fd6844e982063c3a5e4598b8ced43b9633baa3a85ef33af8c05c/pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8", size = 6984598, upload-time = "2025-07-01T09:16:27.732Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pyproject-hooks" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/82/28175b2414effca1cdac8dc99f76d660e7a4fb0ceefa4b4ab8f5f6742925/pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", size = 19228, upload-time = "2024-09-29T09:24:13.293Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913", size = 10216, upload-time = "2024-09-29T09:24:11.978Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, +] + +[[package]] +name = "pytest-cov" +version = "6.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", extra = ["toml"] }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/99/668cade231f434aaa59bbfbf49469068d2ddd945000621d3d165d2e7dd7b/pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2", size = 69432, upload-time = "2025-06-12T10:47:47.684Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/16/4ea354101abb1287856baa4af2732be351c7bee728065aed451b678153fd/pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5", size = 24644, upload-time = "2025-06-12T10:47:45.932Z" }, +] + +[[package]] +name = "ruff" +version = "0.12.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/81/0bd3594fa0f690466e41bd033bdcdf86cba8288345ac77ad4afbe5ec743a/ruff-0.12.7.tar.gz", hash = "sha256:1fc3193f238bc2d7968772c82831a4ff69252f673be371fb49663f0068b7ec71", size = 5197814, upload-time = "2025-07-29T22:32:35.877Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/d2/6cb35e9c85e7a91e8d22ab32ae07ac39cc34a71f1009a6f9e4a2a019e602/ruff-0.12.7-py3-none-linux_armv6l.whl", hash = "sha256:76e4f31529899b8c434c3c1dede98c4483b89590e15fb49f2d46183801565303", size = 11852189, upload-time = "2025-07-29T22:31:41.281Z" }, + { url = "https://files.pythonhosted.org/packages/63/5b/a4136b9921aa84638f1a6be7fb086f8cad0fde538ba76bda3682f2599a2f/ruff-0.12.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:789b7a03e72507c54fb3ba6209e4bb36517b90f1a3569ea17084e3fd295500fb", size = 12519389, upload-time = "2025-07-29T22:31:54.265Z" }, + { url = "https://files.pythonhosted.org/packages/a8/c9/3e24a8472484269b6b1821794141f879c54645a111ded4b6f58f9ab0705f/ruff-0.12.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2e1c2a3b8626339bb6369116e7030a4cf194ea48f49b64bb505732a7fce4f4e3", size = 11743384, upload-time = "2025-07-29T22:31:59.575Z" }, + { url = "https://files.pythonhosted.org/packages/26/7c/458dd25deeb3452c43eaee853c0b17a1e84169f8021a26d500ead77964fd/ruff-0.12.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32dec41817623d388e645612ec70d5757a6d9c035f3744a52c7b195a57e03860", size = 11943759, upload-time = "2025-07-29T22:32:01.95Z" }, + { url = "https://files.pythonhosted.org/packages/7f/8b/658798472ef260ca050e400ab96ef7e85c366c39cf3dfbef4d0a46a528b6/ruff-0.12.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47ef751f722053a5df5fa48d412dbb54d41ab9b17875c6840a58ec63ff0c247c", size = 11654028, upload-time = "2025-07-29T22:32:04.367Z" }, + { url = "https://files.pythonhosted.org/packages/a8/86/9c2336f13b2a3326d06d39178fd3448dcc7025f82514d1b15816fe42bfe8/ruff-0.12.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a828a5fc25a3efd3e1ff7b241fd392686c9386f20e5ac90aa9234a5faa12c423", size = 13225209, upload-time = "2025-07-29T22:32:06.952Z" }, + { url = "https://files.pythonhosted.org/packages/76/69/df73f65f53d6c463b19b6b312fd2391dc36425d926ec237a7ed028a90fc1/ruff-0.12.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5726f59b171111fa6a69d82aef48f00b56598b03a22f0f4170664ff4d8298efb", size = 14182353, upload-time = "2025-07-29T22:32:10.053Z" }, + { url = "https://files.pythonhosted.org/packages/58/1e/de6cda406d99fea84b66811c189b5ea139814b98125b052424b55d28a41c/ruff-0.12.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74e6f5c04c4dd4aba223f4fe6e7104f79e0eebf7d307e4f9b18c18362124bccd", size = 13631555, upload-time = "2025-07-29T22:32:12.644Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ae/625d46d5164a6cc9261945a5e89df24457dc8262539ace3ac36c40f0b51e/ruff-0.12.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d0bfe4e77fba61bf2ccadf8cf005d6133e3ce08793bbe870dd1c734f2699a3e", size = 12667556, upload-time = "2025-07-29T22:32:15.312Z" }, + { url = "https://files.pythonhosted.org/packages/55/bf/9cb1ea5e3066779e42ade8d0cd3d3b0582a5720a814ae1586f85014656b6/ruff-0.12.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06bfb01e1623bf7f59ea749a841da56f8f653d641bfd046edee32ede7ff6c606", size = 12939784, upload-time = "2025-07-29T22:32:17.69Z" }, + { url = "https://files.pythonhosted.org/packages/55/7f/7ead2663be5627c04be83754c4f3096603bf5e99ed856c7cd29618c691bd/ruff-0.12.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e41df94a957d50083fd09b916d6e89e497246698c3f3d5c681c8b3e7b9bb4ac8", size = 11771356, upload-time = "2025-07-29T22:32:20.134Z" }, + { url = "https://files.pythonhosted.org/packages/17/40/a95352ea16edf78cd3a938085dccc55df692a4d8ba1b3af7accbe2c806b0/ruff-0.12.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:4000623300563c709458d0ce170c3d0d788c23a058912f28bbadc6f905d67afa", size = 11612124, upload-time = "2025-07-29T22:32:22.645Z" }, + { url = "https://files.pythonhosted.org/packages/4d/74/633b04871c669e23b8917877e812376827c06df866e1677f15abfadc95cb/ruff-0.12.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:69ffe0e5f9b2cf2b8e289a3f8945b402a1b19eff24ec389f45f23c42a3dd6fb5", size = 12479945, upload-time = "2025-07-29T22:32:24.765Z" }, + { url = "https://files.pythonhosted.org/packages/be/34/c3ef2d7799c9778b835a76189c6f53c179d3bdebc8c65288c29032e03613/ruff-0.12.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a07a5c8ffa2611a52732bdc67bf88e243abd84fe2d7f6daef3826b59abbfeda4", size = 12998677, upload-time = "2025-07-29T22:32:27.022Z" }, + { url = "https://files.pythonhosted.org/packages/77/ab/aca2e756ad7b09b3d662a41773f3edcbd262872a4fc81f920dc1ffa44541/ruff-0.12.7-py3-none-win32.whl", hash = "sha256:c928f1b2ec59fb77dfdf70e0419408898b63998789cc98197e15f560b9e77f77", size = 11756687, upload-time = "2025-07-29T22:32:29.381Z" }, + { url = "https://files.pythonhosted.org/packages/b4/71/26d45a5042bc71db22ddd8252ca9d01e9ca454f230e2996bb04f16d72799/ruff-0.12.7-py3-none-win_amd64.whl", hash = "sha256:9c18f3d707ee9edf89da76131956aba1270c6348bfee8f6c647de841eac7194f", size = 12912365, upload-time = "2025-07-29T22:32:31.517Z" }, + { url = "https://files.pythonhosted.org/packages/4c/9b/0b8aa09817b63e78d94b4977f18b1fcaead3165a5ee49251c5d5c245bb2d/ruff-0.12.7-py3-none-win_arm64.whl", hash = "sha256:dfce05101dbd11833a0776716d5d1578641b7fddb537fe7fa956ab85d1769b69", size = 11982083, upload-time = "2025-07-29T22:32:33.881Z" }, +] + +[[package]] +name = "tomli" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, + { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, + { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, + { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, + { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, + { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, + { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" }, + { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" }, + { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" }, + { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" }, + { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" }, + { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" }, + { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" }, + { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" }, + { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" }, + { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" }, + { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" }, + { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" }, + { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" }, + { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" }, + { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" }, + { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" }, + { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" }, + { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" }, + { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" }, + { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, +] + +[[package]] +name = "zipp" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/d8/eab98a517c14134c0b2eb4e2387bc5f457334293ec5d2dd3857ec2966802/zipp-4.1.0.tar.gz", hash = "sha256:4cb57381f544315db7688e976e922a2b18cdb513d21cc194eb42232ba2a3e602", size = 26214, upload-time = "2026-05-18T20:08:57.967Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/13/547360d81e6d88d58492968ffda9f9542854f11310ee556fef14260cc886/zipp-4.1.0-py3-none-any.whl", hash = "sha256:25ad4e16390cd314347dd8f1de67a2ac538ae658ed4ab9db16029c07c188e97f", size = 10238, upload-time = "2026-05-18T20:08:57.045Z" }, +] From 1010a4e8348820638c3c970167e1ac42cc721fa3 Mon Sep 17 00:00:00 2001 From: Ali Date: Sat, 1 Aug 2026 17:47:52 -0400 Subject: [PATCH 2/2] Record release validation evidence --- RUN_LOG.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/RUN_LOG.md b/RUN_LOG.md index 1024ccc..b75de24 100644 --- a/RUN_LOG.md +++ b/RUN_LOG.md @@ -65,7 +65,7 @@ These are single-environment observations, not cross-machine benchmark claims. | reports | terminal, JSON, HTML regeneration tests | passed; HTML contains no script or CDN reference | | demo | `python scripts/generate_demo.py` | GIF, static PNG, sanitized real-command transcript created | | documentation links | `python scripts/check_links.py` | 15 Markdown files, 22 external links inventoried, no missing local links | -| privacy/secret/large file | `python scripts/hygiene.py` | 77 public files scanned before Git, no issues, no file over 1 MB | +| privacy/secret/large file | `python scripts/hygiene.py` | 77 tracked public files scanned, no issues, no file over 1 MB | | package contents | wheel/SDist archive inspection | wheel 32 files, source archive 76 files, no PDF/private research/tool/environment path | | runtime dependencies | wheel metadata plus clean uv environment | no runtime `Requires-Dist`; only PhaseProbe installed before test tooling | | packed install | wheel into isolated project-local environment | version, positive scan, strict replay, test generation, and generated pytest passed | @@ -86,10 +86,19 @@ These are single-environment observations, not cross-machine benchmark claims. 6. The first packed-smoke script used an unavailable old-PowerShell parameter. Replaced it with an explicit artifact count and compatible selection. 7. A uv-created smoke environment intentionally had no `pip` module, so `python -m pip freeze` was not a valid dependency query. Repeated the gate with `uv pip freeze --python`; wheel metadata independently confirmed zero runtime dependencies. 8. The first committed generated test failed Ruff import-block normalization. Updated the generator template, regenerated the test, and restarted the full final gate. +9. The first staged `git diff --check` found extra blank lines at EOF and CRLF-sensitive demo/fixture lines. Normalized the reported text endings, added repository-level LF attributes, and reran the full local gates; the final staged diff check passed. + +## Remote publication evidence + +- Verified GitHub authentication as the required `aliengineering-byte` account before initializing Git. +- Created the public `aliengineering-byte/phaseprobe` repository with `main` as its default branch, then pushed `agent/phaseprobe-v0.1.0`. +- Opened draft pull request #1 from the release branch to `main`. +- GitHub Actions run `30719800789` passed all seven jobs: package-and-hygiene plus Windows and Ubuntu on Python 3.10, 3.12, and 3.14. +- Tag creation, release publication, and the unauthenticated public-clone verification necessarily occur after this release commit is frozen; their evidence is reported in the final release handoff rather than retroactively changing the tagged source. ## Skipped or bounded checks - External documentation links are inventoried in CI but not fetched there to avoid network-flaky builds; the prior-art/name audit used live official/primary sources during this release run. - No formal trademark or legal clearance was performed. - No PyPI or npm publication was attempted or authorized. -- GitHub/CI/release/fresh-public-clone evidence is appended only after those remote actions complete. +- The tagged release remains immutable; post-tag release and fresh-public-clone evidence is therefore external to this tracked pre-release log.