runprov 0.1.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 (55) hide show
  1. runprov-0.1.0/.gitignore +46 -0
  2. runprov-0.1.0/.pre-commit-config.yaml +26 -0
  3. runprov-0.1.0/.ruff.toml +79 -0
  4. runprov-0.1.0/CHANGELOG.md +732 -0
  5. runprov-0.1.0/CITATION.cff +95 -0
  6. runprov-0.1.0/CODE_OF_CONDUCT.md +57 -0
  7. runprov-0.1.0/CONTRIBUTING.md +179 -0
  8. runprov-0.1.0/DCO.txt +34 -0
  9. runprov-0.1.0/GETTING-STARTED.md +520 -0
  10. runprov-0.1.0/LICENSE +28 -0
  11. runprov-0.1.0/PKG-INFO +142 -0
  12. runprov-0.1.0/README-pypi.md +105 -0
  13. runprov-0.1.0/README.md +2276 -0
  14. runprov-0.1.0/SECURITY.md +68 -0
  15. runprov-0.1.0/WHY.md +552 -0
  16. runprov-0.1.0/ci.py +496 -0
  17. runprov-0.1.0/docs/adr/0001-provenance-layout-and-overrides.md +114 -0
  18. runprov-0.1.0/docs/adr/0002-detecting-unregistered-reads.md +141 -0
  19. runprov-0.1.0/docs/adr/0003-a-module-all-ratifies-it-does-not-decide.md +92 -0
  20. runprov-0.1.0/docs/adr/0004-a-pin-states-what-it-covers.md +130 -0
  21. runprov-0.1.0/docs/adr/0005-a-record-is-written-whole-or-not-at-all.md +123 -0
  22. runprov-0.1.0/docs/adr/0006-an-artifact-answers-for-itself.md +93 -0
  23. runprov-0.1.0/docs/adr/0007-the-gate-passes-the-exit-code-through.md +75 -0
  24. runprov-0.1.0/docs/adr/0008-capture-observes-what-declaration-cannot-reach.md +75 -0
  25. runprov-0.1.0/docs/adr/0009-export-is-a-derived-view-in-two-vocabularies.md +76 -0
  26. runprov-0.1.0/docs/adr/0010-a-record-states-what-it-was-able-to-observe.md +155 -0
  27. runprov-0.1.0/docs/adr/README.md +39 -0
  28. runprov-0.1.0/docs/public-surface.txt +82 -0
  29. runprov-0.1.0/examples/.gitignore +2 -0
  30. runprov-0.1.0/examples/data/measurements.tsv +5 -0
  31. runprov-0.1.0/examples/format_compatibility.py +1117 -0
  32. runprov-0.1.0/examples/summarise.py +91 -0
  33. runprov-0.1.0/pyproject.toml +226 -0
  34. runprov-0.1.0/runprov/__init__.py +189 -0
  35. runprov-0.1.0/runprov/__main__.py +1816 -0
  36. runprov-0.1.0/runprov/_atomic.py +180 -0
  37. runprov-0.1.0/runprov/_report.py +126 -0
  38. runprov-0.1.0/runprov/environment.py +409 -0
  39. runprov-0.1.0/runprov/export.py +258 -0
  40. runprov-0.1.0/runprov/hashing.py +639 -0
  41. runprov-0.1.0/runprov/project.py +703 -0
  42. runprov-0.1.0/runprov/prune.py +277 -0
  43. runprov-0.1.0/runprov/py.typed +0 -0
  44. runprov-0.1.0/runprov/run.py +3309 -0
  45. runprov-0.1.0/runprov/show.py +1089 -0
  46. runprov-0.1.0/runprov/sinks.py +309 -0
  47. runprov-0.1.0/runprov/terminal.py +512 -0
  48. runprov-0.1.0/runprov/verify.py +742 -0
  49. runprov-0.1.0/runprov/watch.py +277 -0
  50. runprov-0.1.0/tests/fixtures/README.md +59 -0
  51. runprov-0.1.0/tests/fixtures/artifact_v1.tsv +8 -0
  52. runprov-0.1.0/tests/fixtures/history_v1.jsonl +2 -0
  53. runprov-0.1.0/tests/fixtures/history_v2_then_v1.jsonl +2 -0
  54. runprov-0.1.0/tests/test_runprov.py +19813 -0
  55. runprov-0.1.0/tools/torture.py +558 -0
@@ -0,0 +1,46 @@
1
+ # A provenance tool whose own repository tracks a stale coverage database is a poor
2
+ # advertisement. `.coverage` was committed until 2026-08-11.
3
+ __pycache__/
4
+ *.py[cod]
5
+ .coverage
6
+ .coverage.*
7
+ .pytest_cache/
8
+ .mypy_cache/
9
+ .ruff_cache/
10
+ dist/
11
+ build/
12
+ *.egg-info/
13
+ .venv/
14
+
15
+ # Written by running `examples/summarise.py` from the repository. The example configures
16
+ # the root as this repository, so its history lands here — which is the correct behaviour
17
+ # and not something to commit.
18
+ /provenance/
19
+ examples/results/
20
+
21
+ # THE TWO INTERNAL DRAFTS, untracked 2026-09-08 when the repository went public.
22
+ #
23
+ # L-08 had already kept `PUBLISHING.md` and `LICENSING.md` out of the sdist and off the
24
+ # README, on the grounds that they address one person and keep superseded reasoning on
25
+ # purpose. A private repository made that enough. A public one does not: below their
26
+ # "SUPERSEDED IN PART" banners they still recommend MIT with the CHU as holder, which is
27
+ # neither the licence nor the rights holder the source headers assert — and the DRCI has
28
+ # not yet answered which is correct. A reader following the repository link from the letter
29
+ # that asks that question would find the project appearing to answer it two ways.
30
+ #
31
+ # They stay on disk because the reasoning in them is worth having; they are simply not the
32
+ # project's public position. Untracking is not deleting, and it does not remove them from
33
+ # history — that would need a rewrite.
34
+ /LICENSING.md
35
+ /PUBLISHING.md
36
+
37
+ # AGENT WORKING STATE. `.claude/` is a tool's scratch directory, and it was NOT ignored — it
38
+ # happens to be empty, so nothing has leaked, but a `git add -A` would have committed whatever
39
+ # it contained into a public repository. That is the shape of the incident this file's first
40
+ # rule records: a swap file committed by the initial `git add -A`, holding text that had been
41
+ # written and deliberately cut.
42
+ #
43
+ # Worse here than a swap file, because the repository is public and the directory can hold
44
+ # session transcripts, worktrees and prompts. Nothing belongs in the project's history that
45
+ # nobody chose to put there.
46
+ /.claude/
@@ -0,0 +1,26 @@
1
+ # Same checks as CI, run locally. `pre-commit install` once, then a failing push is
2
+ # something you find in a second rather than in a queue.
3
+ repos:
4
+ # PINNED TO THE VERSION THE GATE ENFORCES, and it has to stay that way. This said v0.6.9
5
+ # against a gate on 0.16.2 -- ten minor versions apart -- and with `--fix` that is worse
6
+ # than a version skew: the hook REWRITES code to satisfy an old ruff, then CI rejects it
7
+ # with the new one, so the tool that exists to catch a failure early creates one. There is
8
+ # a test asserting these two agree.
9
+ - repo: https://github.com/astral-sh/ruff-pre-commit
10
+ rev: v0.16.2
11
+ hooks:
12
+ - id: ruff
13
+ args: [--fix]
14
+ - id: ruff-format
15
+ - repo: https://github.com/pre-commit/pre-commit-hooks
16
+ rev: v4.6.0
17
+ hooks:
18
+ - id: end-of-file-fixer
19
+ - id: trailing-whitespace
20
+ - id: check-yaml
21
+ - id: check-toml
22
+ - id: check-added-large-files
23
+ # This package has no data files. Anything large is a mistake, and a provenance
24
+ # tool that ships someone's parquet by accident would be a poor advertisement.
25
+ args: [--maxkb=200]
26
+ - id: check-merge-conflict
@@ -0,0 +1,79 @@
1
+ # Ruff config for the runprov package, kept beside the code so it travels with it into the
2
+ # standalone repository. Ruff replaces black + isort + flake8: `ruff format` is
3
+ # black-compatible byte for byte, and the `I` rules ARE isort.
4
+ #
5
+ # target-version is py310, NOT the repository's py311. Without it, UP017 rewrites
6
+ # `dt.timezone.utc` to `dt.UTC` — an alias added in 3.11 — and the package silently stops
7
+ # importing on the oldest interpreter it claims to support. A linter that breaks your
8
+ # declared support floor is worse than no linter.
9
+ line-length = 100
10
+ target-version = "py310"
11
+
12
+ [lint]
13
+ select = ["E", "F", "I", "N", "UP", "ANN", "S", "B", "C4", "PT", "RUF"]
14
+
15
+ # RUF003 flags an "ambiguous" EN DASH in a comment and suggests a hyphen. The comment is
16
+ # the copyright notice, and the en dash is part of "Assistance Publique – Hôpitaux de
17
+ # Paris" -- the institution's registered name. A linter does not get to respell a legal
18
+ # entity, and a copyright line that names the holder inexactly is worse than a lint
19
+ # warning. Ignored deliberately, everywhere, for that reason.
20
+ ignore = ["RUF003"]
21
+
22
+ [lint.per-file-ignores]
23
+ # project.py holds the ONE subprocess call in the package: `git`, resolved from PATH.
24
+ # S603 the argv is a literal program name plus the caller's own arguments, no shell.
25
+ # S607 `git` is deliberately taken from PATH. Pinning an absolute path would break
26
+ # every environment whose git is not where this machine's is, which is most.
27
+ # Scoped to the file rather than inline because `ruff format` moves which physical line a
28
+ # multi-line call reports on, and an inline noqa silently stops applying when it does.
29
+ "project.py" = ["S603", "S607"]
30
+ # `"__main__.py" = ["ANN401"]` used to sit here and suppressed NOTHING: that file has
31
+ # no `Any` annotation, and its stated reason ("main returns an int exit code and its
32
+ # locals are obvious") describes ANN201/ANN001, a different rule family. A per-file
33
+ # ignore that ignores nothing is the ADR-015 shape inside the linter's own config --
34
+ # a rule declared and not in effect. Removed rather than corrected: there was nothing
35
+ # to correct.
36
+
37
+ # Tests are not library code and linting them as if they were produces 198 findings that
38
+ # all say the same thing. What is switched off and why:
39
+ # S101 `assert` IS the test. Flagging it is the rule misapplied.
40
+ # ANN a test's arguments are fixtures and its return is None; annotating them adds
41
+ # nothing a reader did not already know.
42
+ # S108 /tmp paths in fixtures are the point.
43
+ # PT011 pytest.raises(Exception) is deliberate where the type IS the assertion.
44
+ # PT018 a compound assert is often the clearest statement of one fact.
45
+ # PT012 the crash tests need a `with Run(...)` INSIDE pytest.raises; that nesting is
46
+ # the behaviour under test, not sloppiness.
47
+ # RUF012 test doubles declare class attributes to imitate a real object's shape. The
48
+ # rule guards against shared mutable state across instances; a stub built and
49
+ # discarded inside one test has no instances to share anything with.
50
+ # S603/S607 the tests shell out to `git` to build a real repository to detect. That is
51
+ # the subject under test, not an injection risk.
52
+ # ci.py exists to run other tools; S603 is the rule describing its entire job. Scoped to
53
+ # the file, with the reason here, for the same reason as project.py: an inline directive
54
+ # detaches the next time the formatter moves the line it sits on.
55
+ # `run.py` and `__main__.py` each execute a program the CALLER named: `run.tool("samtools")`
56
+ # asks a binary for its version, and `runprov exec -- <cmd>` exists to run the command in
57
+ # front of it. S603 describes exactly what those two do on purpose, and there is no
58
+ # untrusted input to check -- the argv came from the person typing it. Scoped to the files
59
+ # rather than inline, for the reason given above project.py: `ruff format` moves which
60
+ # physical line a multi-line call reports on, and a detached noqa is a coincidence.
61
+ "run.py" = ["S603", "S607"]
62
+ "__main__.py" = ["S603"]
63
+ "ci.py" = ["S603"]
64
+
65
+ # `examples/` is teaching code that a reader copies, and it is linted as such. What is
66
+ # switched off and why:
67
+ # ANN an example annotated to library standards reads like library code, which is
68
+ # exactly the wrong signal for a file whose job is to show the shortest honest
69
+ # version of a call. `summarise.py` keeps its annotations; the format matrix is a
70
+ # table of lambdas where they would be noise.
71
+ # S301 `pickle.loads` IS the format under test in format_compatibility.py. The rule is
72
+ # about untrusted input; this input was written two lines earlier by the same file.
73
+ # S314 likewise `ElementTree.parse`, which is how you check an SVG is still well-formed.
74
+ # N802 `CASE(...)` is a table constructor, and shouting it is what makes the table
75
+ # readable as data rather than as calls.
76
+ "examples/*" = ["ANN", "S301", "S314", "N802"]
77
+
78
+ "tests/*" = ["S101", "S105", "S106", "S108", "ANN", "PT011", "PT012", "PT018", "RUF012", "S603", "S607",
79
+ "N802"]