vnnlib-test-solver 2.0.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 (65) hide show
  1. vnnlib_test_solver-2.0.0/.gitignore +218 -0
  2. vnnlib_test_solver-2.0.0/CHANGELOG.md +252 -0
  3. vnnlib_test_solver-2.0.0/CONTRIBUTING.md +186 -0
  4. vnnlib_test_solver-2.0.0/LICENSE +21 -0
  5. vnnlib_test_solver-2.0.0/PKG-INFO +176 -0
  6. vnnlib_test_solver-2.0.0/README.md +142 -0
  7. vnnlib_test_solver-2.0.0/docs/CONFIGURATION.md +636 -0
  8. vnnlib_test_solver-2.0.0/examples/README.md +61 -0
  9. vnnlib_test_solver-2.0.0/examples/capabilities.toml +45 -0
  10. vnnlib_test_solver-2.0.0/examples/f.onnx +0 -0
  11. vnnlib_test_solver-2.0.0/examples/g.onnx +0 -0
  12. vnnlib_test_solver-2.0.0/examples/misbehaving.toml +48 -0
  13. vnnlib_test_solver-2.0.0/examples/results.toml +37 -0
  14. vnnlib_test_solver-2.0.0/examples/worked-example.toml +35 -0
  15. vnnlib_test_solver-2.0.0/pyproject.toml +143 -0
  16. vnnlib_test_solver-2.0.0/src/vnnlib_test_solver/__init__.py +1 -0
  17. vnnlib_test_solver-2.0.0/src/vnnlib_test_solver/assignments.py +163 -0
  18. vnnlib_test_solver-2.0.0/src/vnnlib_test_solver/cli.py +504 -0
  19. vnnlib_test_solver-2.0.0/src/vnnlib_test_solver/config.py +130 -0
  20. vnnlib_test_solver-2.0.0/src/vnnlib_test_solver/dtypes.py +67 -0
  21. vnnlib_test_solver-2.0.0/src/vnnlib_test_solver/errors.py +30 -0
  22. vnnlib_test_solver-2.0.0/src/vnnlib_test_solver/injection.py +119 -0
  23. vnnlib_test_solver-2.0.0/src/vnnlib_test_solver/py.typed +0 -0
  24. vnnlib_test_solver-2.0.0/src/vnnlib_test_solver/querymodel.py +250 -0
  25. vnnlib_test_solver-2.0.0/src/vnnlib_test_solver/rules.py +119 -0
  26. vnnlib_test_solver-2.0.0/src/vnnlib_test_solver/spec.py +79 -0
  27. vnnlib_test_solver-2.0.0/src/vnnlib_test_solver/supports.py +151 -0
  28. vnnlib_test_solver-2.0.0/src/vnnlib_test_solver/validation.py +495 -0
  29. vnnlib_test_solver-2.0.0/src/vnnlib_test_solver/verify.py +417 -0
  30. vnnlib_test_solver-2.0.0/tests/conftest.py +256 -0
  31. vnnlib_test_solver-2.0.0/tests/fixtures/all_real.vnnlib +19 -0
  32. vnnlib_test_solver-2.0.0/tests/fixtures/duplicate_network_name.vnnlib +22 -0
  33. vnnlib_test_solver-2.0.0/tests/fixtures/equal_to.vnnlib +16 -0
  34. vnnlib_test_solver-2.0.0/tests/fixtures/isomorphic.vnnlib +19 -0
  35. vnnlib_test_solver-2.0.0/tests/fixtures/mixed_types.vnnlib +19 -0
  36. vnnlib_test_solver-2.0.0/tests/fixtures/unusual_but_legal.vnnlib +22 -0
  37. vnnlib_test_solver-2.0.0/tests/fixtures/worked_example.vnnlib +16 -0
  38. vnnlib_test_solver-2.0.0/tests/golden/injected_extra_output.stdout +2 -0
  39. vnnlib_test_solver-2.0.0/tests/golden/injected_nothing.stdout +0 -0
  40. vnnlib_test_solver-2.0.0/tests/golden/result_sat.stdout +1 -0
  41. vnnlib_test_solver-2.0.0/tests/golden/result_timed_out.stdout +1 -0
  42. vnnlib_test_solver-2.0.0/tests/golden/result_unknown.stdout +1 -0
  43. vnnlib_test_solver-2.0.0/tests/golden/result_unsat.stdout +1 -0
  44. vnnlib_test_solver-2.0.0/tests/golden/supports_boolean.stdout +1 -0
  45. vnnlib_test_solver-2.0.0/tests/golden/supports_element_types.stdout +4 -0
  46. vnnlib_test_solver-2.0.0/tests/golden/supports_operators.stdout +6 -0
  47. vnnlib_test_solver-2.0.0/tests/golden/supports_opset_versions.stdout +2 -0
  48. vnnlib_test_solver-2.0.0/tests/golden/worked_example.stdout +20 -0
  49. vnnlib_test_solver-2.0.0/tests/test_assignments.py +489 -0
  50. vnnlib_test_solver-2.0.0/tests/test_cli.py +127 -0
  51. vnnlib_test_solver-2.0.0/tests/test_config.py +690 -0
  52. vnnlib_test_solver-2.0.0/tests/test_conformance.py +432 -0
  53. vnnlib_test_solver-2.0.0/tests/test_contract.py +732 -0
  54. vnnlib_test_solver-2.0.0/tests/test_determinism.py +293 -0
  55. vnnlib_test_solver-2.0.0/tests/test_error_conditions.py +524 -0
  56. vnnlib_test_solver-2.0.0/tests/test_injection.py +615 -0
  57. vnnlib_test_solver-2.0.0/tests/test_network_mapping.py +399 -0
  58. vnnlib_test_solver-2.0.0/tests/test_not_over_policed.py +272 -0
  59. vnnlib_test_solver-2.0.0/tests/test_output_bytes.py +98 -0
  60. vnnlib_test_solver-2.0.0/tests/test_packaging.py +30 -0
  61. vnnlib_test_solver-2.0.0/tests/test_querymodel.py +369 -0
  62. vnnlib_test_solver-2.0.0/tests/test_rules.py +336 -0
  63. vnnlib_test_solver-2.0.0/tests/test_supports.py +599 -0
  64. vnnlib_test_solver-2.0.0/tests/test_timeout.py +165 -0
  65. vnnlib_test_solver-2.0.0/tests/test_verify.py +341 -0
@@ -0,0 +1,218 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py.cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ # Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ # uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ # poetry.lock
109
+ # poetry.toml
110
+
111
+ # pdm
112
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
113
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
114
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
115
+ # pdm.lock
116
+ # pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # pixi
121
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
122
+ # pixi.lock
123
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
124
+ # in the .venv directory. It is recommended not to include this directory in version control.
125
+ .pixi
126
+
127
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
128
+ __pypackages__/
129
+
130
+ # Celery stuff
131
+ celerybeat-schedule
132
+ celerybeat.pid
133
+
134
+ # Redis
135
+ *.rdb
136
+ *.aof
137
+ *.pid
138
+
139
+ # RabbitMQ
140
+ mnesia/
141
+ rabbitmq/
142
+ rabbitmq-data/
143
+
144
+ # ActiveMQ
145
+ activemq-data/
146
+
147
+ # SageMath parsed files
148
+ *.sage.py
149
+
150
+ # Environments
151
+ .env
152
+ .envrc
153
+ .venv
154
+ env/
155
+ venv/
156
+ ENV/
157
+ env.bak/
158
+ venv.bak/
159
+
160
+ # Spyder project settings
161
+ .spyderproject
162
+ .spyproject
163
+
164
+ # Rope project settings
165
+ .ropeproject
166
+
167
+ # mkdocs documentation
168
+ /site
169
+
170
+ # mypy
171
+ .mypy_cache/
172
+ .dmypy.json
173
+ dmypy.json
174
+
175
+ # Pyre type checker
176
+ .pyre/
177
+
178
+ # pytype static type analyzer
179
+ .pytype/
180
+
181
+ # Cython debug symbols
182
+ cython_debug/
183
+
184
+ # PyCharm
185
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
186
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
187
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
188
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
189
+ # .idea/
190
+
191
+ # Abstra
192
+ # Abstra is an AI-powered process automation framework.
193
+ # Ignore directories containing user credentials, local state, and settings.
194
+ # Learn more at https://abstra.io/docs
195
+ .abstra/
196
+
197
+ # Visual Studio Code
198
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
199
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
200
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
201
+ # you could uncomment the following to ignore the entire vscode folder
202
+ # .vscode/
203
+ # Temporary file for partial code execution
204
+ tempCodeRunnerFile.py
205
+
206
+ # Ruff stuff:
207
+ .ruff_cache/
208
+
209
+ # PyPI configuration file
210
+ .pypirc
211
+
212
+ # Marimo
213
+ marimo/_static/
214
+ marimo/_lsp/
215
+ __marimo__/
216
+
217
+ # Streamlit
218
+ .streamlit/secrets.toml
@@ -0,0 +1,252 @@
1
+ # Version 2.0.0
2
+
3
+ A breaking release, and every break in it is a client ruling rather than a change of
4
+ mind here. Anything written against 1.0.0 needs all three of the following.
5
+
6
+ **The command is renamed `vnnlib-test-solver` to `vnnlibTestSolver`**, and the
7
+ configuration file it looks for in the working directory follows it to
8
+ `vnnlibTestSolver.toml`. There is no alias for either old spelling. The distribution is
9
+ still installed as `vnnlib-test-solver` and the import package is still
10
+ `vnnlib_test_solver`; the three differ deliberately, because a distribution name is
11
+ normalised to lower case by every installer that reads one and an import package cannot
12
+ contain a dash at all.
13
+
14
+ **There is no longer a default configuration.** Running with no `--config`, no
15
+ `VNNLIB_TEST_SOLVER_CONFIG` and no `vnnlibTestSolver.toml` in the working directory is
16
+ now an error, exiting 2 with nothing on standard output. It previously answered
17
+ `unknown` for every query and reported a full set of capabilities. This applies to
18
+ `--name` and `--version` as much as to `verify`: they are configured answers like any
19
+ other. The reasoning is that a test solver must never give an answer nobody chose,
20
+ because that is the one reply in the system indistinguishable from a deliberate one.
21
+
22
+ **The `real` exemption is now a property of the whole query rather than of each
23
+ declaration.** The standard exempts a query written in `real` from two of the three
24
+ error conditions the verify command must report, and defines a real-valued query as one
25
+ that uses that type exclusively. A query declaring `real` alongside any other type is
26
+ therefore not exempt at all, including for its `real` variables, where previously each
27
+ such variable was exempt on its own. A query written entirely in `real` is exempt in
28
+ full, exactly as before. A configuration wanting a mixed query to answer names `real` in
29
+ its soundness claim, which is the same explicit claim every other element type already
30
+ needed. Mixing element types is still not itself refused.
31
+
32
+ Also in this release, and neither of them breaking:
33
+
34
+ * Reporting the `real` element type no longer warns that it is unclaimed. The standard
35
+ makes `real` the answer a solver gives precisely when it cannot promise sound
36
+ reasoning over floating point, so a configuration reporting it alone and claiming
37
+ nothing was being warned at for following the standard's own advice. Reporting
38
+ `float32` or `float64` without the matching claim still warns, which is the case the
39
+ clause actually names.
40
+ * The exit codes are unchanged and are now confirmed conformant rather than assumed to
41
+ be. The standard says only that zero means the solver ran and terminated normally,
42
+ `unsat` and `timed-out` included, and is deliberately not opinionated about what any
43
+ non-zero value means.
44
+
45
+ **This version is the first published to PyPI**, so it installs with `pip install
46
+ vnnlib-test-solver` instead of from a clone of the repository. Nothing about the solver
47
+ changed to make that true and no behaviour here depends on how it was installed; the
48
+ distribution keeps the name it already had, and the command it installs is still
49
+ `vnnlibTestSolver`.
50
+
51
+ **Still not in this release.** The three limitations listed under *Not in this release* in
52
+ the 1.0.0 entry below all carry forward unchanged: serialised assignments are not written,
53
+ a red continuous-integration result does not mechanically block a merge, and assignment
54
+ values are not checked beside a result other than `sat`. None of them was resolved here.
55
+
56
+ ---
57
+
58
+ # Version 1.0.0
59
+
60
+ The first release.
61
+
62
+ A configurable test solver for the VNN-LIB standard: it implements the command-line
63
+ interface of chapter 5 of the standard and answers entirely from a TOML configuration
64
+ file, performing no verification of its own. It exists so that a tool driving VNN-LIB
65
+ solvers can be tested against a solver whose every response it controls.
66
+
67
+ What a caller gets from this release:
68
+
69
+ * Both global options, `--name` and `--version`, answered from configuration.
70
+ * The `supports` command answering all eleven of the capabilities the standard makes
71
+ mandatory, in the four response shapes it defines.
72
+ * The `verify` command accepting the full invocation of section 5.3 and returning any of
73
+ `sat`, `unsat`, `unknown` or `timed-out`, chosen by matching the query file's name
74
+ against configured rules.
75
+ * A configurable delay, so that a caller's own timeout handling can be exercised against
76
+ a solver that answers on the caller's deadline rather than one that genuinely takes
77
+ minutes.
78
+ * The satisfying assignment of section 5.3.1 in the command-line format, printed from
79
+ configured values and the query file's own declarations, and compared byte for byte
80
+ against the standard's worked example.
81
+ * All three of the error conditions section 5.3.2 requires, each distinguishable from
82
+ the others and checked in the order the standard lists them.
83
+ * Four controls that make the solver misbehave on request - noise on `stderr`, an
84
+ arbitrary exit status, deliberately malformed output, and abnormal termination - so
85
+ that a caller can exercise its own handling of a solver behaving badly.
86
+ * Output guaranteed to be deterministic and `LF`-terminated on every platform, with
87
+ results on `stdout` and everything else on `stderr`.
88
+
89
+ Installing needs no compiler and no build tooling on Linux `x86_64`, macOS and Windows.
90
+ There is no `aarch64` Linux wheel for the pinned parser dependency, so that one platform
91
+ builds it from source; the readme's requirements section has the detail.
92
+
93
+ ## Not in this release
94
+
95
+ * **Serialised assignments.** Section 5.3.1's optional `.pb` `TensorProto` output is not
96
+ written. The standard requires the option only of a solver that reports supporting it,
97
+ and this solver reports `false`; the option is still accepted, and says on `stderr`
98
+ that nothing was written rather than exiting zero having silently done nothing. The
99
+ command-line assignment format, which the standard makes the default and requires of
100
+ every solver, is implemented.
101
+ * **Enforcement of the continuous integration result.** The suite runs on nine platform
102
+ and version combinations on every pull request, but a red result does not block a
103
+ merge. That is branch protection rather than a workflow, and it needs repository
104
+ administration this team does not hold.
105
+ * **A check on assignment values beside a result other than `sat`.** A value array of the
106
+ wrong length, or one naming a variable the query does not declare, is refused beside
107
+ `result = "sat"` and accepted in silence beside any other result, because values that
108
+ will never be printed are never measured. Documented rather than corrected: changing it
109
+ moves an exit code that other code in this project already tests against.
110
+
111
+ ## How this release was built
112
+
113
+ Everything below is the record of the work, newest first. None of it describes a change
114
+ to a previously published version, because there is not one - 0.1.0 was never tagged and
115
+ never published. It is kept because what was decided, and why, is worth more than a
116
+ summary of it.
117
+
118
+ Each entry describes the package as it stood when that piece of work landed. An entry
119
+ saying something is not implemented yet has been overtaken by one above it; the Version
120
+ 1.0.0 entry above is what 1.0.0 actually does.
121
+
122
+ * Added continuous integration. The test suite now runs on Ubuntu, macOS and Windows
123
+ against Python 3.9, 3.11 and 3.13 on every pull request, with `fail-fast` disabled so
124
+ that one platform's failure does not hide another's. Two of the defects found in this
125
+ package so far are invisible on Linux, and the declared Python floor of 3.9 had never
126
+ been executed anywhere until now.
127
+ * Added a job that installs the package into a container with no compiler, no CMake and
128
+ no pybind11 present, and which refuses to run at all if one of them is on the path. It
129
+ also forbids a source build of the parser, so the pure-Python claim cannot be satisfied
130
+ by a machine that happened to be able to compile something.
131
+ * Added a byte-for-byte conformance suite comparing standard output against eleven
132
+ committed expected-output files: the standard's own worked example, one per verify
133
+ result, the four capability response shapes transcribed from the standard's own
134
+ examples, and the two cases where injected output replaces the answer. Every comparison
135
+ is made on raw bytes, and a mismatch names the line that differs rather than printing
136
+ two blobs.
137
+ * Added tests asserting that five consecutive runs of every command that writes to
138
+ standard output produce identical bytes. The five runs use five different hash seeds,
139
+ so an unordered collection reaching the output fails every time rather than most of the
140
+ time.
141
+ * Added a self-test, selectable with `-m contract`, asserting that the published
142
+ interface is exactly what is documented: the global options, both commands and their
143
+ options, the eleven capability flags, every configuration file key, the configuration
144
+ discovery order and the exit codes. It checks both directions - that everything
145
+ documented exists, and that nothing undocumented exists.
146
+ * Documented, without changing it, that a rule's assignment values are only checked
147
+ against the query when that rule answers `sat`. A value array of the wrong length, or a
148
+ name the query does not declare, is refused beside `result = "sat"` and accepted in
149
+ silence beside `result = "unsat"`. This has been true since assignments were added and
150
+ had simply never been stated.
151
+ * Corrected the description of the `verify` command in the README, which said two of the
152
+ standard's three error conditions were not implemented. All three have been implemented
153
+ since the previous release, and the same file documented them at length two sections
154
+ further down.
155
+ * Added regression tests asserting what the solver deliberately does not reject. The
156
+ standard leaves anything outside its three named error conditions to the solver's
157
+ discretion, and a conformance baseline that invents a fourth rejection teaches that
158
+ invented rule to every test written against it. Covered: a query mixing the `real`
159
+ element type with another one, two networks declared under the same name, hidden nodes,
160
+ comments and irregular whitespace, a mapped model file that does not exist or is not an
161
+ ONNX file at all, and a query file with any name or none. No behaviour changed.
162
+ * The four failure-injection controls now do something. `stderr` writes the configured
163
+ noise before the answer and before any delay, `exit_code` returns the configured status,
164
+ `raw_stdout` replaces standard output entirely, and `crash` terminates the process
165
+ abnormally after the output has been written and flushed. Each was accepted and
166
+ validated from the first release and had no effect until now.
167
+ * Added a top-level `[injection]` table carrying the same four controls and applying to
168
+ whichever command runs. A rule is selected by matching the query file's name and the
169
+ `supports` command has no query file, so nothing hanging off a rule could reach a
170
+ capability response, and a malformed one was not producible at all. Where a rule and the
171
+ table both set a control, the rule wins for that control alone.
172
+ * Nothing is injected into a run that is refused. A configuration mistake, a usage mistake
173
+ and each of the standard's error conditions keep their own exit code and write nothing
174
+ to standard output, so the solver can still report its own errors.
175
+ * Fixed: `exit_code` is now refused unless it is between 0 and 255. A process exit status
176
+ carries eight bits on a Unix-like system, so `exit_code = 256` reached the caller as
177
+ `0` - a run configured to fail reporting that it had succeeded. Invisible on Windows,
178
+ where the whole value survives. The refusal names what the value would have become.
179
+ * Setting `crash` and `exit_code` in the same table is refused, because a process that
180
+ terminates abnormally reports no exit status and only one of the two can happen. Set on
181
+ a rule and in the table respectively, the rule's wins.
182
+ * Added the second and third error conditions the standard requires of `verify`. The
183
+ element types the supplied model files expose must not disagree with the types the query
184
+ declares, and a query declaring a type the solver cannot analyse soundly is refused
185
+ unless the user consented by declaring it `real`. Both write nothing to `stdout`, exit
186
+ `1`, and carry messages distinct from each other and from the model-mapping condition,
187
+ so a caller can tell which one it triggered without matching on wording. All three are
188
+ checked in the order the standard lists them.
189
+ * Added `[rules.model_element_types]`, a table keyed by variable name stating what the
190
+ supplied model files expose. Nothing here opens a model file, so this is the only source
191
+ the element-type condition can have; the query supplies the declared types it is compared
192
+ against. A variable the table does not name is taken to match, and a name the query does
193
+ not declare is a configuration error rather than one of the standard's conditions.
194
+ * `[soundness] sound-for` now drives an error as well as a warning. A configuration with no
195
+ `[soundness]` table is not checked at all, because reading silence as a claim of total
196
+ unsoundness would make every such configuration refuse every query; an explicitly empty
197
+ `sound-for` is a claim, and refuses everything not declared `real`.
198
+ * A declaration written in `real` is exempt from both new conditions, applied to each
199
+ declaration on its own rather than to the query as a whole. The standard describes a
200
+ real-valued query as using `real` exclusively, but the query parser accepts a query
201
+ mixing `real` with another element type, so a decision was needed and refusing such a
202
+ query would have invented a rejection the standard leaves to solver discretion.
203
+ * The packaged default configuration now reports and claims soundness over `int32`.
204
+ Without it the default refused the standard's own worked example as soon as a model file
205
+ was mapped, since that example declares an `int32` variable.
206
+ * Added the `verify` command, as far as its result line: the query file path, a
207
+ repeatable `--network <name>=<path>`, `--timeout` in whole seconds, and
208
+ `--serialise-assignments`. `--assignments` is accepted alongside the latter, because
209
+ section 5.3 spells the same option both ways. The result is the first line of `stdout`
210
+ and is one of `sat`, `unsat`, `unknown` or `timed-out`. Variable assignments for a `sat`
211
+ result are not implemented yet, and the query file is not read at all.
212
+ * Which result a query receives is decided by the `[[rules]]` array. The first rule whose
213
+ `match` pattern matches the query file's **name** wins; patterns are compared against the
214
+ name alone, case sensitively on every platform. A query that no rule matches is an error
215
+ rather than a silently chosen default, and the message names the file and every pattern
216
+ it tried.
217
+ * A rule's `delay_seconds` now interacts with `--timeout`. A delay longer than the timeout
218
+ waits out the timeout and answers `timed-out`, so a caller's own deadline handling can be
219
+ exercised against a solver that reliably runs long. A delay equal to the timeout answers
220
+ normally, and an absent `--timeout` is not a timeout of zero.
221
+ * Fixed: an empty query path is refused. `verify ""` was accepted, matched the catch-all
222
+ rule pattern, and answered for a file that cannot exist.
223
+ * Fixed: `--timeout` and a rule's `delay_seconds` are bounded. Above roughly 68 years the
224
+ wait itself raises, so a large value reached the caller as an unhandled traceback rather
225
+ than a message. The bound is a fixed constant rather than the platform's own ceiling, so
226
+ a configuration that loads on one machine cannot raise on another.
227
+ * `--serialise-assignments` now reports on `stderr` that assignment files are not written
228
+ yet. It was previously accepted and silently ignored, which to a caller testing
229
+ serialisation was indistinguishable from success.
230
+ * Fixed: `stdout` is written as bytes and uses `LF` line endings on every platform. On
231
+ Windows every command emitted `CRLF`, which made recorded output impossible to compare
232
+ byte for byte between platforms and left a trailing carriage return inside every value a
233
+ consumer read by splitting on newlines.
234
+ * Fixed: a configuration value that would corrupt the shape of a response is refused when
235
+ the file loads rather than emitted. An operator name containing whitespace was the
236
+ damaging case, because it produced output that parsed cleanly and meant something else:
237
+ an operator named `Conv Relu` was read back as the operator `Conv` supporting an element
238
+ type called `Relu`. Empty and whitespace-bearing version strings, and line breaks in the
239
+ solver name or version, are refused for the same reason. The failure-injection keys are
240
+ deliberately exempt, since producing unparseable output is their purpose.
241
+ * Added the `supports` command, answering all eleven mandatory capabilities of the
242
+ standard's section 5.4 from configuration, in the four response shapes it defines.
243
+ `capabilities` is accepted as an undocumented alias, because section 5.1 names the
244
+ command that way while section 5.4 defines it as `supports`.
245
+ * A configuration reporting an ONNX element type it does not also claim sound analysis
246
+ over now warns on `stderr`, per section 5.4.1.
247
+ * Added the installable package: console entry point, configuration discovery across four
248
+ sources, schema validation, and the `--name` and `--version` global options.
249
+ * Added `.gitattributes` to force LF line endings and mark golden/fixture files
250
+ binary, so line-ending conversion can never corrupt a byte-exact comparison.
251
+ * Wrote the first `README.md`, replacing the two-line placeholder the repository was
252
+ created with.
@@ -0,0 +1,186 @@
1
+ # Contributing
2
+
3
+ This repository implements the VNN-LIB 2.0 command-line interface (Chapter 5 of the
4
+ standard) as a configurable test solver.
5
+
6
+ ## Setting up a working copy
7
+
8
+ ```bash
9
+ git clone <repo-url>
10
+ cd VNNLIB-Test-Solver
11
+ python -m venv .venv
12
+ source .venv/bin/activate # Windows: .venv\Scripts\activate
13
+ pip install -e ".[dev]"
14
+ ```
15
+
16
+ The `[dev]` extra pulls in the test, lint and type-checking tools alongside the package
17
+ itself. `pip install .` on its own is enough to run the solver, but not enough to run its
18
+ test suite - if `pytest` cannot be found, this is why.
19
+
20
+ Python 3.9 or later. This package is pure Python, but its one dependency is a compiled
21
+ extension and there is no Linux `aarch64` wheel for the pinned version. On Linux
22
+ `x86_64`, macOS and Windows an install needs no compiler, CMake or other build tooling;
23
+ on arm64 Linux it builds the parser from source and does need them. The readme's
24
+ requirements section carries the platform table.
25
+
26
+ ## The parser dependency
27
+
28
+ `vnnlib` is confined to a single module, which is the only file permitted to import it.
29
+ Everything crossing out of that module is a plain string, integer or tuple - no parser
30
+ object and no name from the parser's vocabulary reaches the rest of the package. That
31
+ confinement is what makes the dependency reversible: were this package ever required to
32
+ be standalone, one file would change and every other module would keep its interface.
33
+
34
+ ## Running the checks
35
+
36
+ ```bash
37
+ python -m pytest -q # the test suite
38
+ python -m pytest -q -m contract # the published interface, on its own
39
+ ruff check . # lint
40
+ ruff format --check . # formatting
41
+ mypy src/ # type checking
42
+ ```
43
+
44
+ `ruff format --check` is not the same command as `ruff check`: the first looks at
45
+ formatting and the second does not, so running one is not running the other.
46
+
47
+ `vnnlibTestSolver --version` should also work once installed. The test suite shells out
48
+ to that installed console script rather than importing the package, because what this
49
+ project delivers is a binary, so a suite that called into Python directly would prove
50
+ nothing about what happens after `pip install`.
51
+
52
+ ## What runs on a pull request
53
+
54
+ `.github/workflows/buildAndTest.yml` runs the suite on Ubuntu, macOS and Windows against
55
+ Python 3.9, 3.11 and 3.13 - nine combinations, with `fail-fast` off so one platform's
56
+ failure does not hide another's. Two further jobs run alongside it: lint, formatting and
57
+ type checking; and an installation into a container with no compiler, no CMake and no
58
+ pybind11 present, which is how the pure-Python claim is proven rather than asserted.
59
+
60
+ A failing check is visible on the pull request but does not mechanically prevent a merge,
61
+ because branch protection is not configured on this repository. Read the checks before
62
+ merging.
63
+
64
+ ## How correctness is established
65
+
66
+ This package is a conformance baseline: other people's tests measure against its output.
67
+ If it is wrong, every one of those tests is measuring against a broken ruler and still
68
+ passing. That makes "the suite is green" a weaker claim here than it is in most projects,
69
+ and the practices below exist to close the gap.
70
+
71
+ ### The suite tests the delivered artifact
72
+
73
+ Tests shell out to the installed console script rather than importing the package. What
74
+ this project ships is a binary, so a suite that called into Python directly would prove
75
+ nothing about what happens after `pip install` - it would not catch a broken entry point,
76
+ a missing dependency, or packaging metadata that disagrees with the code.
77
+ `tests/test_packaging.py` checks the installed distribution specifically.
78
+
79
+ ### Output is compared as bytes, never as text
80
+
81
+ `tests/golden/` holds eleven expected-output files: the standard's own worked example, one
82
+ per `verify` result, the four capability response shapes, and the two cases where injected
83
+ output replaces the answer. Every comparison is on raw bytes, so line endings, spacing
84
+ inside `[2,2]` and trailing newlines are all part of what is asserted.
85
+ `tests/test_output_bytes.py` reads streams as bytes rather than decoded text, because a
86
+ decoded comparison silently hides exactly the platform differences most worth catching.
87
+
88
+ **Nothing regenerates the golden files.** If a change makes one fail, the question is which
89
+ of the two is wrong, and the answer belongs in the pull request description line by line.
90
+ An expected-output file that nobody has read is not a specification, it is a snapshot of
91
+ whatever the code did last.
92
+
93
+ `tests/test_determinism.py` runs the same input repeatedly and requires the bytes to be
94
+ identical, which is what stops a set iteration or a timestamp reaching output unnoticed.
95
+
96
+ ### Tests assert what is *not* rejected
97
+
98
+ Section 5.3.2 makes three things errors and leaves everything else to the solver's
99
+ discretion. A package that quietly grew a fourth error condition would break callers
100
+ relying on that discretion, and no ordinary test would notice, because nothing was
101
+ asserting the absence. `tests/test_not_over_policed.py` exists to hold that line: it feeds
102
+ in unusual but legal queries and requires them to be answered rather than refused.
103
+
104
+ ### A claim is verified or it is labelled an assumption
105
+
106
+ Where the standard is silent or contradicts itself, this package implements the most
107
+ conservative reading and says so, rather than encoding a guess as though it were settled.
108
+ The inconsistencies found while implementing Chapter 5 were reported upstream -
109
+ [VNNLIB-Standard#179](https://github.com/VNNLIB/VNNLIB-Standard/issues/179) and
110
+ [VNNLIB-Standard#181](https://github.com/VNNLIB/VNNLIB-Standard/issues/181) - rather than
111
+ resolved privately, so that the next implementer meets a documented question instead of
112
+ rediscovering it.
113
+
114
+ ### Claims about the build are proven, not asserted
115
+
116
+ "Pure Python, no toolchain needed" is easy to believe on a machine that has a compiler
117
+ sitting on its path. The container job refuses to run at all if `cc`, `gcc`, `g++`,
118
+ `clang`, `make` or `cmake` is present, and forbids pip from building the parser from
119
+ source, so it cannot pass for the wrong reason. The platform where that claim does *not*
120
+ hold - Linux `aarch64` - is named in the readme rather than left to be discovered.
121
+
122
+ ### When reviewing a test, check it can fail
123
+
124
+ A check comparing two things to each other rather than to an expected value passes when
125
+ both are broken. This has happened here: two spellings of one command were compared to
126
+ each other, and with no capabilities configured both failed identically, so the check
127
+ passed while measuring nothing. Before trusting a new test, break the thing it names and
128
+ confirm it goes red.
129
+
130
+ ## Making a release
131
+
132
+ 1. Update the version in `pyproject.toml` and `src/vnnlib_test_solver/__init__.py`.
133
+ Tests assert that both agree with the installed distribution, so missing one fails
134
+ the suite rather than shipping.
135
+
136
+ 2. Add a row to the version compatibility table in `README.md`.
137
+
138
+ 3. Add the version and its changes to `CHANGELOG.md`.
139
+
140
+ 4. Merge to `main`, then tag the merge commit once its checks are green:
141
+
142
+ ```bash
143
+ git checkout main && git pull
144
+ git tag -a v1.0.0 -m "Version 1.0.0"
145
+ git push origin v1.0.0
146
+ ```
147
+
148
+ 5. Create a Release on GitHub against that tag, using the changelog entry as its
149
+ description. Publishing the Release runs `Build-and-Deploy`, which builds the source
150
+ distribution and the wheel, checks them, and uploads both to PyPI.
151
+
152
+ To rehearse an upload first, run `Build-and-Deploy` by hand and choose TestPyPI. It
153
+ builds and checks exactly as the real run does, and uploads to an index nobody installs
154
+ from.
155
+
156
+ To see what would be built without uploading anything:
157
+
158
+ ```bash
159
+ pip install build twine
160
+ python -m build
161
+ twine check --strict dist/*
162
+ python .github/scripts/check_sdist.py
163
+ ```
164
+
165
+ `build` and `twine` are deliberately not in the `dev` extra. They inspect the package
166
+ rather than forming part of it, and what this package installs is a constraint it is
167
+ held to.
168
+
169
+ ### What an upload commits you to
170
+
171
+ A version number on PyPI can never be reused. Deleting a release does not free it, and
172
+ yanking one hides it from resolvers while leaving the files downloadable by anyone who
173
+ asks for that version exactly. There is no correcting a release afterwards, only
174
+ releasing again with a higher number.
175
+
176
+ Two checks therefore run before every upload and both refuse it outright rather than
177
+ warning. One compares the source distribution against `git ls-files` and fails if it
178
+ carries a single file this repository does not track. The other fails if the version
179
+ built disagrees with the tag the Release names, which is what a Release created against
180
+ an older tag looks like.
181
+
182
+ The first is not a formality. The build backend ships whatever is in the project
183
+ directory unless told otherwise, and the files kept out of this repository through
184
+ `.git/info/exclude` are invisible to it, because that file is local to one clone. Built
185
+ without the explicit list in `pyproject.toml`, this package produced a 1.3 MB archive
186
+ carrying working notes, meeting minutes and a copy of the standard.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 VNNLIB
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.