claimtrail 0.5.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.
- claimtrail-0.5.0/.github/workflows/publish.yml +22 -0
- claimtrail-0.5.0/.github/workflows/tests.yml +28 -0
- claimtrail-0.5.0/.gitignore +74 -0
- claimtrail-0.5.0/INTEGRATION.md +244 -0
- claimtrail-0.5.0/LICENSE +21 -0
- claimtrail-0.5.0/PKG-INFO +327 -0
- claimtrail-0.5.0/README.md +299 -0
- claimtrail-0.5.0/docs/history/qprov-engine-overview.pdf +0 -0
- claimtrail-0.5.0/examples/lending-review/pipeline.py +55 -0
- claimtrail-0.5.0/examples/lending-review/report_draft.md +17 -0
- claimtrail-0.5.0/examples/lending-review/run_demo.py +97 -0
- claimtrail-0.5.0/examples/q-numbers/claims.tex +41 -0
- claimtrail-0.5.0/examples/q-numbers/q_real_demo.py +33 -0
- claimtrail-0.5.0/examples/q-numbers/q_real_python.py +318 -0
- claimtrail-0.5.0/examples/q-numbers/run_example.py +133 -0
- claimtrail-0.5.0/pyproject.toml +45 -0
- claimtrail-0.5.0/src/claimtrail/__init__.py +110 -0
- claimtrail-0.5.0/src/claimtrail/assertions.py +183 -0
- claimtrail-0.5.0/src/claimtrail/audit_paper.py +656 -0
- claimtrail-0.5.0/src/claimtrail/audit_report.py +397 -0
- claimtrail-0.5.0/src/claimtrail/claims.py +290 -0
- claimtrail-0.5.0/src/claimtrail/cli.py +917 -0
- claimtrail-0.5.0/src/claimtrail/contrib/__init__.py +5 -0
- claimtrail-0.5.0/src/claimtrail/contrib/qnumbers.py +931 -0
- claimtrail-0.5.0/src/claimtrail/external.py +149 -0
- claimtrail-0.5.0/src/claimtrail/gitinfo.py +32 -0
- claimtrail-0.5.0/src/claimtrail/hardware.py +90 -0
- claimtrail-0.5.0/src/claimtrail/inputs.py +315 -0
- claimtrail-0.5.0/src/claimtrail/ledger.py +247 -0
- claimtrail-0.5.0/src/claimtrail/properties.py +150 -0
- claimtrail-0.5.0/src/claimtrail/quantities.py +165 -0
- claimtrail-0.5.0/src/claimtrail/query.py +30 -0
- claimtrail-0.5.0/src/claimtrail/serialize.py +232 -0
- claimtrail-0.5.0/src/claimtrail/store.py +1040 -0
- claimtrail-0.5.0/src/claimtrail/tracking.py +584 -0
- claimtrail-0.5.0/src/claimtrail/verify.py +200 -0
- claimtrail-0.5.0/src/qprov/__init__.py +23 -0
- claimtrail-0.5.0/tests/__init__.py +0 -0
- claimtrail-0.5.0/tests/conftest.py +23 -0
- claimtrail-0.5.0/tests/test_assertions.py +127 -0
- claimtrail-0.5.0/tests/test_audit_paper.py +290 -0
- claimtrail-0.5.0/tests/test_audit_report.py +136 -0
- claimtrail-0.5.0/tests/test_canonical_file.py +328 -0
- claimtrail-0.5.0/tests/test_claims.py +137 -0
- claimtrail-0.5.0/tests/test_cli.py +221 -0
- claimtrail-0.5.0/tests/test_compat.py +62 -0
- claimtrail-0.5.0/tests/test_external.py +203 -0
- claimtrail-0.5.0/tests/test_ledger.py +141 -0
- claimtrail-0.5.0/tests/test_paper_tag_gate.py +154 -0
- claimtrail-0.5.0/tests/test_properties.py +478 -0
- claimtrail-0.5.0/tests/test_quantities.py +75 -0
- claimtrail-0.5.0/tests/test_sage_integration.py +105 -0
- claimtrail-0.5.0/tests/test_serialize.py +94 -0
- claimtrail-0.5.0/tests/test_store.py +473 -0
- claimtrail-0.5.0/tests/test_tracking.py +142 -0
- claimtrail-0.5.0/tests/test_verify.py +57 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
name: publish
|
|
2
|
+
|
|
3
|
+
# Publishes to PyPI when a version tag (v*) is pushed. Uses PyPI trusted
|
|
4
|
+
# publishing, so no API token is stored in the repo or in secrets.
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
tags: ["v*"]
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
publish:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
environment: pypi
|
|
14
|
+
permissions:
|
|
15
|
+
id-token: write
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
- uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: "3.12"
|
|
21
|
+
- run: pip install build && python -m build
|
|
22
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
strategy:
|
|
11
|
+
fail-fast: false
|
|
12
|
+
matrix:
|
|
13
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
with:
|
|
17
|
+
fetch-depth: 0
|
|
18
|
+
- uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: ${{ matrix.python-version }}
|
|
21
|
+
- run: pip install -e ".[dev]"
|
|
22
|
+
- run: python -m pytest
|
|
23
|
+
- name: Run the example end to end
|
|
24
|
+
run: |
|
|
25
|
+
pip install sympy
|
|
26
|
+
cd examples/q-numbers && python run_example.py
|
|
27
|
+
- name: Run the lending-review demo end to end
|
|
28
|
+
run: python examples/lending-review/run_demo.py
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
build/
|
|
8
|
+
develop-eggs/
|
|
9
|
+
dist/
|
|
10
|
+
downloads/
|
|
11
|
+
eggs/
|
|
12
|
+
.eggs/
|
|
13
|
+
lib/
|
|
14
|
+
lib64/
|
|
15
|
+
parts/
|
|
16
|
+
sdist/
|
|
17
|
+
var/
|
|
18
|
+
wheels/
|
|
19
|
+
*.egg-info/
|
|
20
|
+
.installed.cfg
|
|
21
|
+
*.egg
|
|
22
|
+
MANIFEST
|
|
23
|
+
|
|
24
|
+
# Test / coverage artifacts
|
|
25
|
+
.pytest_cache/
|
|
26
|
+
.hypothesis/
|
|
27
|
+
.coverage
|
|
28
|
+
.coverage.*
|
|
29
|
+
htmlcov/
|
|
30
|
+
.tox/
|
|
31
|
+
.nox/
|
|
32
|
+
coverage.xml
|
|
33
|
+
*.cover
|
|
34
|
+
|
|
35
|
+
# Type checkers / linters
|
|
36
|
+
.mypy_cache/
|
|
37
|
+
.ruff_cache/
|
|
38
|
+
|
|
39
|
+
# Virtual envs
|
|
40
|
+
.venv/
|
|
41
|
+
venv/
|
|
42
|
+
env/
|
|
43
|
+
|
|
44
|
+
# IDE
|
|
45
|
+
.vscode/
|
|
46
|
+
.idea/
|
|
47
|
+
*.swp
|
|
48
|
+
|
|
49
|
+
# OS
|
|
50
|
+
.DS_Store
|
|
51
|
+
Thumbs.db
|
|
52
|
+
|
|
53
|
+
# claimtrail stores generated locally (the project keeps its own under .claimtrail/)
|
|
54
|
+
.claimtrail/
|
|
55
|
+
*.sqlite
|
|
56
|
+
*.sqlite-journal
|
|
57
|
+
|
|
58
|
+
# Example run artifacts
|
|
59
|
+
examples/q-numbers/.claimtrail/
|
|
60
|
+
examples/*/*.tex.bak
|
|
61
|
+
|
|
62
|
+
# Local-only override notes
|
|
63
|
+
*.local.md
|
|
64
|
+
|
|
65
|
+
# LaTeX source and build artifacts for engine-overview.pdf; only the PDF is committed
|
|
66
|
+
engine-overview.tex
|
|
67
|
+
engine-overview.aux
|
|
68
|
+
engine-overview.log
|
|
69
|
+
engine-overview.out
|
|
70
|
+
engine-overview.synctex.gz
|
|
71
|
+
|
|
72
|
+
# Demo outputs, regenerated by run_demo.py
|
|
73
|
+
examples/lending-review/data/
|
|
74
|
+
examples/lending-review/report.md
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# claimtrail integration guide for paper authors
|
|
2
|
+
|
|
3
|
+
This guide shows how to attach claimtrail provenance to a LaTeX manuscript, so that
|
|
4
|
+
every numerical claim in the paper points to a recorded computation in the
|
|
5
|
+
claimtrail store and a reader can run `claimtrail verify <id>` to reproduce it.
|
|
6
|
+
|
|
7
|
+
## The claimtrail store
|
|
8
|
+
|
|
9
|
+
By default the store is the nearest ancestor `.claimtrail/` directory, falling back
|
|
10
|
+
to `./.claimtrail`. Override it with the `CLAIMTRAIL_HOME` environment variable or
|
|
11
|
+
`claimtrail.set_store_root(path)`.
|
|
12
|
+
|
|
13
|
+
Store contents:
|
|
14
|
+
|
|
15
|
+
- `.claimtrail/claimtrail.sqlite` - SQLite metadata (computations, tags, claims)
|
|
16
|
+
- `.claimtrail/payloads/{id[:2]}/{id}.json.gz` - one gzipped JSON per computation,
|
|
17
|
+
holding inputs, outputs, source code, and captured stdout/stderr
|
|
18
|
+
|
|
19
|
+
A single store can hold the computations behind several papers. Filter a
|
|
20
|
+
paper's claims with the `paper=` tag.
|
|
21
|
+
|
|
22
|
+
## Two ways a computation enters the store
|
|
23
|
+
|
|
24
|
+
### (a) Live: the `@tracked` decorator
|
|
25
|
+
|
|
26
|
+
For computations you are writing now:
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
import claimtrail
|
|
30
|
+
|
|
31
|
+
@claimtrail.tracked(tags={"paper": "your-paper", "experiment": "X.Y"})
|
|
32
|
+
def compute_thing(N):
|
|
33
|
+
...
|
|
34
|
+
return result
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Every call records a row keyed on
|
|
38
|
+
`blake2b(function_name | input_hash | code_sha)`. The same inputs and the same
|
|
39
|
+
code collapse to the same id, so repeats overwrite in place.
|
|
40
|
+
|
|
41
|
+
**If the function reads a file by path, declare it.** The `data_files`
|
|
42
|
+
parameter makes the file's *contents* part of the input hash, not just the path
|
|
43
|
+
string. Without it, two machines holding files under the same name with
|
|
44
|
+
different content silently collapse to one row:
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
@claimtrail.tracked(
|
|
48
|
+
tags={"paper": "your-paper"},
|
|
49
|
+
data_files=["csv_path"],
|
|
50
|
+
)
|
|
51
|
+
def scan_csv(csv_path, N):
|
|
52
|
+
...
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The decorator replaces `csv_path` with a `canonical_file(...)` descriptor
|
|
56
|
+
before hashing. The descriptor carries the file's blake2b digest, size, and
|
|
57
|
+
mtime; the digest is what feeds the input hash. The resulting row has a
|
|
58
|
+
non-NULL `canonical_data_hash` column, so `claimtrail lint` will not flag it as
|
|
59
|
+
NOHASH.
|
|
60
|
+
|
|
61
|
+
### (b) Retroactive: `register_external(...)`
|
|
62
|
+
|
|
63
|
+
For pre-existing computations whose output is already on disk:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
import claimtrail
|
|
67
|
+
|
|
68
|
+
claimtrail.register_external(
|
|
69
|
+
function_name="my_search_v1", # logical name; part of the id
|
|
70
|
+
inputs={"target": "x", "N": 2000}, # dict; hashed for the id
|
|
71
|
+
outputs={"result": 0}, # any JSON-serializable value
|
|
72
|
+
code_path="path/to/script.py", # recorded in the payload
|
|
73
|
+
code_sha="3c0d5d7f...", # part of the id
|
|
74
|
+
runtime_seconds=12.5, # optional
|
|
75
|
+
tags={"paper": "your-paper", "retroactive": True},
|
|
76
|
+
source_file="path/to/original/output.json",
|
|
77
|
+
notes="anything useful",
|
|
78
|
+
)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The id is again `blake2b(function_name | input_hash | code_sha)`. Re-running the
|
|
82
|
+
registration script is idempotent by design, so you can re-run it whenever you
|
|
83
|
+
add or change a record and the database stays clean.
|
|
84
|
+
|
|
85
|
+
## Claims
|
|
86
|
+
|
|
87
|
+
A claim is a one-line factual statement, optionally linked to a computation.
|
|
88
|
+
Claims are what end up in the paper as `\fact{...}` macros.
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
claimtrail.claim(
|
|
92
|
+
"No polynomial of bidegree at most (6, 50) annihilates [x]_q modulo q^2000.",
|
|
93
|
+
computation_id="<some_comp_id>",
|
|
94
|
+
claim_id="emptiness_x", # stable id; re-runs overwrite
|
|
95
|
+
tags={"paper": "your-paper"}, # paper tag gates this claim
|
|
96
|
+
)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### The paper-tag gate
|
|
100
|
+
|
|
101
|
+
When a claim carries `tags={"paper": "..."}`, claimtrail requires a non-NULL
|
|
102
|
+
`computation_id`. Calls without one raise `UnbackedPaperClaimError`, so a
|
|
103
|
+
paper-bound statement can never silently render as `\provid{None}` in the
|
|
104
|
+
exported LaTeX.
|
|
105
|
+
|
|
106
|
+
If you genuinely need to stage a claim before its computation lands (for
|
|
107
|
+
example, writing a draft from notes while a long scan is still running), pass
|
|
108
|
+
`allow_unbacked=True` and back-attach later:
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
# Stage now:
|
|
112
|
+
claimtrail.claim(
|
|
113
|
+
"A statement to be backed once the scan finishes.",
|
|
114
|
+
tags={"paper": "your-paper"},
|
|
115
|
+
allow_unbacked=True,
|
|
116
|
+
claim_id="staged_claim",
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# Back-attach after the computation registers:
|
|
120
|
+
claimtrail.claim(
|
|
121
|
+
"A statement to be backed once the scan finishes.",
|
|
122
|
+
tags={"paper": "your-paper"},
|
|
123
|
+
computation_id="<the_real_comp_id>",
|
|
124
|
+
claim_id="staged_claim", # same id; overwrites in place
|
|
125
|
+
)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`claimtrail lint` flags every unbacked paper claim, so a pre-flight catches anything
|
|
129
|
+
left staged.
|
|
130
|
+
|
|
131
|
+
### Three claim-id strategies
|
|
132
|
+
|
|
133
|
+
| Need | Use | Effect |
|
|
134
|
+
|---|---|---|
|
|
135
|
+
| One-off interactive claim | default | random hex id every call |
|
|
136
|
+
| Idempotent batch script | `deterministic_id=True` | id derived from `(text, comp_id, value_numeric)` |
|
|
137
|
+
| Stable claim that updates with new data | `claim_id="<your_key>"` | you control the id; overwrites in place |
|
|
138
|
+
|
|
139
|
+
Use `claim_id=...` for any claim whose text will evolve as you add
|
|
140
|
+
computations (for example a bound widening from `(6,50)` to `(7,50)`).
|
|
141
|
+
Re-running the registration script then updates the same row instead of leaving
|
|
142
|
+
stale claims.
|
|
143
|
+
|
|
144
|
+
## Tags
|
|
145
|
+
|
|
146
|
+
Every computation and most claims carry tags. Tags are how the paper-level
|
|
147
|
+
filter works (`claimtrail export-latex --tag paper=your-paper`). Useful conventions:
|
|
148
|
+
|
|
149
|
+
- `paper`: short slug for the paper a record contributes to.
|
|
150
|
+
- `phase`: lifecycle stage (`part-1`, `validation`, ...).
|
|
151
|
+
- `retroactive`: `True` for records added via `register_external`.
|
|
152
|
+
- `type`: cross-cutting kinds (`verification`, `negative_control`, ...).
|
|
153
|
+
|
|
154
|
+
Find by tag:
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
claimtrail.find(tags={"paper": "your-paper", "phase": "part-1"}, limit=500)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Generating `claims.tex`
|
|
161
|
+
|
|
162
|
+
The CLI exports every claim in the store as a `\fact{...}` macro with a
|
|
163
|
+
`\footnote{Provenance: \provid{<id>}}` attached. Filter by tag:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
python -m claimtrail.cli export-latex \
|
|
167
|
+
--tag paper=your-paper \
|
|
168
|
+
--output claims.tex
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
The exporter runs a `latexify` pass over every `$...$` math span, turning
|
|
172
|
+
polynomial strings like `X*q^10` into clean LaTeX (`X q^{10}`). It is automatic
|
|
173
|
+
and idempotent.
|
|
174
|
+
|
|
175
|
+
## Inlining into `main.tex`
|
|
176
|
+
|
|
177
|
+
Add to the preamble:
|
|
178
|
+
|
|
179
|
+
```latex
|
|
180
|
+
% Provenance macros required by claims.tex
|
|
181
|
+
\newcommand{\provid}[1]{\texttt{\small #1}}
|
|
182
|
+
\newcommand{\fact}[1]{#1} % or wrap in a box, theorem env, etc.
|
|
183
|
+
|
|
184
|
+
\input{claims.tex}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Then footnote each numerical statement with its claim id:
|
|
188
|
+
|
|
189
|
+
```latex
|
|
190
|
+
\begin{theorem}
|
|
191
|
+
... \footnote{Claim \provid{emptiness\_x}; reproducible via
|
|
192
|
+
\texttt{claimtrail verify <id>}.}
|
|
193
|
+
\end{theorem}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Stable claim ids (`emptiness_x`) read better in the source than the 32-char
|
|
197
|
+
blake2b digests, which is one reason to use them.
|
|
198
|
+
|
|
199
|
+
## Cross-checking with a second tool
|
|
200
|
+
|
|
201
|
+
For a result that rests on a single computer algebra system, run a second,
|
|
202
|
+
independent implementation and record both. If the two disagree, you want to
|
|
203
|
+
know before the number reaches the paper, not after. Record each side as its
|
|
204
|
+
own computation and tag them so a reviewer can see that both were checked.
|
|
205
|
+
|
|
206
|
+
## Verifying a recorded computation
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
claimtrail verify <comp_id>
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
This re-imports the original function from its recorded module, re-invokes it
|
|
213
|
+
with the recorded args and kwargs, and compares the output hash byte for byte.
|
|
214
|
+
Determinism is required: any uncontrolled randomness makes verify fail. The
|
|
215
|
+
recommended pattern is to seed inside the decorated function so the seed shows
|
|
216
|
+
up in the input hash.
|
|
217
|
+
|
|
218
|
+
`verify` is only meaningful for `@tracked` computations. Records added via
|
|
219
|
+
`register_external` are immutable records of work done elsewhere and cannot be
|
|
220
|
+
re-run by claimtrail itself.
|
|
221
|
+
|
|
222
|
+
## Common pitfalls
|
|
223
|
+
|
|
224
|
+
- **Non-stable inputs.** If the `inputs` dict varies between runs due to
|
|
225
|
+
insertion order or floating-point quirks, ids will differ. Use
|
|
226
|
+
deterministic, JSON-serializable types.
|
|
227
|
+
- **Claims pile up.** Without `claim_id` or `deterministic_id`, every
|
|
228
|
+
`claimtrail.claim(...)` call mints a new row, so re-running a script duplicates.
|
|
229
|
+
Pin claim ids in batch scripts.
|
|
230
|
+
- **Filename-only file inputs.** A tracked function that takes `csv_path` as a
|
|
231
|
+
plain string and reads it inside the body hashes only the path string. Two
|
|
232
|
+
files with the same name but different contents then collapse to one id.
|
|
233
|
+
Declare `data_files=["csv_path"]`, or wrap the call site in
|
|
234
|
+
`canonical_file(...)`. `claimtrail lint` flags these as NOHASH advisories.
|
|
235
|
+
- **Paper-tagged claim with no computation.** Calling `claimtrail.claim(...,
|
|
236
|
+
tags={"paper": "..."}, computation_id=None)` raises
|
|
237
|
+
`UnbackedPaperClaimError`. Stage with `allow_unbacked=True` and back-attach
|
|
238
|
+
before exporting LaTeX.
|
|
239
|
+
|
|
240
|
+
## Asking for help
|
|
241
|
+
|
|
242
|
+
If something in claimtrail is not doing what this guide claims, prefer reading the
|
|
243
|
+
source over guessing: the package is small. The public API surface in
|
|
244
|
+
`src/claimtrail/__init__.py` is the source of truth for what exists.
|
claimtrail-0.5.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Patrick Taylor
|
|
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.
|