phaseprobe 0.2.0__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/.github/workflows/ci.yml +24 -10
  2. phaseprobe-0.3.0/.github/workflows/publish-pypi.yml +166 -0
  3. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/.github/workflows/release.yml +4 -7
  4. phaseprobe-0.3.0/CHANGELOG.md +63 -0
  5. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/CITATION.cff +1 -2
  6. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/PKG-INFO +62 -13
  7. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/README.md +60 -11
  8. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/SCIENTIFIC_METHODS.md +3 -0
  9. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/assets/scipy-demo-session.txt +10 -10
  10. phaseprobe-0.3.0/assets/scipy-demo-static.png +0 -0
  11. phaseprobe-0.3.0/assets/scipy-demo.gif +0 -0
  12. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/docs/upstream/SCIPY_INTEGRATION_PROPOSAL.md +47 -11
  13. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/examples/README.md +10 -5
  14. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/pyproject.toml +1 -1
  15. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/scripts/audit_package.py +36 -4
  16. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/scripts/generate_scipy_demo.py +8 -8
  17. phaseprobe-0.3.0/scripts/installed_smoke.py +199 -0
  18. phaseprobe-0.3.0/scripts/verify_artifacts.py +221 -0
  19. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/__init__.py +1 -1
  20. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/cli.py +30 -3
  21. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/config.py +69 -18
  22. phaseprobe-0.3.0/src/phaseprobe/data/examples/scipy-lorenz-negative.json +54 -0
  23. phaseprobe-0.3.0/src/phaseprobe/data/examples/scipy-lorenz.json +54 -0
  24. phaseprobe-0.3.0/src/phaseprobe/data/examples/scipy-predator-prey-coarse.json +45 -0
  25. phaseprobe-0.3.0/src/phaseprobe/data/examples/scipy-predator-prey.json +45 -0
  26. phaseprobe-0.3.0/src/phaseprobe/generate.py +377 -0
  27. phaseprobe-0.3.0/src/phaseprobe/py.typed +1 -0
  28. phaseprobe-0.3.0/tests/test_artifacts_replay.py +229 -0
  29. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/tests/test_cli.py +27 -0
  30. phaseprobe-0.3.0/tests/test_config.py +148 -0
  31. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/tests/test_optional_dependency.py +7 -1
  32. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/tests/test_scipy_adapter.py +52 -6
  33. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/uv.lock +1 -1
  34. phaseprobe-0.2.0/CHANGELOG.md +0 -30
  35. phaseprobe-0.2.0/assets/scipy-demo-static.png +0 -0
  36. phaseprobe-0.2.0/assets/scipy-demo.gif +0 -0
  37. phaseprobe-0.2.0/src/phaseprobe/generate.py +0 -72
  38. phaseprobe-0.2.0/tests/test_artifacts_replay.py +0 -86
  39. phaseprobe-0.2.0/tests/test_config.py +0 -69
  40. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  41. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  42. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  43. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/.github/ISSUE_TEMPLATE/good_first_accessibility.yml +0 -0
  44. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/.github/ISSUE_TEMPLATE/good_first_pendulum.yml +0 -0
  45. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/.github/ISSUE_TEMPLATE/good_first_schema.yml +0 -0
  46. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  47. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/.gitignore +0 -0
  48. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/AGENTS.md +0 -0
  49. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/ARCHITECTURE.md +0 -0
  50. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/CODE_OF_CONDUCT.md +0 -0
  51. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/CONTRIBUTING.md +0 -0
  52. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/LICENSE +0 -0
  53. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/LIMITATIONS.md +0 -0
  54. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/PRIOR_ART.md +0 -0
  55. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/ROADMAP.md +0 -0
  56. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/SECURITY.md +0 -0
  57. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/assets/demo-session.txt +0 -0
  58. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/assets/demo-static.png +0 -0
  59. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/assets/demo.gif +0 -0
  60. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/assets/social-preview.jpg +0 -0
  61. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/docs/SCIPY_SOLVE_IVP_AUDIT.md +0 -0
  62. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/examples/configs/logistic-negative.json +0 -0
  63. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/examples/configs/logistic-scan.json +0 -0
  64. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/examples/configs/lorenz-negative.json +0 -0
  65. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/examples/configs/lorenz-perturb.json +0 -0
  66. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/examples/configs/predator-prey-check.json +0 -0
  67. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/examples/configs/predator-prey-negative.json +0 -0
  68. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/examples/configs/toggle-negative.json +0 -0
  69. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/examples/configs/toggle-perturb.json +0 -0
  70. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/examples/scipy/lorenz-negative.json +0 -0
  71. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/examples/scipy/lorenz.json +0 -0
  72. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/examples/scipy/predator-prey-coarse.json +0 -0
  73. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/examples/scipy/predator-prey.json +0 -0
  74. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/examples/scipy/report.html +0 -0
  75. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/scripts/check_links.py +0 -0
  76. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/scripts/generate_demo.py +0 -0
  77. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/scripts/hygiene.py +0 -0
  78. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/__main__.py +0 -0
  79. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/adapters/__init__.py +0 -0
  80. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/adapters/loader.py +0 -0
  81. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/adapters/scipy.py +0 -0
  82. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/api.py +0 -0
  83. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/artifacts.py +0 -0
  84. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/data/__init__.py +0 -0
  85. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/data/examples/__init__.py +0 -0
  86. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/data/examples/logistic-negative.json +0 -0
  87. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/data/examples/logistic-scan.json +0 -0
  88. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/data/examples/lorenz-negative.json +0 -0
  89. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/data/examples/lorenz-perturb.json +0 -0
  90. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/data/examples/predator-prey-check.json +0 -0
  91. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/data/examples/predator-prey-negative.json +0 -0
  92. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/data/examples/toggle-negative.json +0 -0
  93. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/data/examples/toggle-perturb.json +0 -0
  94. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/engine.py +0 -0
  95. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/errors.py +0 -0
  96. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/examples/__init__.py +0 -0
  97. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/examples/scipy_models.py +0 -0
  98. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/models/__init__.py +0 -0
  99. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/models/_common.py +0 -0
  100. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/models/logistic.py +0 -0
  101. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/models/lorenz.py +0 -0
  102. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/models/predator_prey.py +0 -0
  103. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/models/toggle.py +0 -0
  104. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/replay.py +0 -0
  105. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/reporting.py +0 -0
  106. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/src/phaseprobe/types.py +0 -0
  107. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/tests/conftest.py +0 -0
  108. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/tests/generated/fixtures/logistic_map-replay.json +0 -0
  109. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/tests/generated/fixtures/lorenz_scipy-replay.json +0 -0
  110. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/tests/generated/fixtures/predator_prey_scipy-replay.json +0 -0
  111. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/tests/generated/test_logistic_map_transition.py +0 -0
  112. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/tests/generated/test_lorenz_scipy_transition.py +0 -0
  113. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/tests/generated/test_predator_prey_scipy_transition.py +0 -0
  114. {phaseprobe-0.2.0 → phaseprobe-0.3.0}/tests/test_engine.py +0 -0
@@ -50,8 +50,8 @@ jobs:
50
50
  - run: python -m ruff check .
51
51
  - run: python -m mypy src tests
52
52
  - run: python -m pytest --cov=phaseprobe --cov-report=term
53
- - run: python -m phaseprobe perturb --config examples/scipy/lorenz-negative.json
54
- - run: python -m phaseprobe check --config examples/scipy/predator-prey.json
53
+ - run: python -m phaseprobe perturb --example scipy-lorenz-negative
54
+ - run: python -m phaseprobe check --example scipy-predator-prey
55
55
 
56
56
  package-and-hygiene:
57
57
  runs-on: ubuntu-latest
@@ -65,14 +65,10 @@ jobs:
65
65
  - run: python -m build
66
66
  - run: python -m twine check dist/*
67
67
  - run: python scripts/audit_package.py
68
- - run: python -m venv .cache/packed-smoke
69
- - run: .cache/packed-smoke/bin/python -m pip install dist/*.whl
70
- - run: .cache/packed-smoke/bin/python -m phaseprobe --version
71
- - run: .cache/packed-smoke/bin/python -m phaseprobe scan --example logistic-negative
72
- - run: .cache/packed-smoke/bin/python -c "import importlib.util; assert importlib.util.find_spec('numpy') is None; assert importlib.util.find_spec('scipy') is None"
73
- - run: python -m venv .cache/scipy-smoke
74
- - run: .cache/scipy-smoke/bin/python -m pip install "$(find dist -name '*.whl')[scipy]"
75
- - run: .cache/scipy-smoke/bin/python -m phaseprobe check --config examples/scipy/predator-prey.json
68
+ - run: >-
69
+ python scripts/verify_artifacts.py
70
+ --dist-dir dist
71
+ --work-root "${RUNNER_TEMP}/phaseprobe artifact gate"
76
72
  - run: python scripts/check_links.py
77
73
  - run: python scripts/hygiene.py
78
74
  - uses: actions/upload-artifact@v4
@@ -80,3 +76,21 @@ jobs:
80
76
  name: phaseprobe-packages
81
77
  path: dist/*
82
78
  if-no-files-found: error
79
+
80
+ windows-artifact-install:
81
+ name: artifact install / windows / Python 3.12
82
+ runs-on: windows-latest
83
+ steps:
84
+ - uses: actions/checkout@v4
85
+ - uses: actions/setup-python@v5
86
+ with:
87
+ python-version: "3.12"
88
+ cache: pip
89
+ - run: python -m pip install --upgrade pip build twine
90
+ - run: python -m build
91
+ - run: python -m twine check dist/*
92
+ - run: python scripts/audit_package.py
93
+ - run: >-
94
+ python scripts/verify_artifacts.py
95
+ --dist-dir dist
96
+ --work-root "$env:RUNNER_TEMP\phaseprobe artifact gate"
@@ -0,0 +1,166 @@
1
+ name: Publish an existing GitHub release to PyPI
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ tag:
7
+ description: Immutable release tag (for example, v0.2.1)
8
+ required: true
9
+ type: string
10
+ version:
11
+ description: Expected package version (for example, 0.2.1)
12
+ required: true
13
+ type: string
14
+ commit:
15
+ description: Expected 40-character commit for the tag
16
+ required: true
17
+ type: string
18
+ wheel_sha256:
19
+ description: Expected SHA-256 for the wheel release asset
20
+ required: true
21
+ type: string
22
+ sdist_sha256:
23
+ description: Expected SHA-256 for the source release asset
24
+ required: true
25
+ type: string
26
+
27
+ permissions: {}
28
+
29
+ concurrency:
30
+ group: pypi-${{ inputs.tag }}
31
+ cancel-in-progress: false
32
+
33
+ jobs:
34
+ verify:
35
+ name: Verify immutable release assets
36
+ if: >-
37
+ github.repository == 'aliengineering-byte/phaseprobe' &&
38
+ github.ref == 'refs/heads/main'
39
+ runs-on: ubuntu-latest
40
+ permissions: {}
41
+ steps:
42
+ - name: Download and verify the selected release
43
+ env:
44
+ TAG: ${{ inputs.tag }}
45
+ VERSION: ${{ inputs.version }}
46
+ EXPECTED_COMMIT: ${{ inputs.commit }}
47
+ WHEEL_SHA256: ${{ inputs.wheel_sha256 }}
48
+ SDIST_SHA256: ${{ inputs.sdist_sha256 }}
49
+ shell: bash
50
+ run: |
51
+ set -euo pipefail
52
+
53
+ [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]
54
+ [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]
55
+ [[ "$TAG" == "v$VERSION" ]]
56
+ [[ "$EXPECTED_COMMIT" =~ ^[0-9a-f]{40}$ ]]
57
+ [[ "$WHEEL_SHA256" =~ ^[0-9a-f]{64}$ ]]
58
+ [[ "$SDIST_SHA256" =~ ^[0-9a-f]{64}$ ]]
59
+
60
+ repository_url="https://github.com/${GITHUB_REPOSITORY}.git"
61
+ tag_refs="$(git ls-remote "$repository_url" "refs/tags/$TAG" "refs/tags/$TAG^{}")"
62
+ direct_commit="$(awk -v ref="refs/tags/$TAG" '$2 == ref { print $1 }' <<<"$tag_refs")"
63
+ peeled_commit="$(awk -v ref="refs/tags/$TAG^{}" '$2 == ref { print $1 }' <<<"$tag_refs")"
64
+ resolved_commit="${peeled_commit:-$direct_commit}"
65
+ [[ -n "$resolved_commit" ]]
66
+ [[ "$resolved_commit" == "$EXPECTED_COMMIT" ]]
67
+
68
+ wheel="phaseprobe-$VERSION-py3-none-any.whl"
69
+ sdist="phaseprobe-$VERSION.tar.gz"
70
+ release_url="https://github.com/${GITHUB_REPOSITORY}/releases/download/$TAG"
71
+ mkdir dist
72
+ curl --fail --location --proto '=https' --tlsv1.2 \
73
+ --output "dist/$wheel" "$release_url/$wheel"
74
+ curl --fail --location --proto '=https' --tlsv1.2 \
75
+ --output "dist/$sdist" "$release_url/$sdist"
76
+ printf '%s %s\n%s %s\n' \
77
+ "$WHEEL_SHA256" "dist/$wheel" \
78
+ "$SDIST_SHA256" "dist/$sdist" | sha256sum --check --strict
79
+
80
+ python3 -m venv .twine-check
81
+ .twine-check/bin/python -m pip install --disable-pip-version-check 'twine==7.0.0'
82
+ .twine-check/bin/python -m twine check "dist/$wheel" "dist/$sdist"
83
+
84
+ python3 - "$VERSION" "dist/$wheel" "dist/$sdist" <<'PY'
85
+ import email
86
+ import sys
87
+ import tarfile
88
+ import zipfile
89
+
90
+ expected_version, wheel_path, sdist_path = sys.argv[1:]
91
+
92
+ with zipfile.ZipFile(wheel_path) as archive:
93
+ wheel_names = set(archive.namelist())
94
+ metadata_names = [
95
+ name for name in wheel_names if name.endswith(".dist-info/METADATA")
96
+ ]
97
+ assert len(metadata_names) == 1, metadata_names
98
+ wheel_metadata = email.message_from_bytes(archive.read(metadata_names[0]))
99
+
100
+ with tarfile.open(sdist_path, "r:gz") as archive:
101
+ sdist_names = {member.name for member in archive.getmembers()}
102
+ metadata_members = [
103
+ member
104
+ for member in archive.getmembers()
105
+ if member.isfile() and member.name.count("/") == 1 and member.name.endswith("/PKG-INFO")
106
+ ]
107
+ assert len(metadata_members) == 1, [member.name for member in metadata_members]
108
+ extracted = archive.extractfile(metadata_members[0])
109
+ assert extracted is not None
110
+ sdist_metadata = email.message_from_bytes(extracted.read())
111
+
112
+ sdist_roots = {name.split("/", 1)[0] for name in sdist_names if "/" in name}
113
+ assert len(sdist_roots) == 1, sorted(sdist_roots)
114
+ sdist_root = next(iter(sdist_roots))
115
+ source_prefix = f"{sdist_root}/src/phaseprobe/"
116
+ runtime_files = {
117
+ name.removeprefix(f"{sdist_root}/src/")
118
+ for name in sdist_names
119
+ if name.startswith(source_prefix)
120
+ and not name.endswith(".py")
121
+ and "__pycache__" not in name.split("/")
122
+ }
123
+ assert "phaseprobe/py.typed" in runtime_files
124
+ assert not runtime_files - wheel_names, sorted(runtime_files - wheel_names)
125
+
126
+ for metadata in (wheel_metadata, sdist_metadata):
127
+ assert metadata["Name"] == "phaseprobe"
128
+ assert metadata["Version"] == expected_version
129
+ assert metadata["Requires-Python"] == ">=3.10"
130
+ assert metadata["License-Expression"] == "Apache-2.0"
131
+ assert "scipy" in metadata.get_all("Provides-Extra", [])
132
+ requirements = metadata.get_all("Requires-Dist", [])
133
+ assert any(req.startswith("numpy") and "extra == 'scipy'" in req for req in requirements)
134
+ assert any(req.startswith("scipy") and "extra == 'scipy'" in req for req in requirements)
135
+ project_urls = metadata.get_all("Project-URL", [])
136
+ assert any(url.startswith("Source, https://github.com/aliengineering-byte/phaseprobe") for url in project_urls)
137
+ PY
138
+
139
+ - name: Store the verified distributions
140
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
141
+ with:
142
+ name: verified-distributions-${{ inputs.tag }}
143
+ path: dist/*
144
+ if-no-files-found: error
145
+ retention-days: 1
146
+
147
+ publish:
148
+ name: Publish verified distributions
149
+ needs: verify
150
+ runs-on: ubuntu-latest
151
+ environment:
152
+ name: pypi
153
+ url: https://pypi.org/project/phaseprobe/${{ inputs.version }}/
154
+ permissions:
155
+ id-token: write
156
+ steps:
157
+ - name: Retrieve the verified distributions
158
+ uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
159
+ with:
160
+ name: verified-distributions-${{ inputs.tag }}
161
+ path: dist
162
+
163
+ - name: Publish distributions to PyPI
164
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
165
+ with:
166
+ packages-dir: dist/
@@ -20,13 +20,10 @@ jobs:
20
20
  - run: python -m build
21
21
  - run: python -m twine check dist/*
22
22
  - run: python scripts/audit_package.py
23
- - run: python -m venv .cache/tag-core-smoke
24
- - run: .cache/tag-core-smoke/bin/python -m pip install dist/*.whl
25
- - run: .cache/tag-core-smoke/bin/python -m phaseprobe --version
26
- - run: .cache/tag-core-smoke/bin/python -m phaseprobe scan --example logistic
27
- - run: python -m venv .cache/tag-scipy-smoke
28
- - run: .cache/tag-scipy-smoke/bin/python -m pip install "$(find dist -name '*.whl')[scipy]"
29
- - run: .cache/tag-scipy-smoke/bin/python -m phaseprobe check --config examples/scipy/predator-prey.json
23
+ - run: >-
24
+ python scripts/verify_artifacts.py
25
+ --dist-dir dist
26
+ --work-root "${RUNNER_TEMP}/phaseprobe tagged artifact gate"
30
27
  - run: python scripts/hygiene.py
31
28
  - uses: actions/upload-artifact@v4
32
29
  with:
@@ -0,0 +1,63 @@
1
+ # Changelog
2
+
3
+ All notable changes are documented here. PhaseProbe follows semantic versioning.
4
+
5
+ ## Unreleased
6
+
7
+ - Added bounded offline `verify-evidence` validation for generated pytest evidence, replay
8
+ decisions, artifact hashes, safe relative paths, and the fixed regression template.
9
+ - The unreleased package identity is now `0.3.0` for the new generated-evidence contract.
10
+ - `generate-test` now writes a path-portable claim/decision evidence record that binds the
11
+ validated replay verdict to SHA-256 hashes of the copied fixture and generated pytest, the
12
+ exact reproduction command, PhaseProbe attribution, and explicit scientific limitations.
13
+ - Identical generated evidence is idempotent; conflicting evidence is rejected without overwrite.
14
+ - Invariant-only fixtures now use the narrower `simulation-replay-regression` claim kind, and the
15
+ unsigned evidence-integrity boundary is explicit.
16
+
17
+ ## 0.2.1 — 2026-08-31
18
+
19
+ - Fixed Issue #4: all four SciPy quick-start configurations now ship inside the importable
20
+ package in both wheel and source distribution and load through `importlib.resources` as the
21
+ `scipy-lorenz`, `scipy-lorenz-negative`, `scipy-predator-prey`, and
22
+ `scipy-predator-prey-coarse` built-in examples.
23
+ - Preserved the four exact former `examples/scipy/` config paths as narrow compatibility aliases
24
+ when no file exists at the requested path. Existing user files take precedence; matching is
25
+ case-sensitive and separator-portable, with no fuzzy or basename fallback.
26
+ - Added actionable, versioned diagnostics for missing or malformed built-in resources without
27
+ changing arbitrary config-path or built-in-example behavior.
28
+ - Added Linux and Windows Python 3.12 release gates that build, inspect, install, and exercise the
29
+ wheel and sdist outside the checkout with `PYTHONPATH` removed. The gate runs scan, replay,
30
+ generated pytest, SciPy Lorenz, SciPy predator–prey, `pip check`, and a dependency-free base
31
+ wheel check.
32
+ - Added a source-derived runtime-resource audit and the PEP 561 `py.typed` marker.
33
+ - Generated regression creation is now idempotent for identical evidence and rejects conflicting
34
+ files instead of silently overwriting them.
35
+ - Added an analytic backward-time directional-event check while preserving all solver defaults
36
+ and the existing tolerance-based adaptive replay contract.
37
+
38
+ ## 0.2.0 — 2026-08-02
39
+
40
+ - Added the backward-compatible `TrajectoryAdapter` protocol and shared engine dispatch; all v0.1
41
+ step adapters and examples continue unchanged.
42
+ - Added optional `phaseprobe[scipy]` support with a typed public `SolveIVPAdapter`, controlled
43
+ evaluation grids, scalar/vector tolerances, methods, maximum step, named events, observables,
44
+ classifiers, invariants, invalid-value checks, bounded retention, and solver/environment evidence.
45
+ - Added schema-v2 `exact` and `tolerance` replay while keeping schema-v1 exact fixtures readable.
46
+ Tolerance fixtures preserve state, observable, classifier, invariant threshold, grid, endpoint,
47
+ event, solver-success, version, and platform evidence without claiming byte-identical replay.
48
+ - Added genuine SciPy Lorenz finite-time divergence and predator–prey first-integral examples,
49
+ negative controls, refinement evidence, HTML evidence, and generated pytest fixtures.
50
+ - Added explicit safe-shape Python module/factory loading, core-without-SciPy tests, optional import
51
+ diagnostics, a Windows/Linux core/SciPy CI matrix, current SciPy audit, security/limitations docs,
52
+ demo assets, and an unposted upstream visibility proposal.
53
+ - Base installation remains dependency-free. NumPy and SciPy are optional; no PyPI publication was
54
+ performed.
55
+
56
+ ## 0.1.0 — 2026-08-01
57
+
58
+ - Added `scan`, `perturb`, `check`, `replay`, `generate-test`, and `report` commands.
59
+ - Added typed adapters for logistic map, Lorenz, Lotka–Volterra predator–prey, and a mutually repressing genetic toggle.
60
+ - Added deterministic seeds, canonical configuration, fixed-step execution, invalid-integration diagnostics, capped trace retention, and exact trace hashes.
61
+ - Added transition brackets, bounded initial-state search, repeatability confirmation, replay fixtures with integrity hashes, and fixed-template pytest generation.
62
+ - Added terminal, versioned JSON, and self-contained offline HTML evidence.
63
+ - Added Windows/Linux CI, packaging and packed-install checks, documentation/hygiene gates, and repository-native demo assets.
@@ -4,8 +4,7 @@ title: "PhaseProbe"
4
4
  type: software
5
5
  authors:
6
6
  - name: "Ali"
7
- version: 0.2.0
8
- date-released: 2026-08-02
7
+ version: 0.3.0
9
8
  url: "https://github.com/aliengineering-byte/phaseprobe"
10
9
  repository-code: "https://github.com/aliengineering-byte/phaseprobe"
11
10
  license: Apache-2.0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: phaseprobe
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Find reproducible qualitative simulation transitions and turn them into regression tests.
5
5
  Project-URL: Documentation, https://github.com/aliengineering-byte/phaseprobe#readme
6
6
  Project-URL: Issues, https://github.com/aliengineering-byte/phaseprobe/issues
@@ -43,7 +43,7 @@ Description-Content-Type: text/markdown
43
43
  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.
44
44
 
45
45
  ```console
46
- $ python -m pip install .
46
+ $ pip install phaseprobe pytest
47
47
  $ phaseprobe scan --example logistic
48
48
  QUALITATIVE TRANSITION FOUND
49
49
 
@@ -62,15 +62,24 @@ $ python -m pytest -q tests/generated
62
62
 
63
63
  No API key, LLM, GPU, Docker, account, telemetry, network connection, or hosted service is required at runtime. PhaseProbe's base installation has no third-party runtime dependencies; NumPy and SciPy are isolated in the optional `scipy` extra.
64
64
 
65
+ ## Installation
66
+
67
+ ```bash
68
+ pip install phaseprobe
69
+ pip install "phaseprobe[scipy]"
70
+ pip install pytest # needed only to execute a generated regression test
71
+ ```
72
+
65
73
  ## Five-minute quick start
66
74
 
67
75
  Requires Python 3.10 or newer on Windows or Linux.
68
76
 
69
77
  ```bash
70
- python -m pip install .
78
+ pip install phaseprobe pytest
71
79
  phaseprobe scan --example logistic
72
80
  phaseprobe replay .phaseprobe/runs/<run-id>/replay.json
73
81
  phaseprobe generate-test .phaseprobe/runs/<run-id>/replay.json
82
+ phaseprobe verify-evidence tests/generated/logistic_map-pytest-evidence.json
74
83
  python -m pytest -q tests/generated
75
84
  phaseprobe report .phaseprobe/runs/<run-id>
76
85
  ```
@@ -90,22 +99,41 @@ Five-minute path:
90
99
  [![SciPy demo: solve_ivp evidence, tolerance replay, and generated pytest](assets/scipy-demo-static.png)](assets/scipy-demo.gif)
91
100
 
92
101
  The verified command transcript is [assets/scipy-demo-session.txt](assets/scipy-demo-session.txt),
93
- with a self-contained [HTML example report](examples/scipy/report.html).
102
+ with a self-contained [HTML example report](examples/scipy/report.html). The following path starts
103
+ from a clean environment and does not require a source checkout:
94
104
 
95
105
  ```bash
96
- python -m pip install "phaseprobe[scipy]"
97
- phaseprobe perturb --config examples/scipy/lorenz.json
98
- phaseprobe check --config examples/scipy/predator-prey.json
106
+ python -m venv .venv
107
+ # Linux: source .venv/bin/activate
108
+ # Windows PowerShell: .venv\Scripts\Activate.ps1
109
+ python -m pip install "phaseprobe[scipy]" "pytest==8.4.1"
110
+ phaseprobe perturb --example scipy-lorenz
111
+ phaseprobe check --example scipy-predator-prey
99
112
  phaseprobe replay .phaseprobe/runs/<run-id>/replay.json
100
113
  phaseprobe generate-test .phaseprobe/runs/<run-id>/replay.json
101
114
  python -m pytest -q tests/generated
102
115
  ```
103
116
 
117
+ PhaseProbe 0.2.1 also recognizes the former Issue #4 command
118
+ `phaseprobe perturb --config examples/scipy/lorenz.json` when that relative file is absent and
119
+ loads the corresponding packaged example. This compatibility is limited to the four former
120
+ `examples/scipy/` paths: an existing file always takes precedence, matching is case-sensitive,
121
+ both slash styles are accepted, and unrelated missing paths remain errors. New documentation and
122
+ automation should use the installed-safe `--example scipy-*` names.
123
+
104
124
  The Lorenz command searches a declared initial-`x` perturbation and reports only finite-time
105
- divergence evidence. `examples/scipy/lorenz-negative.json` is its short-window negative control.
106
- The predator–prey command checks the declared first integral with tightly resolved DOP853 settings;
107
- `examples/scipy/predator-prey-coarse.json` deliberately fails the same policy with loose RK23
108
- settings.
125
+ divergence evidence and prints `FINITE-TIME TRAJECTORY DIVERGENCE FOUND`. Its installed
126
+ short-window control is `--example scipy-lorenz-negative`. The predator–prey command checks the
127
+ declared first integral with tightly resolved DOP853 settings and prints `CHECK POLICY PASSED`;
128
+ `--example scipy-predator-prey-coarse` deliberately fails the same policy with loose RK23 settings.
129
+ Each successful command writes a replay fixture and offline report below
130
+ `.phaseprobe/runs/<run-id>/`.
131
+
132
+ Run `phaseprobe perturb --help` or `phaseprobe check --help` to list installed example names. A
133
+ missing packaged resource reports the PhaseProbe version and resource name. Adaptive SciPy replay
134
+ compares the declared state, observable, invariant, endpoint, event, and retained-grid
135
+ tolerances—it does not promise bitwise trajectory equality across platforms or dependency
136
+ versions.
109
137
 
110
138
  For a direct Python API:
111
139
 
@@ -144,7 +172,8 @@ does not import it. See [the audited contract](docs/SCIPY_SOLVE_IVP_AUDIT.md),
144
172
  | `perturb` | Baseline/perturbed twin runs over bounded initial-state changes | Finding or no finding, exit `0` |
145
173
  | `check` | Execute a declared configuration policy for CI | Exit `1` only when policy fails |
146
174
  | `replay` | Validate fixture integrity and re-execute model, parameters, seed, initial state, tolerances, and retention | Declared `exact` or `tolerance` comparison passes |
147
- | `generate-test` | Validate and copy a fixture into a non-extensible pytest template | Executable test under `tests/generated/` |
175
+ | `generate-test` | Validate and copy a fixture into a non-extensible pytest template without conflicting overwrites | Executable test under `tests/generated/` |
176
+ | `verify-evidence` | Re-derive claims/replay decisions and validate bounded strict JSON, artifact hashes, paths, and the fixed template offline | Stable verified or invalid-input exit |
148
177
  | `report` | Regenerate terminal, versioned JSON, and self-contained offline HTML evidence | Local report files |
149
178
 
150
179
  Common options:
@@ -152,6 +181,7 @@ Common options:
152
181
  ```console
153
182
  phaseprobe scan --config examples/configs/logistic-scan.json
154
183
  phaseprobe perturb --example lorenz --json
184
+ phaseprobe perturb --example scipy-lorenz --json
155
185
  phaseprobe check --example predator-prey
156
186
  phaseprobe scan --example logistic --fail-on-finding
157
187
  ```
@@ -166,6 +196,8 @@ Exit codes are stable: `0` completed, `1` declared policy or explicit `--fail-on
166
196
  | 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) |
167
197
  | 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) |
168
198
  | 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) |
199
+ | SciPy Lorenz | `phaseprobe perturb --example scipy-lorenz` | DOP853 twin trajectories cross the declared finite-time distance threshold | `--example scipy-lorenz-negative` shortens the window | [Lorenz, 1963](https://journals.ametsoc.org/view/journals/atsc/20/2/1520-0469_1963_020_0130_dnf_2_0_co_2.xml) |
200
+ | SciPy predator–prey | `phaseprobe check --example scipy-predator-prey` | Tight DOP853 settings preserve the declared first integral tolerance | `--example scipy-predator-prey-coarse` deliberately fails | [Lotka, 1920](https://doi.org/10.1073/pnas.6.7.410) |
169
201
 
170
202
  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.
171
203
 
@@ -188,6 +220,22 @@ manifest.json sizes and SHA-256 hashes
188
220
 
189
221
  Model names used for generated test paths are sanitized. The generated source comes from a fixed template; configuration strings never become executable Python.
190
222
 
223
+ `generate-test` validates the replay before writing anything. Alongside the fixed pytest template
224
+ and copied fixture, it writes `<model>-pytest-evidence.json`: a path-portable claim/decision record
225
+ containing repository/version attribution, the declared replay comparisons, SHA-256 hashes for
226
+ both executable artifacts, the exact pytest command, and explicit scientific limitations.
227
+ Run `phaseprobe verify-evidence <evidence.json>` before pytest to validate the strict schema,
228
+ recompute the hashes, re-execute the replay policy, derive the claim/decision fields, and confirm
229
+ that the regression still matches PhaseProbe's fixed template. Validation is offline, bounded to a
230
+ 1 MiB evidence document and 32 MiB per generated artifact, rejects duplicate keys and unsafe paths,
231
+ and exits `2` for invalid input.
232
+ The record is unsigned: consumers must recompute the two artifact hashes to detect a recomputed or
233
+ substituted record. It is portable integrity metadata, not an authentication signature.
234
+
235
+ If `generate-test` reports a conflict, it has left the existing output untouched. An identical
236
+ artifact can be generated again safely; for different content, inspect the existing files and
237
+ choose a fresh output directory (or remove them only after deciding they are no longer needed).
238
+
191
239
  ## What PhaseProbe adds—and what it does not
192
240
 
193
241
  | existing category | established strength | PhaseProbe’s narrower job |
@@ -269,6 +317,7 @@ python scripts/hygiene.py
269
317
  ```
270
318
 
271
319
  See [CONTRIBUTING.md](CONTRIBUTING.md) and the concrete good-first-issue templates in `.github/ISSUE_TEMPLATE/`.
320
+ Report a reproducible defect with the [bug form](https://github.com/aliengineering-byte/phaseprobe/issues/new?template=bug_report.yml), or propose a bounded adapter or workflow with the [feature form](https://github.com/aliengineering-byte/phaseprobe/issues/new?template=feature_request.yml). Please do not include secrets or private model data.
272
321
 
273
322
  ## License and citation
274
323
 
@@ -5,7 +5,7 @@
5
5
  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.
6
6
 
7
7
  ```console
8
- $ python -m pip install .
8
+ $ pip install phaseprobe pytest
9
9
  $ phaseprobe scan --example logistic
10
10
  QUALITATIVE TRANSITION FOUND
11
11
 
@@ -24,15 +24,24 @@ $ python -m pytest -q tests/generated
24
24
 
25
25
  No API key, LLM, GPU, Docker, account, telemetry, network connection, or hosted service is required at runtime. PhaseProbe's base installation has no third-party runtime dependencies; NumPy and SciPy are isolated in the optional `scipy` extra.
26
26
 
27
+ ## Installation
28
+
29
+ ```bash
30
+ pip install phaseprobe
31
+ pip install "phaseprobe[scipy]"
32
+ pip install pytest # needed only to execute a generated regression test
33
+ ```
34
+
27
35
  ## Five-minute quick start
28
36
 
29
37
  Requires Python 3.10 or newer on Windows or Linux.
30
38
 
31
39
  ```bash
32
- python -m pip install .
40
+ pip install phaseprobe pytest
33
41
  phaseprobe scan --example logistic
34
42
  phaseprobe replay .phaseprobe/runs/<run-id>/replay.json
35
43
  phaseprobe generate-test .phaseprobe/runs/<run-id>/replay.json
44
+ phaseprobe verify-evidence tests/generated/logistic_map-pytest-evidence.json
36
45
  python -m pytest -q tests/generated
37
46
  phaseprobe report .phaseprobe/runs/<run-id>
38
47
  ```
@@ -52,22 +61,41 @@ Five-minute path:
52
61
  [![SciPy demo: solve_ivp evidence, tolerance replay, and generated pytest](assets/scipy-demo-static.png)](assets/scipy-demo.gif)
53
62
 
54
63
  The verified command transcript is [assets/scipy-demo-session.txt](assets/scipy-demo-session.txt),
55
- with a self-contained [HTML example report](examples/scipy/report.html).
64
+ with a self-contained [HTML example report](examples/scipy/report.html). The following path starts
65
+ from a clean environment and does not require a source checkout:
56
66
 
57
67
  ```bash
58
- python -m pip install "phaseprobe[scipy]"
59
- phaseprobe perturb --config examples/scipy/lorenz.json
60
- phaseprobe check --config examples/scipy/predator-prey.json
68
+ python -m venv .venv
69
+ # Linux: source .venv/bin/activate
70
+ # Windows PowerShell: .venv\Scripts\Activate.ps1
71
+ python -m pip install "phaseprobe[scipy]" "pytest==8.4.1"
72
+ phaseprobe perturb --example scipy-lorenz
73
+ phaseprobe check --example scipy-predator-prey
61
74
  phaseprobe replay .phaseprobe/runs/<run-id>/replay.json
62
75
  phaseprobe generate-test .phaseprobe/runs/<run-id>/replay.json
63
76
  python -m pytest -q tests/generated
64
77
  ```
65
78
 
79
+ PhaseProbe 0.2.1 also recognizes the former Issue #4 command
80
+ `phaseprobe perturb --config examples/scipy/lorenz.json` when that relative file is absent and
81
+ loads the corresponding packaged example. This compatibility is limited to the four former
82
+ `examples/scipy/` paths: an existing file always takes precedence, matching is case-sensitive,
83
+ both slash styles are accepted, and unrelated missing paths remain errors. New documentation and
84
+ automation should use the installed-safe `--example scipy-*` names.
85
+
66
86
  The Lorenz command searches a declared initial-`x` perturbation and reports only finite-time
67
- divergence evidence. `examples/scipy/lorenz-negative.json` is its short-window negative control.
68
- The predator–prey command checks the declared first integral with tightly resolved DOP853 settings;
69
- `examples/scipy/predator-prey-coarse.json` deliberately fails the same policy with loose RK23
70
- settings.
87
+ divergence evidence and prints `FINITE-TIME TRAJECTORY DIVERGENCE FOUND`. Its installed
88
+ short-window control is `--example scipy-lorenz-negative`. The predator–prey command checks the
89
+ declared first integral with tightly resolved DOP853 settings and prints `CHECK POLICY PASSED`;
90
+ `--example scipy-predator-prey-coarse` deliberately fails the same policy with loose RK23 settings.
91
+ Each successful command writes a replay fixture and offline report below
92
+ `.phaseprobe/runs/<run-id>/`.
93
+
94
+ Run `phaseprobe perturb --help` or `phaseprobe check --help` to list installed example names. A
95
+ missing packaged resource reports the PhaseProbe version and resource name. Adaptive SciPy replay
96
+ compares the declared state, observable, invariant, endpoint, event, and retained-grid
97
+ tolerances—it does not promise bitwise trajectory equality across platforms or dependency
98
+ versions.
71
99
 
72
100
  For a direct Python API:
73
101
 
@@ -106,7 +134,8 @@ does not import it. See [the audited contract](docs/SCIPY_SOLVE_IVP_AUDIT.md),
106
134
  | `perturb` | Baseline/perturbed twin runs over bounded initial-state changes | Finding or no finding, exit `0` |
107
135
  | `check` | Execute a declared configuration policy for CI | Exit `1` only when policy fails |
108
136
  | `replay` | Validate fixture integrity and re-execute model, parameters, seed, initial state, tolerances, and retention | Declared `exact` or `tolerance` comparison passes |
109
- | `generate-test` | Validate and copy a fixture into a non-extensible pytest template | Executable test under `tests/generated/` |
137
+ | `generate-test` | Validate and copy a fixture into a non-extensible pytest template without conflicting overwrites | Executable test under `tests/generated/` |
138
+ | `verify-evidence` | Re-derive claims/replay decisions and validate bounded strict JSON, artifact hashes, paths, and the fixed template offline | Stable verified or invalid-input exit |
110
139
  | `report` | Regenerate terminal, versioned JSON, and self-contained offline HTML evidence | Local report files |
111
140
 
112
141
  Common options:
@@ -114,6 +143,7 @@ Common options:
114
143
  ```console
115
144
  phaseprobe scan --config examples/configs/logistic-scan.json
116
145
  phaseprobe perturb --example lorenz --json
146
+ phaseprobe perturb --example scipy-lorenz --json
117
147
  phaseprobe check --example predator-prey
118
148
  phaseprobe scan --example logistic --fail-on-finding
119
149
  ```
@@ -128,6 +158,8 @@ Exit codes are stable: `0` completed, `1` declared policy or explicit `--fail-on
128
158
  | 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) |
129
159
  | 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) |
130
160
  | 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) |
161
+ | SciPy Lorenz | `phaseprobe perturb --example scipy-lorenz` | DOP853 twin trajectories cross the declared finite-time distance threshold | `--example scipy-lorenz-negative` shortens the window | [Lorenz, 1963](https://journals.ametsoc.org/view/journals/atsc/20/2/1520-0469_1963_020_0130_dnf_2_0_co_2.xml) |
162
+ | SciPy predator–prey | `phaseprobe check --example scipy-predator-prey` | Tight DOP853 settings preserve the declared first integral tolerance | `--example scipy-predator-prey-coarse` deliberately fails | [Lotka, 1920](https://doi.org/10.1073/pnas.6.7.410) |
131
163
 
132
164
  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.
133
165
 
@@ -150,6 +182,22 @@ manifest.json sizes and SHA-256 hashes
150
182
 
151
183
  Model names used for generated test paths are sanitized. The generated source comes from a fixed template; configuration strings never become executable Python.
152
184
 
185
+ `generate-test` validates the replay before writing anything. Alongside the fixed pytest template
186
+ and copied fixture, it writes `<model>-pytest-evidence.json`: a path-portable claim/decision record
187
+ containing repository/version attribution, the declared replay comparisons, SHA-256 hashes for
188
+ both executable artifacts, the exact pytest command, and explicit scientific limitations.
189
+ Run `phaseprobe verify-evidence <evidence.json>` before pytest to validate the strict schema,
190
+ recompute the hashes, re-execute the replay policy, derive the claim/decision fields, and confirm
191
+ that the regression still matches PhaseProbe's fixed template. Validation is offline, bounded to a
192
+ 1 MiB evidence document and 32 MiB per generated artifact, rejects duplicate keys and unsafe paths,
193
+ and exits `2` for invalid input.
194
+ The record is unsigned: consumers must recompute the two artifact hashes to detect a recomputed or
195
+ substituted record. It is portable integrity metadata, not an authentication signature.
196
+
197
+ If `generate-test` reports a conflict, it has left the existing output untouched. An identical
198
+ artifact can be generated again safely; for different content, inspect the existing files and
199
+ choose a fresh output directory (or remove them only after deciding they are no longer needed).
200
+
153
201
  ## What PhaseProbe adds—and what it does not
154
202
 
155
203
  | existing category | established strength | PhaseProbe’s narrower job |
@@ -231,6 +279,7 @@ python scripts/hygiene.py
231
279
  ```
232
280
 
233
281
  See [CONTRIBUTING.md](CONTRIBUTING.md) and the concrete good-first-issue templates in `.github/ISSUE_TEMPLATE/`.
282
+ Report a reproducible defect with the [bug form](https://github.com/aliengineering-byte/phaseprobe/issues/new?template=bug_report.yml), or propose a bounded adapter or workflow with the [feature form](https://github.com/aliengineering-byte/phaseprobe/issues/new?template=feature_request.yml). Please do not include secrets or private model data.
234
283
 
235
284
  ## License and citation
236
285
 
@@ -19,6 +19,9 @@ Tolerance replay compares retained times/states, named numeric observables, clas
19
19
  invariant results and stored thresholds, endpoint/event evidence, and expected solver success.
20
20
  Environment and solver evidence are retained for interpretation. Artifact hashes still detect
21
21
  fixture tampering, but an adaptive trace hash is not required to match across supported platforms.
22
+ The adapter validation suite checks an exponential system with an analytic terminal crossing in
23
+ both forward and backward integration directions; this validates event plumbing, not exhaustive
24
+ root detection between adaptive internal steps.
22
25
  See [the complete SciPy audit](docs/SCIPY_SOLVE_IVP_AUDIT.md).
23
26
 
24
27
  PhaseProbe 0.1.0 produces bounded computational evidence. It does not perform formal verification, model validation against observations, or computer-assisted proof.