claims-cli 0.6.0__py3-none-any.whl

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 (51) hide show
  1. claims/__init__.py +132 -0
  2. claims/__main__.py +5 -0
  3. claims/adapters/__init__.py +6 -0
  4. claims/adapters/array.py +212 -0
  5. claims/adapters/base.py +75 -0
  6. claims/adapters/cells.py +94 -0
  7. claims/adapters/columnar.py +188 -0
  8. claims/adapters/prose.py +261 -0
  9. claims/adapters/reads.py +47 -0
  10. claims/adapters/rows.py +116 -0
  11. claims/adapters/sheet.py +149 -0
  12. claims/adapters/sqlite.py +73 -0
  13. claims/adapters/table.py +135 -0
  14. claims/adapters/tree.py +163 -0
  15. claims/checks.py +511 -0
  16. claims/cli.py +1103 -0
  17. claims/core/__init__.py +9 -0
  18. claims/core/artifacts.py +121 -0
  19. claims/core/checks.py +132 -0
  20. claims/core/claims.py +100 -0
  21. claims/core/evidence.py +708 -0
  22. claims/core/manifest.py +349 -0
  23. claims/core/numbers.py +146 -0
  24. claims/core/outcomes.py +410 -0
  25. claims/core/report.py +503 -0
  26. claims/corpus.py +344 -0
  27. claims/exceptions.py +106 -0
  28. claims/export/__init__.py +23 -0
  29. claims/export/rocrate.py +787 -0
  30. claims/export/sarif.py +401 -0
  31. claims/manifest.py +59 -0
  32. claims/models.py +222 -0
  33. claims/numbers.py +453 -0
  34. claims/origins.py +303 -0
  35. claims/policy.py +369 -0
  36. claims/provenance.py +42 -0
  37. claims/record.py +1099 -0
  38. claims/regression.py +171 -0
  39. claims/renderers/__init__.py +10 -0
  40. claims/renderers/sarif.py +294 -0
  41. claims/reruns.py +111 -0
  42. claims/resolve.py +116 -0
  43. claims/stale.py +333 -0
  44. claims/starter.py +432 -0
  45. claims/toolchain.py +120 -0
  46. claims/verify.py +1371 -0
  47. claims/why.py +398 -0
  48. claims_cli-0.6.0.dist-info/METADATA +139 -0
  49. claims_cli-0.6.0.dist-info/RECORD +51 -0
  50. claims_cli-0.6.0.dist-info/WHEEL +4 -0
  51. claims_cli-0.6.0.dist-info/entry_points.txt +2 -0
claims/why.py ADDED
@@ -0,0 +1,398 @@
1
+ """For one claim: what the record establishes, and what it does not.
2
+
3
+ `repro verify` answers for every assertion and `repro stale` for every file. This puts the two
4
+ answers for one claim side by side, with the commands and files upstream of what it reads, and
5
+ then lists what nothing in the record covers: a file no command is recorded for, a command with
6
+ no re-run that reproduced, an assertion that was not checked.
7
+
8
+ Under each assertion it also puts what `claims.checks` recorded of it: what it declared and how
9
+ it came out at each `repro verify --record` that found it changed.
10
+
11
+ Nothing is evaluated here that those two do not evaluate. Each assertion goes through
12
+ `claims.verify.check_evidence`, each state comes from `claims.stale.stale`, and the chain is the
13
+ part of that report's graph `repro.regenerate.upstream` reaches from the files the claim reads.
14
+ Executes nothing and writes nothing.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import collections
20
+ import difflib
21
+ import pathlib
22
+
23
+ from claims.adapters.reads import reading_once
24
+ from claims.checks import Records, address, checked
25
+ from claims.exceptions import ReproError
26
+ from claims.models import (
27
+ AggregateEvidence,
28
+ AssertionAccount,
29
+ ChainNode,
30
+ Claim,
31
+ ClaimAccount,
32
+ Conversion,
33
+ CorrespondenceEvidence,
34
+ Decision,
35
+ Evidence,
36
+ FileRead,
37
+ Freshness,
38
+ Manifest,
39
+ NotEstablished,
40
+ NotEstablishedKind,
41
+ Outcome,
42
+ QuoteEvidence,
43
+ StaleCause,
44
+ TableCellEvidence,
45
+ Validity,
46
+ )
47
+ from claims.reruns import upstream
48
+ from claims.resolve import stored
49
+ from claims.stale import stale
50
+ from claims.verify import DEFAULT_BACKENDS, Backend, artifact_states, check_evidence
51
+
52
+ #: Ids named in an error. A manifest can declare thousands.
53
+ MAX_LISTED = 10
54
+
55
+ #: What the design does not check. An account ends on each that applies to it: the first where
56
+ #: a command is recorded, the second where an assertion has an address, the third where there
57
+ #: is an assertion.
58
+ UNDECLARED_INPUTS = (
59
+ "the inputs each command declares are the author's list; a file a command reads and does "
60
+ "not declare is invisible"
61
+ )
62
+ ADDRESS_IS_CHOSEN = (
63
+ "the address is the author's choice; nothing checks that it holds the quantity the "
64
+ "sentence means"
65
+ )
66
+ COMPARISON_ONLY = (
67
+ "a verified assertion says the declared value or passage is in the file, not that the "
68
+ "analysis is right"
69
+ )
70
+
71
+ #: What is said of a file no command is recorded for, by what its pin found.
72
+ _TAKEN_AS_GIVEN = {
73
+ Validity.AUTHORITATIVE: "its bytes are pinned and taken as given",
74
+ Validity.UNPINNED_ARTIFACT: "its bytes are not pinned and are taken as given",
75
+ Validity.BROKEN_PIN: "its bytes are not the pinned ones and are taken as given",
76
+ }
77
+
78
+ _NOT_CHECKED = {
79
+ Outcome.UNCHECKED: NotEstablishedKind.UNCHECKED,
80
+ Outcome.NOT_FOUND: NotEstablishedKind.NOT_FOUND,
81
+ Outcome.ERROR: NotEstablishedKind.ERROR,
82
+ }
83
+
84
+
85
+ class UnknownClaimError(ReproError):
86
+ """A claim was named that the manifest does not declare."""
87
+
88
+ def __init__(self, unknown: str, declared: tuple[str, ...]) -> None:
89
+ self.unknown = unknown
90
+ self.declared = declared
91
+ near = tuple(difflib.get_close_matches(unknown, declared, n=MAX_LISTED))
92
+ listed = near or declared[:MAX_LISTED]
93
+ more = len(declared) - len(listed)
94
+ super().__init__(
95
+ f"the manifest declares no claim {unknown!r}; "
96
+ + ("nearest: " if near else "declared: ")
97
+ + (", ".join(listed) or "none")
98
+ + (f", and {more} more" if more > 0 and not near else "")
99
+ )
100
+
101
+
102
+ #: One file an assertion reads: the artifact, the side it is, the address, the value stored
103
+ #: there, and the conversion declared for it with the value under it.
104
+ Held = tuple[str, str, str, str | None, Conversion | None, str | None]
105
+
106
+
107
+ def _held(evidence: Evidence, decision: Decision, paths: dict[str, pathlib.Path]) -> list[Held]:
108
+ """Each file an assertion reads, and what was found at the address in it."""
109
+ if isinstance(evidence, QuoteEvidence):
110
+ return [(evidence.artifact, "", "", None, None, None)]
111
+ if isinstance(evidence, CorrespondenceEvidence):
112
+ read = {side.name: side for side in decision.sides}
113
+ return [
114
+ (
115
+ side.artifact,
116
+ side.name,
117
+ address(side.locator),
118
+ read[side.name].extracted if side.name in read else None,
119
+ side.conversion,
120
+ read[side.name].converted if side.name in read else None,
121
+ )
122
+ for side in evidence.sides
123
+ ]
124
+ if isinstance(evidence, AggregateEvidence):
125
+ # No member was read where a file is absent or undeclared: the engine stops before
126
+ # the backend, and the decision lists none.
127
+ read = [(m.extracted, m.converted) for m in decision.members]
128
+ return [
129
+ (member.artifact, "", address(member.locator), stored, evidence.conversion, converted)
130
+ for member, (stored, converted) in zip(
131
+ evidence.members, read or [(None, None)] * evidence.count, strict=True
132
+ )
133
+ ]
134
+ if isinstance(evidence, TableCellEvidence) and not evidence.addresses_one_row:
135
+ # Neither `row` nor `where`, or both: there is no one address to show.
136
+ return [(evidence.artifact, "", "", None, None, None)]
137
+ path = paths.get(evidence.artifact)
138
+ return [
139
+ (
140
+ evidence.artifact,
141
+ "",
142
+ address(evidence.locator),
143
+ stored(evidence, path) if path is not None else None,
144
+ evidence.conversion,
145
+ decision.converted,
146
+ )
147
+ ]
148
+
149
+
150
+ def _not_established(
151
+ claim: Claim, assertions: tuple[AssertionAccount, ...], chain: tuple[ChainNode, ...]
152
+ ) -> tuple[NotEstablished, ...]:
153
+ """What the record does not cover for this claim, then what the design does not check."""
154
+ lines = []
155
+ for node in chain:
156
+ if node.regeneration is None and node.validity in _TAKEN_AS_GIVEN:
157
+ lines.append(
158
+ NotEstablished(
159
+ kind=NotEstablishedKind.NO_PRODUCER,
160
+ subject=node.path or node.artifact,
161
+ said=f"no command is recorded for it; {_TAKEN_AS_GIVEN[node.validity]}",
162
+ )
163
+ )
164
+ for node in chain:
165
+ if node.validity is Validity.ARTIFACT_ABSENT:
166
+ lines.append(
167
+ NotEstablished(
168
+ kind=NotEstablishedKind.ABSENT,
169
+ subject=node.path or node.artifact,
170
+ said="nothing at the declared path",
171
+ )
172
+ )
173
+ elif node.regeneration is not None and node.validity is Validity.BROKEN_PIN:
174
+ lines.append(
175
+ NotEstablished(
176
+ kind=NotEstablishedKind.BROKEN_PIN,
177
+ subject=node.path or node.artifact,
178
+ said="its bytes are not the pinned ones",
179
+ )
180
+ )
181
+ for node in chain:
182
+ record = node.regeneration
183
+ if record is None:
184
+ continue
185
+ # A record on a cycle is compared with no re-run, whether or not one is recorded.
186
+ if StaleCause.CYCLE in record.causes:
187
+ lines.append(
188
+ NotEstablished(
189
+ kind=NotEstablishedKind.CYCLE,
190
+ subject=record.id,
191
+ said="on a cycle of commands, so it is compared with no re-run",
192
+ )
193
+ )
194
+ elif record.rerun_at is None:
195
+ lines.append(
196
+ NotEstablished(
197
+ kind=NotEstablishedKind.NEVER_RERUN,
198
+ subject=record.id,
199
+ said="no re-run that reproduced is recorded; nothing in the record shows "
200
+ f"the command produces {node.path or node.artifact}",
201
+ )
202
+ )
203
+ if not claim.evidence:
204
+ lines.append(
205
+ NotEstablished(
206
+ kind=NotEstablishedKind.NO_EVIDENCE,
207
+ subject=claim.id,
208
+ said="the claim offers no evidence, so nothing was checked",
209
+ )
210
+ )
211
+ for assertion in assertions:
212
+ if assertion.outcome in _NOT_CHECKED:
213
+ reason = assertion.reason.value.replace("_", " ")
214
+ lines.append(
215
+ NotEstablished(
216
+ kind=_NOT_CHECKED[assertion.outcome],
217
+ subject=f"{assertion.kind} {assertion.name}".strip(),
218
+ said=f"{assertion.outcome.value.replace('_', ' ')}: {reason}"
219
+ + (f": {assertion.detail}" if assertion.detail else ""),
220
+ )
221
+ )
222
+ if any(node.regeneration is not None for node in chain):
223
+ lines.append(
224
+ NotEstablished(kind=NotEstablishedKind.UNDECLARED_INPUTS, said=UNDECLARED_INPUTS)
225
+ )
226
+ if any(read.address for assertion in assertions for read in assertion.reads):
227
+ lines.append(NotEstablished(kind=NotEstablishedKind.ADDRESS, said=ADDRESS_IS_CHOSEN))
228
+ if assertions:
229
+ lines.append(NotEstablished(kind=NotEstablishedKind.COMPARISON, said=COMPARISON_ONLY))
230
+ return tuple(lines)
231
+
232
+
233
+ def why(
234
+ manifest: Manifest, claim_id: str, backends: tuple[Backend, ...] = DEFAULT_BACKENDS
235
+ ) -> ClaimAccount:
236
+ """Evaluate one claim's assertions and say what each file it rests on is, against the
237
+ record of re-runs.
238
+
239
+ Executes nothing and writes nothing.
240
+ """
241
+ claim = next((c for c in manifest.claims if c.id == claim_id), None)
242
+ if claim is None:
243
+ raise UnknownClaimError(claim_id, tuple(c.id for c in manifest.claims))
244
+
245
+ report = stale(manifest)
246
+ states, paths = artifact_states(manifest)
247
+ registry: dict[str, Backend] = {b.kind: b for b in backends}
248
+ with reading_once():
249
+ decisions = [
250
+ check_evidence(claim, evidence, manifest, paths, states, registry)
251
+ for evidence in claim.evidence
252
+ ]
253
+ held = [
254
+ _held(evidence, decision, paths)
255
+ for evidence, decision in zip(claim.evidence, decisions, strict=True)
256
+ ]
257
+
258
+ def read(
259
+ artifact_id: str,
260
+ side: str,
261
+ where: str,
262
+ value: str | None,
263
+ conversion: Conversion | None,
264
+ converted: str | None,
265
+ ) -> FileRead:
266
+ declared, state = manifest.artifact(artifact_id), states.get(artifact_id)
267
+ return FileRead(
268
+ artifact=artifact_id,
269
+ path=declared.path.as_posix() if declared else "",
270
+ side=side,
271
+ validity=state.validity if state else None,
272
+ pinned=state.expected if state else None,
273
+ on_disk=state.actual if state else None,
274
+ address=where,
275
+ stored=value,
276
+ conversion=conversion,
277
+ converted=converted,
278
+ )
279
+
280
+ # What the record of checks holds for each assertion, and the assertion as it stands where
281
+ # the record's last entry is something else.
282
+ root = manifest.path.parent if manifest.path else pathlib.Path.cwd()
283
+ kept = Records(root)
284
+ past = kept.history
285
+ present = checked(claim, tuple(decisions))
286
+ # The last state of each assertion of a claim that is no longer declared, where it was
287
+ # not verified and had an address.
288
+ declared = {each.id for each in manifest.claims}
289
+ gone = [
290
+ states[-1]
291
+ for identity, states in past.items()
292
+ if identity[0] not in declared
293
+ and states[-1].outcome is not Outcome.VERIFIED
294
+ and states[-1].address
295
+ ]
296
+
297
+ freshness = next(node for node in report.claims if node.id == claim.id).assertions
298
+ assertions = tuple(
299
+ AssertionAccount(
300
+ kind=evidence.kind,
301
+ name=getattr(evidence, "name", ""),
302
+ printed=evidence.text
303
+ if isinstance(evidence, QuoteEvidence)
304
+ else getattr(evidence, "reported", None),
305
+ reads=tuple(read(*found) for found in files),
306
+ places=getattr(evidence, "places", None),
307
+ tie=decision.tie,
308
+ statistic=evidence.statistic if isinstance(evidence, AggregateEvidence) else None,
309
+ computed=decision.computed,
310
+ outcome=decision.outcome,
311
+ reason=decision.reason,
312
+ detail=decision.detail,
313
+ state=node.state,
314
+ warnings=decision.warnings,
315
+ history=recorded,
316
+ now=None if not recorded or now.same_as(recorded[-1]) else now,
317
+ formerly=tuple(
318
+ last
319
+ for last in gone
320
+ if last.address == now.address and set(last.digests) == set(now.digests)
321
+ ),
322
+ )
323
+ for evidence, decision, files, node, now in zip(
324
+ claim.evidence, decisions, held, freshness, present, strict=True
325
+ )
326
+ for recorded in (past.get(now.identity, ()),)
327
+ )
328
+
329
+ # The records upstream of each file the claim reads, by `upstream`, and so the files: each
330
+ # record's output at the record's distance and its inputs one step further.
331
+ records = {record.id: record for record in manifest.regenerations}
332
+ producers = {record.output.artifact: record for record in manifest.regenerations}
333
+ steps: dict[str, int] = {}
334
+ for name in dict.fromkeys(name for evidence in claim.evidence for name in evidence.artifacts):
335
+ steps[name] = 0
336
+ producer = producers.get(name)
337
+ if producer is None:
338
+ continue
339
+ for record_id, distance in {**upstream(producer, producers), producer.id: 0}.items():
340
+ record = records[record_id]
341
+ for artifact_id, at in (
342
+ (record.output.artifact, distance),
343
+ *((wanted.artifact, distance + 1) for wanted in record.inputs),
344
+ ):
345
+ steps[artifact_id] = min(at, steps.get(artifact_id, at))
346
+
347
+ nodes = {node.artifact: node for node in report.regenerations}
348
+ chain = tuple(
349
+ ChainNode(
350
+ artifact=name,
351
+ path=declared.path.as_posix() if declared else "",
352
+ steps=steps[name],
353
+ validity=states[name].validity if name in states else None,
354
+ state=nodes[name].state if name in nodes else Freshness.NO_PRODUCER,
355
+ command=producers[name].command if name in producers else (),
356
+ volatile=producers[name].volatile if name in producers else (),
357
+ regeneration=nodes.get(name),
358
+ )
359
+ for name in sorted(steps, key=steps.__getitem__)
360
+ for declared in (manifest.artifact(name),)
361
+ )
362
+ on_chain = {node.regeneration.id for node in chain if node.regeneration}
363
+ by_outcome = collections.Counter(assertion.outcome for assertion in assertions)
364
+ by_state = collections.Counter(assertion.state for assertion in assertions)
365
+
366
+ return ClaimAccount(
367
+ project=report.project,
368
+ manifest_digest=report.manifest_digest,
369
+ claim_id=claim.id,
370
+ text=claim.text,
371
+ where=claim.where,
372
+ assertions=assertions,
373
+ chain=chain,
374
+ edges=tuple(
375
+ edge
376
+ for edge in report.edges
377
+ if (edge.kind == "reads" and edge.source == claim.id)
378
+ or (edge.kind == "produced_by" and edge.target in on_chain)
379
+ or (edge.kind == "consumes" and edge.source in on_chain)
380
+ ),
381
+ findings=tuple(found for found in report.findings if found.regeneration in on_chain),
382
+ not_established=_not_established(claim, assertions, chain),
383
+ records=kept.states,
384
+ set_aside=kept.set_aside,
385
+ no_local_record=kept.no_local_record,
386
+ outcomes={outcome: by_outcome[outcome] for outcome in Outcome if by_outcome[outcome]},
387
+ states={state: by_state[state] for state in Freshness if by_state[state]},
388
+ )
389
+
390
+
391
+ __all__ = [
392
+ "ADDRESS_IS_CHOSEN",
393
+ "COMPARISON_ONLY",
394
+ "UNDECLARED_INPUTS",
395
+ "UnknownClaimError",
396
+ "address",
397
+ "why",
398
+ ]
@@ -0,0 +1,139 @@
1
+ Metadata-Version: 2.5
2
+ Name: claims-cli
3
+ Version: 0.6.0
4
+ Summary: Check what a manuscript states against the files it names: numbers, table cells and quotations
5
+ Project-URL: Changelog, https://github.com/elliottower/reproducible-science/blob/main/CHANGELOG.md
6
+ Project-URL: Homepage, https://github.com/elliottower/reproducible-science
7
+ Project-URL: Issues, https://github.com/elliottower/reproducible-science/issues
8
+ Project-URL: Source, https://github.com/elliottower/reproducible-science/tree/main/packages/claims
9
+ Author-email: Elliot Tower <elliot@elliottower.ai>
10
+ License-Expression: MIT
11
+ Keywords: citations,manuscript,open science,provenance,reproducibility
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Topic :: Scientific/Engineering
21
+ Requires-Python: >=3.11
22
+ Requires-Dist: citations<0.7,>=0.6
23
+ Requires-Dist: provenance-core<0.7,>=0.6
24
+ Requires-Dist: pydantic>=2.9
25
+ Requires-Dist: pyyaml>=6.0.2
26
+ Provides-Extra: arrays
27
+ Requires-Dist: numpy>=2.2.6; extra == 'arrays'
28
+ Provides-Extra: hdf5
29
+ Requires-Dist: h5py>=3.12; extra == 'hdf5'
30
+ Provides-Extra: netcdf
31
+ Requires-Dist: netcdf4>=1.7.2; extra == 'netcdf'
32
+ Provides-Extra: parquet
33
+ Requires-Dist: pyarrow>=18; extra == 'parquet'
34
+ Provides-Extra: rds
35
+ Requires-Dist: rdata>=1; extra == 'rds'
36
+ Provides-Extra: sheets
37
+ Requires-Dist: openpyxl>=3.1; extra == 'sheets'
38
+ Requires-Dist: xlrd>=2.0.1; extra == 'sheets'
39
+ Provides-Extra: stata-spss
40
+ Requires-Dist: pyreadstat>=1.3.6; extra == 'stata-spss'
41
+ Description-Content-Type: text/markdown
42
+
43
+ # claims-cli
44
+
45
+ [![pypi](https://img.shields.io/pypi/v/claims-cli)](https://pypi.org/project/claims-cli/)
46
+ [![python](https://img.shields.io/pypi/pyversions/claims-cli)](https://pypi.org/project/claims-cli/)
47
+ [![license](https://img.shields.io/pypi/l/claims-cli)](https://github.com/elliottower/reproducible-science/blob/main/LICENSE)
48
+ [![docs](https://img.shields.io/badge/docs-live-blue)](https://reproducible.science/)
49
+
50
+ Check what a manuscript states against the files it names.
51
+
52
+ The part of [reproducible-science](https://github.com/elliottower/reproducible-science) that reads a manifest of claims and checks each one: a number against a results file, a table cell against a table, a quotation against its source. It installs alone, and it runs no command of yours.
53
+
54
+ ## Install
55
+
56
+ ```bash
57
+ pip install claims-cli
58
+ ```
59
+
60
+ This installs `claims` and one dependency of ours, [`citations`](https://pypi.org/project/citations/), which reads the documents a quotation is checked in. The optional readers are extras, each named for what it reads:
61
+
62
+ ```bash
63
+ pip install "claims-cli[parquet,sheets]"
64
+ ```
65
+
66
+ `arrays`, `hdf5`, `netcdf`, `parquet`, `rds`, `sheets` and `stata-spss`. Without the extra a format needs, a check of that format is `unchecked` and names the extra to install.
67
+
68
+ ## What it reads
69
+
70
+ `repro.yaml`, a manifest that pins a project's files by sha256 and declares what the manuscript claims about them:
71
+
72
+ ```yaml
73
+ artifacts:
74
+ - id: results
75
+ path: analysis/results.json
76
+ digest: {algorithm: sha256, value: 9f635f9a...}
77
+
78
+ claims:
79
+ - id: primary
80
+ text: The treatment reduced the primary outcome
81
+ evidence:
82
+ - kind: metric
83
+ artifact: results
84
+ name: p_value
85
+ pointer: /primary/p
86
+ reported: "0.031"
87
+ ```
88
+
89
+ The file has the same name and the same format whichever command reads it. The [specification](https://reproducible.science/reference/spec/) defines every kind of evidence and every outcome.
90
+
91
+ ## The commands
92
+
93
+ ```bash
94
+ claims manifest init
95
+ ```
96
+
97
+ Writes a starter `repro.yaml` at the top of the project: each file it finds or is given, pinned, with example claims that verify as written. It never overwrites one.
98
+
99
+ ```bash
100
+ claims verify
101
+ ```
102
+
103
+ Checks every declared assertion against the file it names and prints one line per assertion: `ok`, `MISS` where the file holds another value, `GONE` where the address does not resolve, `--` where the check could not run. It exits 1 where the policy fails, and `--format json` and `--format sarif` print the report as a document.
104
+
105
+ ```bash
106
+ claims stale
107
+ ```
108
+
109
+ Says which claims read a file that is no longer what a recorded re-run produced. It reads `.repro/reproductions.jsonl` and runs nothing.
110
+
111
+ ```bash
112
+ claims why primary
113
+ ```
114
+
115
+ For one claim: each assertion as `claims verify` evaluates it now, each file the claim rests on and the command recorded for it, and then what the record does not establish.
116
+
117
+ ```bash
118
+ claims numbers
119
+ ```
120
+
121
+ Counts every number the manuscript's text prints and says which a claim accounts for.
122
+
123
+ ```bash
124
+ claims export record
125
+ ```
126
+
127
+ Writes one record per checked assertion, for another program to read. `sarif` and `ro-crate` write the same records in those formats.
128
+
129
+ ```bash
130
+ claims origins --repo https://github.com/owner/study=../study
131
+ ```
132
+
133
+ For each artifact copied from another repository, which the manifest says with `origin: {repository, commit, path}`, compares the pinned digest with the bytes of that path at that commit: `same`, `differs`, `absent`, `unknown commit` or `not checked`, each with the reason. A clone given with `--repo` is read with `git cat-file`, so committed bytes are compared and never a working tree. With `--fetch`, a GitHub repository no clone is given for is read from `raw.githubusercontent.com`; without it nothing touches the network. `claims verify` never reads an origin.
134
+
135
+ ## With `repro`
136
+
137
+ [`reproducible-science`](https://pypi.org/project/reproducible-science/) depends on this package and adds the commands that run things: `repro reproduce`, which runs each declared command again and writes the record `claims stale` reads, `repro check` and `repro audit`, which drive `prereg`, `results` and `citations`, and `repro ci`. Under `repro`, every command above has the same name, does the same thing and prints the same output: `repro verify` and `claims verify` are one function. A project that uses `repro` has no reason to install this by itself. It is separate so that checking a manuscript's claims does not require installing something that executes commands a manifest names.
138
+
139
+ MIT licensed.
@@ -0,0 +1,51 @@
1
+ claims/__init__.py,sha256=qiTFpxrEffnmtOD3_tEqjih6HFfUW-o640bhaPIT7Bg,3410
2
+ claims/__main__.py,sha256=2G4pjnNFGBK8XPrjiUkGpJHULOk-NzoQrDTdT8xnRcA,115
3
+ claims/checks.py,sha256=MuaPkL3BQvuVb05nQ5CAfGBWMoLbJVfnpWtVzIV50FM,21458
4
+ claims/cli.py,sha256=jnG7jGyfKAXTM84WfPnB9msRtGznCl5VUnhJnulZKn4,49683
5
+ claims/corpus.py,sha256=fjdB-Csd92R-UgMa4RRAHM72JeO3SAXxLA0GaDo_Xls,12538
6
+ claims/exceptions.py,sha256=Hx05bLyk9Xtx9jClieFtrMuG9OSDm5FyFIW0Y_RWz_A,3835
7
+ claims/manifest.py,sha256=QcHVysmvs3jdFfMgDPpPq96jf00dxyW5ym5-a57Fz6w,2310
8
+ claims/models.py,sha256=PSIIIhhiWXDRai5gm9-nmZPx3yXRlZu8j20nDN9W8qs,5840
9
+ claims/numbers.py,sha256=w0yxK_UJoTg8prqv-dNk_wfLZjOUPekDc1o4v350gVE,19295
10
+ claims/origins.py,sha256=eeJBwUz7y27yxPJ13CviQgGL2FOQWM6gGbFFl3Cxfrw,11220
11
+ claims/policy.py,sha256=sWGlpveOA4eCPAGuotaCbw8PadZQ1dvAU4KM1bt6oMA,14540
12
+ claims/provenance.py,sha256=7AXuxhtQaxfAM_Gok-OPruTu0O9jWLOBsn_xf7MIOmk,1590
13
+ claims/record.py,sha256=Z337-37F--HhMVo7YOZ6rJqYsp3QA_Ggv1sZS7EBvwU,43811
14
+ claims/regression.py,sha256=2E0_GnIs_PeacN0UU6zuPtz71i08LDItz5UYKjxGoYg,5945
15
+ claims/reruns.py,sha256=a_1mEux5TLg6T9Gelseb65aoV5gi9CKS9nHXUPsttCc,4563
16
+ claims/resolve.py,sha256=wuGeocIE2NB6Z9FhXnF3DtAGNkjKR-O-9VI-wkGTdBk,4628
17
+ claims/stale.py,sha256=AppPzUyqD6nrx_58DDoWYSFORL50MK0dgViiv5nSD9Y,13142
18
+ claims/starter.py,sha256=8lV9zSG3kfSpa0AGtUtPGPYCcjOYYVnLMdYUYWMLauI,18380
19
+ claims/toolchain.py,sha256=QmzalHL4jHqwXkgQQRTZapfrxuXrkRsl9FWuMlg5V08,4747
20
+ claims/verify.py,sha256=tqvMEg3Fawigp4IN92BJUAwIGDkKjnLIObE_Op6qSRE,59721
21
+ claims/why.py,sha256=YxC8s9yw0eL5FCpTPQ0_CEYIIc4vyBqJxvP9MSyw9ak,15860
22
+ claims/adapters/__init__.py,sha256=Y-oo_AD11xdK4CMEbZbANMWiO_VgHvcnSiHMac5Ywq0,251
23
+ claims/adapters/array.py,sha256=ggeJd3NuNCpWPISiz13QwkMAhN76j5vJ8Fl695zEE9k,9280
24
+ claims/adapters/base.py,sha256=DvoarzVVQwAKFXylczPJqdiqX6HnQj-0Lm9lh_tBgyk,2978
25
+ claims/adapters/cells.py,sha256=XcmOhVsRoRAdMNqKmIvS75Imf5cDKEUNnGvm5ePsE5c,3858
26
+ claims/adapters/columnar.py,sha256=_4PYDb2SQiXP4Lo1I4brvoINorJn5y-VWxs02XkaRsY,8167
27
+ claims/adapters/prose.py,sha256=-XatJ-oz6QatX0_-j_3DqTbzZ4ZWvFqQUR0nk4OUQno,12436
28
+ claims/adapters/reads.py,sha256=b1V9nhRqT7dtmtayw95I_qAgbQZWJS5OUZAwgUUpDXw,1696
29
+ claims/adapters/rows.py,sha256=HhQg2yQHwFYnj8_7u1jRMBYgTvocMmVCIL5IcMz4ZQU,5116
30
+ claims/adapters/sheet.py,sha256=VUEibAXEDvm-t_EZIngaUuQ6UuS47e950ZasKAQOM74,6581
31
+ claims/adapters/sqlite.py,sha256=b7xEYLMxSAIeIOMPEIm7wIaX6gRJfAH0SUjltg6nwTM,2997
32
+ claims/adapters/table.py,sha256=B603jtqpJO_HpPFeKcohbX0wbswDZb4tW9PvOW5W5hQ,5859
33
+ claims/adapters/tree.py,sha256=zRaPi3vY35jYz4WqDfoI8VlIgVnR6KXKwBvi4euu5CM,6247
34
+ claims/core/__init__.py,sha256=C3QIc8B_TH4eDcU8g7VaOJdpGlBmu0wnAh6qJFKiLFA,477
35
+ claims/core/artifacts.py,sha256=SNtxep20cR5vkEUoCF8KAW15akA-LcwbmbTqF-kryNs,4134
36
+ claims/core/checks.py,sha256=cnoRJmv-WB-RbvcbzUM1AX0RgY6UqAdJPgXMyZDzxYs,5418
37
+ claims/core/claims.py,sha256=Ag6aXut6bq4piMcOjVXowlt9Ibs4qPvwJOmrohBn6Rc,3754
38
+ claims/core/evidence.py,sha256=Tp16mtUmO2SrE-D8YKn374RMuDDrCL4MJa-ZhJ9gpqw,27675
39
+ claims/core/manifest.py,sha256=4h5TqeuguFK08s5HCW2dYauSDcxbmJpUpFVjj9dvWzA,14267
40
+ claims/core/numbers.py,sha256=EyMn4Sz8_LUnex_oszxCsy7m6t2WX6EODQT2My1vUPY,6115
41
+ claims/core/outcomes.py,sha256=GwgCCmL31FZbz_W9Ka8mFHUUHpWUZatJPuCTI31GHd8,18024
42
+ claims/core/report.py,sha256=8LFKthC8yfemxLyEd3WTHoCoY7CpSul3zf8rIbUIXHM,18258
43
+ claims/export/__init__.py,sha256=FUJ_4x22azFR_CTLZ4aXT1ezo4znFwUY_Eo7JgqME7A,670
44
+ claims/export/rocrate.py,sha256=RrPePcaf1UyGwwemT_1D97GR2_REygMI2CyJrUnntWE,32953
45
+ claims/export/sarif.py,sha256=EuP8l3YZZm0in2OVvydYLXX6vHO1Lc66ypNnO54kj5s,15628
46
+ claims/renderers/__init__.py,sha256=KMqGyJNw9myWpkEREoKRFuy-bUJvNRq7AhI9lyKRji0,345
47
+ claims/renderers/sarif.py,sha256=yz30yg61O5zt6_xOogXcaXjXmCnQOlAk-Wuy3ijH8xU,12928
48
+ claims_cli-0.6.0.dist-info/METADATA,sha256=fTN3iaV9RQspSTP3ASX-gFVg7sk4yf9n47NpKEwNckA,6372
49
+ claims_cli-0.6.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
50
+ claims_cli-0.6.0.dist-info/entry_points.txt,sha256=qPRfNHlVZYCAD9YWmd03hgV6iYh8YfhM2xLJx7qOM5M,43
51
+ claims_cli-0.6.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ claims = claims.cli:main