tiergraph 0.2.0__tar.gz → 0.2.2__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 (154) hide show
  1. {tiergraph-0.2.0 → tiergraph-0.2.2}/CHANGELOG.md +84 -1
  2. {tiergraph-0.2.0 → tiergraph-0.2.2}/CONTRIBUTING.md +4 -1
  3. {tiergraph-0.2.0 → tiergraph-0.2.2}/Makefile +20 -4
  4. {tiergraph-0.2.0 → tiergraph-0.2.2}/PKG-INFO +1 -1
  5. {tiergraph-0.2.0 → tiergraph-0.2.2}/RELEASING.md +9 -3
  6. {tiergraph-0.2.0 → tiergraph-0.2.2}/docs/concepts.md +9 -5
  7. {tiergraph-0.2.0 → tiergraph-0.2.2}/docs/guide/folding.md +198 -0
  8. {tiergraph-0.2.0 → tiergraph-0.2.2}/docs/manifest.json +17 -0
  9. {tiergraph-0.2.0 → tiergraph-0.2.2}/docs/reference/api.md +323 -11
  10. {tiergraph-0.2.0 → tiergraph-0.2.2}/docs/reference/cli.md +5 -5
  11. {tiergraph-0.2.0 → tiergraph-0.2.2}/scripts/check_reservations.py +0 -25
  12. {tiergraph-0.2.0 → tiergraph-0.2.2}/scripts/check_tracked_clean.py +1 -0
  13. {tiergraph-0.2.0 → tiergraph-0.2.2}/src/tiergraph/__init__.py +16 -2
  14. {tiergraph-0.2.0 → tiergraph-0.2.2}/src/tiergraph/cli/__init__.py +1 -0
  15. {tiergraph-0.2.0 → tiergraph-0.2.2}/src/tiergraph/fold.py +6 -4
  16. tiergraph-0.2.2/src/tiergraph/pathplan.py +863 -0
  17. {tiergraph-0.2.0 → tiergraph-0.2.2}/src/tiergraph/semiring.py +120 -1
  18. {tiergraph-0.2.0 → tiergraph-0.2.2}/src/tiergraph/value.py +38 -1
  19. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/conformance/schema_codec.py +42 -11
  20. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_cli.py +21 -0
  21. tiergraph-0.2.2/tests/test_pathplan.py +953 -0
  22. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_publishability_guards.py +11 -5
  23. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_reservation_register.py +48 -16
  24. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_schema_codec_conformance.py +40 -9
  25. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_semiring.py +48 -0
  26. tiergraph-0.2.2/tests/test_value_embedding.py +139 -0
  27. {tiergraph-0.2.0 → tiergraph-0.2.2}/.github/workflows/ci.yml +0 -0
  28. {tiergraph-0.2.0 → tiergraph-0.2.2}/.github/workflows/publish.yml +0 -0
  29. {tiergraph-0.2.0 → tiergraph-0.2.2}/.gitignore +0 -0
  30. {tiergraph-0.2.0 → tiergraph-0.2.2}/.pre-commit-config.yaml +0 -0
  31. {tiergraph-0.2.0 → tiergraph-0.2.2}/LICENSE +0 -0
  32. {tiergraph-0.2.0 → tiergraph-0.2.2}/README.md +0 -0
  33. {tiergraph-0.2.0 → tiergraph-0.2.2}/SECURITY.md +0 -0
  34. {tiergraph-0.2.0 → tiergraph-0.2.2}/corpus/accepted-documents.jsonl +0 -0
  35. {tiergraph-0.2.0 → tiergraph-0.2.2}/denied-name-digests.txt +0 -0
  36. {tiergraph-0.2.0 → tiergraph-0.2.2}/docs/README.md +0 -0
  37. {tiergraph-0.2.0 → tiergraph-0.2.2}/docs/contributing-docs.md +0 -0
  38. {tiergraph-0.2.0 → tiergraph-0.2.2}/docs/format.md +0 -0
  39. {tiergraph-0.2.0 → tiergraph-0.2.2}/docs/getting-started.md +0 -0
  40. {tiergraph-0.2.0 → tiergraph-0.2.2}/docs/guide/construction.md +0 -0
  41. {tiergraph-0.2.0 → tiergraph-0.2.2}/docs/guide/profiles.md +0 -0
  42. {tiergraph-0.2.0 → tiergraph-0.2.2}/docs/guide/recognize-and-act.md +0 -0
  43. {tiergraph-0.2.0 → tiergraph-0.2.2}/docs/guide/selection-and-traversal.md +0 -0
  44. {tiergraph-0.2.0 → tiergraph-0.2.2}/docs/guide/serialization.md +0 -0
  45. {tiergraph-0.2.0 → tiergraph-0.2.2}/docs/guide/span-views.md +0 -0
  46. {tiergraph-0.2.0 → tiergraph-0.2.2}/docs/guide/timing.md +0 -0
  47. {tiergraph-0.2.0 → tiergraph-0.2.2}/examples/__init__.py +0 -0
  48. {tiergraph-0.2.0 → tiergraph-0.2.2}/examples/caption_alignment.py +0 -0
  49. {tiergraph-0.2.0 → tiergraph-0.2.2}/examples/critical_path.py +0 -0
  50. {tiergraph-0.2.0 → tiergraph-0.2.2}/examples/json_document.py +0 -0
  51. {tiergraph-0.2.0 → tiergraph-0.2.2}/examples/mix_paths.py +0 -0
  52. {tiergraph-0.2.0 → tiergraph-0.2.2}/examples/mixing.py +0 -0
  53. {tiergraph-0.2.0 → tiergraph-0.2.2}/examples/text_segmentation.py +0 -0
  54. {tiergraph-0.2.0 → tiergraph-0.2.2}/pyproject.toml +0 -0
  55. {tiergraph-0.2.0 → tiergraph-0.2.2}/schema/tiergraph.schema.json +0 -0
  56. {tiergraph-0.2.0 → tiergraph-0.2.2}/schema/tiergraph.schema.sha256 +0 -0
  57. {tiergraph-0.2.0 → tiergraph-0.2.2}/scripts/__init__.py +0 -0
  58. {tiergraph-0.2.0 → tiergraph-0.2.2}/scripts/capture_corpus.py +0 -0
  59. {tiergraph-0.2.0 → tiergraph-0.2.2}/scripts/check_changelog_claims.py +0 -0
  60. {tiergraph-0.2.0 → tiergraph-0.2.2}/scripts/check_documented.py +0 -0
  61. {tiergraph-0.2.0 → tiergraph-0.2.2}/scripts/check_format_growth.py +0 -0
  62. {tiergraph-0.2.0 → tiergraph-0.2.2}/scripts/check_format_semantics.py +0 -0
  63. {tiergraph-0.2.0 → tiergraph-0.2.2}/scripts/generate_docs.py +0 -0
  64. {tiergraph-0.2.0 → tiergraph-0.2.2}/scripts/generate_schema.py +0 -0
  65. {tiergraph-0.2.0 → tiergraph-0.2.2}/src/tiergraph/__main__.py +0 -0
  66. {tiergraph-0.2.0 → tiergraph-0.2.2}/src/tiergraph/action.py +0 -0
  67. {tiergraph-0.2.0 → tiergraph-0.2.2}/src/tiergraph/build.py +0 -0
  68. {tiergraph-0.2.0 → tiergraph-0.2.2}/src/tiergraph/clock.py +0 -0
  69. {tiergraph-0.2.0 → tiergraph-0.2.2}/src/tiergraph/core.py +0 -0
  70. {tiergraph-0.2.0 → tiergraph-0.2.2}/src/tiergraph/grammar.py +0 -0
  71. {tiergraph-0.2.0 → tiergraph-0.2.2}/src/tiergraph/inspect.py +0 -0
  72. {tiergraph-0.2.0 → tiergraph-0.2.2}/src/tiergraph/machine.py +0 -0
  73. {tiergraph-0.2.0 → tiergraph-0.2.2}/src/tiergraph/machine_codec.py +0 -0
  74. {tiergraph-0.2.0 → tiergraph-0.2.2}/src/tiergraph/path.py +0 -0
  75. {tiergraph-0.2.0 → tiergraph-0.2.2}/src/tiergraph/profile.py +0 -0
  76. {tiergraph-0.2.0 → tiergraph-0.2.2}/src/tiergraph/py.typed +0 -0
  77. {tiergraph-0.2.0 → tiergraph-0.2.2}/src/tiergraph/rewrite.py +0 -0
  78. {tiergraph-0.2.0 → tiergraph-0.2.2}/src/tiergraph/root.py +0 -0
  79. {tiergraph-0.2.0 → tiergraph-0.2.2}/src/tiergraph/schema.py +0 -0
  80. {tiergraph-0.2.0 → tiergraph-0.2.2}/src/tiergraph/selection.py +0 -0
  81. {tiergraph-0.2.0 → tiergraph-0.2.2}/src/tiergraph/spanview.py +0 -0
  82. {tiergraph-0.2.0 → tiergraph-0.2.2}/src/tiergraph/textgrid.py +0 -0
  83. {tiergraph-0.2.0 → tiergraph-0.2.2}/src/tiergraph/traversal.py +0 -0
  84. {tiergraph-0.2.0 → tiergraph-0.2.2}/src/tiergraph/wire.py +0 -0
  85. {tiergraph-0.2.0 → tiergraph-0.2.2}/src/tiergraph_dot/__init__.py +0 -0
  86. {tiergraph-0.2.0 → tiergraph-0.2.2}/src/tiergraph_dot/py.typed +0 -0
  87. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/__init__.py +0 -0
  88. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/conformance/__init__.py +0 -0
  89. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/conformance/action.py +0 -0
  90. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/conformance/declared_schema_codec_divergences.py +0 -0
  91. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/conformance/fold.py +0 -0
  92. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/conformance/kernel.py +0 -0
  93. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/conformance/machine.py +0 -0
  94. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/conformance/recognition.py +0 -0
  95. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/conformance/schema.py +0 -0
  96. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/conformance/selection.py +0 -0
  97. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/conformance/traversal.py +0 -0
  98. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/conformance/wire.py +0 -0
  99. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/fixtures/textgrid/integer-long.TextGrid +0 -0
  100. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/fixtures/textgrid/reference-long.TextGrid +0 -0
  101. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/fixtures/textgrid/reference-short.TextGrid +0 -0
  102. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/semiring_laws.py +0 -0
  103. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_action.py +0 -0
  104. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_alignment_witness.py +0 -0
  105. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_alternation_witness.py +0 -0
  106. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_boundary_witness.py +0 -0
  107. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_build.py +0 -0
  108. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_canonical_properties.py +0 -0
  109. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_capture_corpus.py +0 -0
  110. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_changelog_claims.py +0 -0
  111. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_cli_envelope.py +0 -0
  112. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_clock.py +0 -0
  113. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_core.py +0 -0
  114. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_displacement.py +0 -0
  115. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_docs.py +0 -0
  116. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_dot.py +0 -0
  117. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_edit.py +0 -0
  118. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_examples.py +0 -0
  119. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_external_reference_witness.py +0 -0
  120. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_fold.py +0 -0
  121. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_fold_exactness.py +0 -0
  122. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_fold_witness.py +0 -0
  123. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_format_growth.py +0 -0
  124. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_format_semantics.py +0 -0
  125. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_gate_environment.py +0 -0
  126. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_grammar.py +0 -0
  127. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_inspect.py +0 -0
  128. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_machine.py +0 -0
  129. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_machine_codec.py +0 -0
  130. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_machine_decoders.py +0 -0
  131. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_mix_paths.py +0 -0
  132. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_mix_paths_render.py +0 -0
  133. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_ordered_containment.py +0 -0
  134. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_ordered_polyadic_traversal.py +0 -0
  135. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_package.py +0 -0
  136. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_path.py +0 -0
  137. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_path_alternation.py +0 -0
  138. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_polyadic_relations.py +0 -0
  139. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_polyadic_view_reachability.py +0 -0
  140. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_profile.py +0 -0
  141. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_refusal_order.py +0 -0
  142. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_rewrite.py +0 -0
  143. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_root.py +0 -0
  144. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_schema.py +0 -0
  145. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_selection.py +0 -0
  146. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_selection_semiring.py +0 -0
  147. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_semiring_law_discrimination.py +0 -0
  148. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_shipped_surface.py +0 -0
  149. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_spanview.py +0 -0
  150. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_text_domain.py +0 -0
  151. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_textgrid.py +0 -0
  152. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_traversal.py +0 -0
  153. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_value.py +0 -0
  154. {tiergraph-0.2.0 → tiergraph-0.2.2}/tests/test_wire.py +0 -0
@@ -7,6 +7,87 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.2] - 2026-09-14
11
+
12
+ ### Added
13
+
14
+ - Added `LOG_PROBABILITY`, the log-sum-exp semiring over finite IEEE-double log
15
+ weights with `-inf` as its zero: stable addition, log-product multiplication,
16
+ positive weights admitted, overflow refused, every required law except
17
+ addition commutativity declared approximate, and no star. `tiergraph semirings` lists it as
18
+ `log-probability`, and `tiergraph fold --semiring log-probability` runs it.
19
+ Its `normalize` method is the declared readout that turns log weights into
20
+ probabilities of a total; `ExpectationSemiring` still refuses this inexact
21
+ base (#186).
22
+ - Added `PathPlan`, which compiles a `FoldDeclaration` over one `OR` relation on
23
+ an acyclic graph once and evaluates it again under any vector of carrier
24
+ values in the plan's item order. `evaluate` reproduces what `run` returns for
25
+ the same values, with the same provenance and cost account: exactly under the
26
+ `ARCTIC` and `TROPICAL` carriers, and within the algebra's declared
27
+ approximation under `LOG_PROBABILITY`, where a gathered log-sum-exp sums its
28
+ exponentials in a different order than pairwise addition does. `marginals`
29
+ adds the outside pass and returns `PathMarginals`, whose
30
+ `posteriors(readout=...)` reads probabilities through the readout the caller
31
+ declares and the algebra publishes, and reports zero mass rather than
32
+ fabricating a distribution, as `PathPosteriors` with the readout named. Under
33
+ those three carriers the plan runs the algebra's operations in a fused
34
+ schedule when the declaration states no witness order, and under the extremum
35
+ carriers also under an `AlgebraOrder` — a witness order by an algebra's own
36
+ selective addition — with `CHOOSE_FIRST`, whose selection it fuses too; every
37
+ other declaration runs the general schedule. Ranked output, index axes,
38
+ several relations, `AND` transitions, and cycles are refused by name (#186).
39
+
40
+ ### Changed
41
+
42
+ - The package root now exports every semiring constant the shell can name:
43
+ `ARCTIC`, `DECIMAL_ARCTIC`, `LOG_PROBABILITY` and `TROPICAL` join `BOOLEAN`,
44
+ `COUNTING`, `DECIMAL_TROPICAL` and `PATH` there. `PATH_WITNESSES`, which the
45
+ shell has no spelling for, stays on `tiergraph.semiring`, as every constant
46
+ still does (#186).
47
+ - `FoldDeclaration` now states where a readout above the algebra is declared,
48
+ and the `declared-readout` reservation is discharged: the reservation
49
+ register carries four entries, and `PathMarginals.posteriors` records the
50
+ readout it applies (#186).
51
+ - The gate's three hash-seed test passes run concurrently under `make
52
+ determinism`; `DETERMINISM_JOBS=1` runs them one at a time, and each seed
53
+ writes its own log under the virtualenv and prints only its summary line when
54
+ it passes; a failing seed prints its whole log.
55
+ The publishability test that plants a sentinel under the checkout's ignored
56
+ local agent directory names it per process, so concurrent passes over one
57
+ checkout no longer race on its cleanup (#187).
58
+ - The schema-codec conformance suite audits its generated probes once per
59
+ module and subtracts each test's policy from that audit, through the
60
+ harness's new `audit_drifts` and `subtract_declared`, whose composition
61
+ `undeclared_drifts` still is; a test holds the two answers equal. The three
62
+ tests that each ran the full audit took most of the suite's time (#187).
63
+ - `FORMAT_VERSION` stays `"0.2.0"`: nothing in this line touches the wire
64
+ format, and `make format-growth` reports no break.
65
+
66
+ ### Documentation
67
+
68
+ - The release runbook's step 3 no longer says a stale local `dist/` build is
69
+ what the publish workflow would upload; the workflow builds from a fresh
70
+ checkout of the tag. It now separates the two local checks: the wheel package
71
+ listing reads `dist/*.whl`, so a stale archive is what it would read, while
72
+ the printed version imports from `src` and never consults `dist/` at all. The
73
+ `dist/` filename listing is the only step that would expose a stale build, as
74
+ an extra pair of archives (#185).
75
+
76
+ ## [0.2.1] - 2026-09-13
77
+
78
+ ### Added
79
+
80
+ - Added `embed_json_value` to embed a native JSON value in an existing graph
81
+ through an explicitly fresh namespace. It returns the extended graph, its
82
+ validated value profile, and the value root while preserving the source
83
+ graph. Namespace collisions refuse without partial changes; owner relations
84
+ remain caller-declared. The graph interchange format is unchanged (#183).
85
+
86
+ ### Documentation
87
+
88
+ - Clarified that release-phase format-growth regression tests must consult
89
+ tags, so the release tag does not invalidate their expectations (#182).
90
+
10
91
  ## [0.2.0] - 2026-09-05
11
92
 
12
93
  ### Highlights
@@ -881,6 +962,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
881
962
  - TG-PATH canonical addressing for structural and durable items and boundaries, profile-owned alternatives, kind checks, and typed refusals with offender details.
882
963
  - Canonical selection, bounded bipartite walks, ordered polyadic traversal, and ordered containment queries that preserve declared incidence and child order where applicable.
883
964
 
884
- [Unreleased]: https://github.com/lenzo-ka/tiergraph/compare/v0.2.0...HEAD
965
+ [Unreleased]: https://github.com/lenzo-ka/tiergraph/compare/v0.2.2...HEAD
966
+ [0.2.2]: https://github.com/lenzo-ka/tiergraph/compare/v0.2.1...v0.2.2
967
+ [0.2.1]: https://github.com/lenzo-ka/tiergraph/compare/v0.2.0...v0.2.1
885
968
  [0.2.0]: https://github.com/lenzo-ka/tiergraph/compare/v0.1.0...v0.2.0
886
969
  [0.1.0]: https://github.com/lenzo-ka/tiergraph/releases/tag/v0.1.0
@@ -52,7 +52,10 @@ version this section used to carry fell a step behind twice: a copied list is a
52
52
  claim about the gate that nothing checks.
53
53
 
54
54
  `make check` builds the virtualenv and then runs `make gate`, which is the same
55
- steps against an environment that already exists. Run `gate` where an index
55
+ steps against an environment that already exists. The `determinism` step runs
56
+ its three seeds concurrently, each writing a log under the virtualenv and
57
+ printing its summary line; on a machine that cannot hold three interpreters at
58
+ once, `make gate DETERMINISM_JOBS=1` runs them one at a time. Run `gate` where an index
56
59
  cannot be reached, rather than copying its steps out by hand. Coverage is
57
60
  measured for the `tiergraph` and `tiergraph_dot` packages and for the `scripts`
58
61
  gates, and must remain at 100% branch coverage.
@@ -26,7 +26,7 @@ export PYTHONPATH := $(MAKEFILE_DIR)/src$(if $(PYTHONPATH),:$(PYTHONPATH))
26
26
  # and runs nothing. A target left off this line is silent until something in the
27
27
  # tree happens to share its name, which is why `format-semantics` and
28
28
  # `corpus-capture` sat missing here without ever being noticed.
29
- .PHONY: venv lint format-check types test determinism-seed determinism schema schema-check format-growth format-semantics corpus-capture docs docs-check tracked-clean documented reservations changelog-claims gate-fast gate check
29
+ .PHONY: venv lint format-check types test determinism-seed determinism determinism-seed-0 determinism-seed-12345 determinism-seed-999 schema schema-check format-growth format-semantics corpus-capture docs docs-check tracked-clean documented reservations changelog-claims gate-fast gate check
30
30
 
31
31
  # Development happens in an isolated environment: a shared interpreter drags in
32
32
  # packages this project does not depend on, and they surface as type errors in
@@ -56,10 +56,26 @@ determinism-seed:
56
56
  @test -n "$(HASH_SEED)" || (echo "HASH_SEED is required" >&2; exit 2)
57
57
  @PYTHONHASHSEED=$(HASH_SEED) $(VENV_PYTHON) -m pytest
58
58
 
59
+ # The three seeds are independent, so they run concurrently: measured serial,
60
+ # they were the larger half of the gate's wall time. Each seed writes its own
61
+ # log under the virtualenv (ignored, so it cannot ship) and prints only its
62
+ # summary line, so three interleaved dot streams do not have to be read. A
63
+ # failing seed prints its whole log. DETERMINISM_JOBS=1 runs them one at a
64
+ # time on a machine that cannot hold three interpreters at once.
65
+ DETERMINISM_JOBS ?= 3
66
+ DETERMINISM_SEEDS := 0 12345 999
67
+
59
68
  determinism:
60
- @for seed in 0 12345 999; do \
61
- $(MAKE) --no-print-directory determinism-seed HASH_SEED=$$seed || exit $$?; \
62
- done
69
+ @$(MAKE) --no-print-directory -j$(DETERMINISM_JOBS) \
70
+ $(addprefix determinism-seed-,$(DETERMINISM_SEEDS))
71
+
72
+ determinism-seed-0 determinism-seed-12345 determinism-seed-999:
73
+ @seed=$(@:determinism-seed-%=%); log=$(VENV)/determinism-$$seed.log; \
74
+ if $(MAKE) --no-print-directory determinism-seed HASH_SEED=$$seed >$$log 2>&1; then \
75
+ printf 'seed %s: %s\n' "$$seed" "$$(tail -n 1 $$log)"; \
76
+ else \
77
+ status=$$?; cat $$log; echo "seed $$seed failed" >&2; exit $$status; \
78
+ fi
63
79
 
64
80
  tracked-clean:
65
81
  @$(VENV_PYTHON) scripts/check_tracked_clean.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: tiergraph
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Ordered tiers, declared relations, and an algebra over them
5
5
  Project-URL: Homepage, https://github.com/lenzo-ka/tiergraph
6
6
  Project-URL: Repository, https://github.com/lenzo-ka/tiergraph
@@ -82,7 +82,11 @@ job references it; the OIDC identity is scoped to it).
82
82
  opened the release line, and this edit is what leaves something for that
83
83
  commit.
84
84
 
85
- 3. **Verify locally.** Run these from the development virtualenv that
85
+ 3. **Regenerate version-bearing documentation, then verify locally.** Run
86
+ `make docs` after changing the package version: the generated API and CLI
87
+ references carry it. Commit those generated changes with the
88
+ release preparation so the gate checks the documentation that will ship.
89
+ Run these from the development virtualenv that
86
90
  [CONTRIBUTING.md](CONTRIBUTING.md#set-up-a-development-environment) builds —
87
91
  `make venv` creates `.venv` and installs the `build` frontend along with the
88
92
  rest of the development dependencies. Three of these steps are **read**, not
@@ -113,8 +117,9 @@ job references it; the OIDC identity is scoped to it).
113
117
  displaying it — so without this line the doc asks for a comparison it
114
118
  gives no way to make. Expect exactly `tiergraph-X.Y.Z.tar.gz` and
115
119
  `tiergraph-X.Y.Z-py3-none-any.whl`; a stale build left in `dist/` from an
116
- earlier version shows up here as an extra pair, and it is what the publish
117
- workflow would upload.
120
+ earlier version shows up here as an extra pair. The publish workflow never
121
+ sees it — it builds from a fresh checkout of the tag — but the local
122
+ checks below would read the wrong archive.
118
123
  - **The wheel listing must show both packages.** The grep exits 0 when
119
124
  either package is present, so the exit status decides nothing: read the
120
125
  output and confirm entries under `tiergraph/` *and* under `tiergraph_dot/`.
@@ -255,6 +260,7 @@ ascending sort order.
255
260
  still exists, before `rm -rf dist build` removes it. That listing is read, not
256
261
  scored: the pattern matches either package, so *both* is a fact about the
257
262
  output rather than about the exit status.
263
+ - **Tag-sensitive format-growth tests**: A regression test that asserts what `format-growth` reports is phase-dependent: the report changes the moment the line's first tag exists. Such a test must read the phase from the tags, as `test_the_committed_schema_reports_only_the_break_this_release_priced` does, or it fails on the release commit once tagged; the tag goes up with the release commit in step 4, and the CI run that must be green before step 5 checks out full history and tags, so it sees the tag and a test depending on the tag's absence fails there.
258
264
  - **CI must be green first**: `ci.yml` runs on the push; only cut the release
259
265
  once it passes.
260
266
  - **Re-releases**: PyPI is immutable — you cannot overwrite `X.Y.Z`. If a build
@@ -121,11 +121,15 @@ or schema is present or recognized; required fields or types inside `value`;
121
121
  numeric ranges; or canonical serialization. The producing and consuming
122
122
  applications must perform those validations.
123
123
 
124
- Separately, `json_value_graph` and `JsonValueProfile` already represent a JSON
125
- value as a checked, standalone graph. They are the foundation for a future
126
- first-class payload-attachment API that could attach a JSON value to an item in
127
- another graph. That API does not yet exist. If it is added, stringified-JSON
128
- attributes will be candidates for migration.
124
+ Separately, `json_value_graph` and `JsonValueProfile` represent a JSON value as
125
+ a checked, standalone graph. `embed_json_value(graph, value, namespace=...)`
126
+ adds that native structure to an existing graph and returns the extended graph,
127
+ validated profile, and root reference. Supply a `NamespaceDeclaration` with an
128
+ unused URI and prefix; collisions refuse rather than rename or overwrite.
129
+ Existing facts remain unchanged, including boundaries, seals, and layers.
130
+ The helper does not infer an owner: link the returned root through your own
131
+ declared relation. It is not an arbitrary graph merge or an automatic migration
132
+ of stringified-JSON attributes.
129
133
 
130
134
  ## Coordinates, and what an edit does to them
131
135
 
@@ -335,6 +335,204 @@ Use `TROPICAL` or `ARCTIC`, whose associativity check is approximate, for
335
335
  `xsd:double` values. The refusal is a declaration-time guard, so a fold that
336
336
  runs has already been checked for this mismatch.
337
337
 
338
+ ## Preparing a path plan
339
+
340
+ A fold over one `OR` relation on an acyclic graph is a path graph: every
341
+ derivation is a root-to-sink path, and its value is the product of the local
342
+ values along it. When the graph stays fixed and only the values change — an
343
+ expectation step re-weighting the same lattice, a sweep over parameters —
344
+ `PathPlan` compiles the declaration's topology once and evaluates it under any
345
+ vector of carrier values given in the plan's item order. `PathPlan.evaluate`
346
+ returns what `FoldDeclaration.run` returns, provenance and cost account
347
+ included, and `PathPlan.marginals` adds the outside pass: for every item, the
348
+ sum over the derivations that pass through it.
349
+
350
+ The example is a small lattice whose states and arcs are items of one tier,
351
+ joined by a `next` relation, with the arcs after the states. Under
352
+ `LOG_PROBABILITY` the values are log weights and `-INF` is the zero. A sink
353
+ accepts with its own value, so the final state carries `0.0` and a dead end
354
+ would carry `-INF`.
355
+
356
+ ```python
357
+ import math
358
+
359
+ from tiergraph import (
360
+ AttributeDeclaration,
361
+ AttributeDomain,
362
+ AttributeValuation,
363
+ AttributeValue,
364
+ BipartiteRelationDeclaration,
365
+ ChildCombination,
366
+ FoldDeclaration,
367
+ FoldTransition,
368
+ Graph,
369
+ Item,
370
+ ItemRef,
371
+ NamespaceDeclaration,
372
+ QualifiedName,
373
+ RelationInstance,
374
+ SimpleRelationDeclaration,
375
+ Tier,
376
+ TierDeclaration,
377
+ TiePolicy,
378
+ XsdType,
379
+ )
380
+ from tiergraph.pathplan import AlgebraOrder, PathPlan
381
+ from tiergraph.semiring import ARCTIC, LOG_PROBABILITY
382
+
383
+ ns = "https://example.com/plan"
384
+ nodes = QualifiedName(ns, "nodes")
385
+ node = QualifiedName(ns, "node")
386
+ follows = QualifiedName(ns, "next")
387
+ weight = QualifiedName(ns, "weight")
388
+
389
+
390
+ def item(label: str, log_weight: float) -> Item:
391
+ lexical = "-INF" if log_weight == -math.inf else repr(log_weight)
392
+ return Item(label, (AttributeValue(weight, XsdType.DOUBLE, lexical),))
393
+
394
+
395
+ labels = ("s", "m", "f", "merged", "first", "silent")
396
+ lattice_refs = {label: ItemRef(nodes, index) for index, label in enumerate(labels)}
397
+ graph = Graph(
398
+ (NamespaceDeclaration("lattice", ns),),
399
+ (
400
+ Tier(
401
+ TierDeclaration(nodes, "States and arcs"),
402
+ (
403
+ item("s", 0.0),
404
+ item("m", 0.0),
405
+ item("f", 0.0),
406
+ item("merged", math.log(0.4)),
407
+ item("first", math.log(0.3)),
408
+ item("silent", math.log(0.5)),
409
+ ),
410
+ ),
411
+ ),
412
+ (
413
+ SimpleRelationDeclaration(QualifiedName(ns, "membership"), nodes, node),
414
+ BipartiteRelationDeclaration(follows, node, node, acyclic=True),
415
+ ),
416
+ tuple(
417
+ RelationInstance(follows, lattice_refs[left], lattice_refs[right])
418
+ for left, right in (
419
+ ("s", "merged"),
420
+ ("merged", "f"),
421
+ ("s", "first"),
422
+ ("first", "m"),
423
+ ("m", "silent"),
424
+ ("silent", "f"),
425
+ )
426
+ ),
427
+ (AttributeDeclaration(weight, AttributeDomain.ITEM, XsdType.DOUBLE),),
428
+ )
429
+ valuation = AttributeValuation("weight", weight, (nodes,))
430
+ transitions = (FoldTransition(follows, ChildCombination.OR),)
431
+
432
+ plan = PathPlan.prepare(
433
+ FoldDeclaration(
434
+ "lattice",
435
+ graph,
436
+ valuation,
437
+ LOG_PROBABILITY,
438
+ lambda value, _label: value,
439
+ transitions,
440
+ roots=(lattice_refs["s"],),
441
+ )
442
+ )
443
+ marginals = plan.marginals()
444
+ print("log mass:", round(marginals.total, 6))
445
+ posteriors = marginals.posteriors(readout="normalize")
446
+ print("readout:", posteriors.readout)
447
+ probabilities = posteriors.values
448
+ assert probabilities is not None # zero mass is reported, never fabricated
449
+ for label, probability in zip(plan.labels, probabilities):
450
+ print(f"{label}: {probability:.4f}")
451
+ ```
452
+
453
+ ```text
454
+ log mass: -0.597837
455
+ readout: normalize
456
+ s: 1.0000
457
+ m: 0.2727
458
+ f: 1.0000
459
+ merged: 0.7273
460
+ first: 0.2727
461
+ silent: 0.2727
462
+ ```
463
+
464
+ The mass is `0.4 + 0.3 × 0.5 = 0.55`, and each posterior is the share of that
465
+ mass passing through the item. Normalizing is a division above the algebra,
466
+ so the caller declares the readout by name and the carrier must publish it —
467
+ `normalize` is the one `LOG_PROBABILITY` publishes — and `PathPosteriors`
468
+ records the readout it applied. A zero total is reported as `zero_mass` with
469
+ no values rather than as a fabricated distribution. Aggregating
470
+ posteriors by anything other than item — by the output an arc emits, say — is
471
+ the caller's readout in turn; two arcs producing the same output each carry
472
+ their own share here.
473
+
474
+ New values reuse the compiled topology. `plan.values` holds what the
475
+ declaration lifted, `plan.index` locates an item's position, and a vector of
476
+ another length is refused:
477
+
478
+ ```python
479
+ reweighted = list(plan.values)
480
+ reweighted[plan.index(lattice_refs["merged"])] = math.log(0.1)
481
+ print("re-weighted log mass:", round(plan.marginals(reweighted).total, 6))
482
+ ```
483
+
484
+ ```text
485
+ re-weighted log mass: -1.386294
486
+ ```
487
+
488
+ A best path runs the same plan under a selective carrier. `AlgebraOrder` is a
489
+ witness order by the algebra's own addition, so the fold and the plan agree on
490
+ what wins; with `CHOOSE_FIRST` a tie goes to the alternative earliest in the
491
+ graph's canonical order, deterministically, and `ALL` keeps every tied path up
492
+ to `output_cap`.
493
+
494
+ ```python
495
+ best = PathPlan.prepare(
496
+ FoldDeclaration(
497
+ "best",
498
+ graph,
499
+ valuation,
500
+ ARCTIC,
501
+ lambda value, _label: value,
502
+ transitions,
503
+ roots=(lattice_refs["s"],),
504
+ witness_order=AlgebraOrder(ARCTIC),
505
+ tie_policy=TiePolicy.CHOOSE_FIRST,
506
+ )
507
+ )
508
+ result = best.evaluate()
509
+ print("best:", round(result.value, 6), result.provenance)
510
+ print("carrier ops:", result.cost.carrier_work)
511
+ ```
512
+
513
+ ```text
514
+ best: -0.916291 (('s', 'merged', 'f'),)
515
+ carrier ops: 8
516
+ ```
517
+
518
+ Under `LOG_PROBABILITY`, `ARCTIC`, and `TROPICAL` the plan runs a fused
519
+ schedule that gathers each item's alternatives at once with the algebra's
520
+ operations inlined, and an `AlgebraOrder` with `CHOOSE_FIRST` fuses the
521
+ selection too. The cost account is the general schedule's. A gathered
522
+ log-sum-exp sums its exponentials in a different order than pairwise
523
+ addition, so under the log carrier the plan agrees with `run` within the
524
+ algebra's declared approximation — at the rounding scale of the operands,
525
+ which a total near cancellation can show in its result — and under the
526
+ extremum carriers exactly. Every other declaration runs the general schedule
527
+ through the algebra's methods and agrees with `run` exactly. A result
528
+ that would leave the finite double carrier is refused as overflow rather than
529
+ read as mass created or destroyed, and a value vector outside the carrier —
530
+ `NaN`, a Boolean, the excluded infinity — is refused before anything runs.
531
+
532
+ A plan refuses what a path cannot carry, each by name: ranked output, an index
533
+ product, more than one dependency relation, an `AND` transition, and a cycle,
534
+ which is reported by its closing edge.
535
+
338
536
  ## From the command line
339
537
 
340
538
  `tiergraph semirings` lists the algebras the `tiergraph fold` shell can name,
@@ -38,6 +38,7 @@
38
38
  "GraphEditor"
39
39
  ],
40
40
  "fold": [
41
+ "AlgebraOrder",
41
42
  "AttributeValuation",
42
43
  "ChildCombination",
43
44
  "ExactnessRefusal",
@@ -48,6 +49,9 @@
48
49
  "FoldHomomorphism",
49
50
  "FoldResult",
50
51
  "FoldTransition",
52
+ "PathMarginals",
53
+ "PathPlan",
54
+ "PathPosteriors",
51
55
  "TiePolicy"
52
56
  ],
53
57
  "grammar": [
@@ -147,6 +151,7 @@
147
151
  "RoleBinding",
148
152
  "RoleValue",
149
153
  "SpanViewProfile",
154
+ "embed_json_value",
150
155
  "json_value_graph",
151
156
  "span_view",
152
157
  "to_html",
@@ -195,10 +200,14 @@
195
200
  "selection_loads"
196
201
  ],
197
202
  "semirings": [
203
+ "ARCTIC",
198
204
  "BOOLEAN",
199
205
  "COUNTING",
206
+ "DECIMAL_ARCTIC",
200
207
  "DECIMAL_TROPICAL",
208
+ "LOG_PROBABILITY",
201
209
  "PATH",
210
+ "TROPICAL",
202
211
  "StarRefusal",
203
212
  "StarSelector",
204
213
  "ZeroClosedStar"
@@ -297,6 +306,12 @@
297
306
  "text",
298
307
  "python",
299
308
  "text",
309
+ "python",
310
+ "text",
311
+ "python",
312
+ "text",
313
+ "python",
314
+ "text",
300
315
  "console"
301
316
  ],
302
317
  "docs/guide/profiles.md": [
@@ -378,6 +393,7 @@
378
393
  "COUNTING",
379
394
  "DECIMAL_ARCTIC",
380
395
  "DECIMAL_TROPICAL",
396
+ "LOG_PROBABILITY",
381
397
  "PATH",
382
398
  "PATH_WITNESSES",
383
399
  "TROPICAL",
@@ -389,6 +405,7 @@
389
405
  "ExpectationSemiring",
390
406
  "LawCheck",
391
407
  "LexicographicSemiring",
408
+ "LogProbabilitySemiring",
392
409
  "Path",
393
410
  "PathSemiring",
394
411
  "PathValue",