linkedparticles-core 1.192.2__tar.gz → 1.192.28__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 (223) hide show
  1. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/PKG-INFO +8 -8
  2. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/constraints.txt +9 -9
  3. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/config.py +147 -3
  4. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/core/closure_measure.py +59 -2
  5. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/core/conflict_review.py +36 -0
  6. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/core/contradiction_disclosure.py +2 -3
  7. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/core/held_release.py +3 -3
  8. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/core/observer_scope.py +44 -0
  9. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/core/status.py +5 -2
  10. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/embeddings.py +26 -0
  11. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/extraction/agent.py +51 -9
  12. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/extraction/general.py +17 -2
  13. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/extraction/mcp_memory.py +2 -2
  14. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/extraction/reddit.py +164 -37
  15. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/extraction/subject_scope.py +2 -2
  16. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/llm/usage.py +3 -2
  17. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/render/markdown.py +2 -2
  18. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/pyproject.toml +47 -16
  19. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/test_core_observer_scope.py +53 -0
  20. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/test_embeddings.py +30 -0
  21. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/test_extraction_agent.py +41 -0
  22. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/test_llm_usage.py +11 -0
  23. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/LICENSE +0 -0
  24. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/NOTICE +0 -0
  25. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/README.md +0 -0
  26. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/artifacts/conformance/README.md +0 -0
  27. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/artifacts/conformance/profile.yaml +0 -0
  28. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/artifacts/conformance/similarity_vectors.json +0 -0
  29. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/artifacts/schemas/context.jsonld +0 -0
  30. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/artifacts/schemas/interchange.schema.json +0 -0
  31. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/artifacts/schemas/particle.schema.json +0 -0
  32. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/artifacts/schemas/shacl/CorpusSnapshotShape.ttl +0 -0
  33. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/artifacts/schemas/shacl/ParticleShape.ttl +0 -0
  34. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/artifacts/schemas/shacl/ProvenanceChainShape.ttl +0 -0
  35. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/artifacts/schemas/shacl/SubjectShape.ttl +0 -0
  36. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/artifacts/schemas/shacl/TrustStatementShape.ttl +0 -0
  37. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/artifacts/schemas/trust_lens.schema.json +0 -0
  38. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/__init__.py +0 -0
  39. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/conformance/__init__.py +0 -0
  40. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/conformance/_resources.py +0 -0
  41. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/conformance/contract.py +0 -0
  42. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/conformance/fixtures.py +0 -0
  43. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/conformance/jsonschema.py +0 -0
  44. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/conformance/profile.py +0 -0
  45. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/conformance/runner.py +0 -0
  46. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/conformance/shacl.py +0 -0
  47. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/conformance/types.py +0 -0
  48. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/conformance/validator.py +0 -0
  49. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/core/__init__.py +0 -0
  50. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/core/_resources.py +0 -0
  51. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/core/cascade_gate.py +0 -0
  52. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/core/claims.py +0 -0
  53. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/core/conflict_resolution.py +0 -0
  54. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/core/consolidation_cadence.py +0 -0
  55. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/core/duplicate_key.py +0 -0
  56. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/core/equivalence.py +0 -0
  57. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/core/fingerprint.py +0 -0
  58. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/core/generics.py +0 -0
  59. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/core/granularity.py +0 -0
  60. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/core/jsonld_context.py +0 -0
  61. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/core/ladders.py +0 -0
  62. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/core/modality.py +0 -0
  63. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/core/past_value.py +0 -0
  64. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/core/predicate_profile.py +0 -0
  65. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/core/probe_verdict.py +0 -0
  66. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/core/progress.py +0 -0
  67. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/core/provenance.py +0 -0
  68. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/core/reanchor.py +0 -0
  69. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/core/rung_ladder.py +0 -0
  70. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/core/schema.py +0 -0
  71. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/core/scoring/__init__.py +0 -0
  72. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/core/scoring/confidence.py +0 -0
  73. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/core/scoring/decay.py +0 -0
  74. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/core/scoring/relevance.py +0 -0
  75. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/core/scoring/utility.py +0 -0
  76. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/core/spend_budget.py +0 -0
  77. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/core/stance.py +0 -0
  78. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/core/validity.py +0 -0
  79. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/core/vocabulary.py +0 -0
  80. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/core/vocabulary_jsonld.py +0 -0
  81. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/core/vocabulary_report.py +0 -0
  82. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/egress.py +0 -0
  83. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/extraction/__init__.py +0 -0
  84. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/extraction/append_delta.py +0 -0
  85. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/extraction/calibration.py +0 -0
  86. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/extraction/components.py +0 -0
  87. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/extraction/docstrings.py +0 -0
  88. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/extraction/github/__init__.py +0 -0
  89. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/extraction/github/_shared.py +0 -0
  90. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/extraction/github/gist.py +0 -0
  91. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/extraction/github/pages.py +0 -0
  92. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/extraction/github/repo.py +0 -0
  93. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/extraction/hackernews.py +0 -0
  94. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/extraction/incremental.py +0 -0
  95. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/extraction/journal.py +0 -0
  96. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/extraction/mastodon.py +0 -0
  97. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/extraction/migration_preview.py +0 -0
  98. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/extraction/modality.py +0 -0
  99. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/extraction/nomisma.py +0 -0
  100. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/extraction/numista/__init__.py +0 -0
  101. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/extraction/numista/_shared.py +0 -0
  102. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/extraction/numista/coin.py +0 -0
  103. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/extraction/numista/issuer.py +0 -0
  104. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/extraction/numista/listing.py +0 -0
  105. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/extraction/polarity.py +0 -0
  106. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/extraction/property_keys.py +0 -0
  107. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/extraction/rdf.py +0 -0
  108. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/extraction/registry.py +0 -0
  109. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/extraction/scope.py +0 -0
  110. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/extraction/session_state.py +0 -0
  111. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/extraction/structure.py +0 -0
  112. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/extraction/subject_gate.py +0 -0
  113. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/extraction/taxonomy.py +0 -0
  114. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/extraction/tool_turns.py +0 -0
  115. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/extraction/trust_lens.py +0 -0
  116. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/extraction/vocabulary.py +0 -0
  117. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/extraction/wikidata.py +0 -0
  118. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/http.py +0 -0
  119. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/interchange/__init__.py +0 -0
  120. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/interchange/codec.py +0 -0
  121. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/interchange/jsonl.py +0 -0
  122. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/interchange/yaml_ld.py +0 -0
  123. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/llm/__init__.py +0 -0
  124. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/llm/adapters/__init__.py +0 -0
  125. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/llm/adapters/anthropic.py +0 -0
  126. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/llm/adapters/local.py +0 -0
  127. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/llm/adapters/openai_compat.py +0 -0
  128. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/llm/batch_budget.py +0 -0
  129. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/llm/breaker.py +0 -0
  130. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/llm/client.py +0 -0
  131. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/llm/errors.py +0 -0
  132. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/llm/fencing.py +0 -0
  133. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/llm/logprobs.py +0 -0
  134. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/llm/pool.py +0 -0
  135. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/llm/registry.py +0 -0
  136. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/llm/replay.py +0 -0
  137. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/llm/spend_budget.py +0 -0
  138. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/py.typed +0 -0
  139. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/render/__init__.py +0 -0
  140. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/secrets.py +0 -0
  141. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/url_canonical.py +0 -0
  142. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/particles/url_safety.py +0 -0
  143. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/__init__.py +0 -0
  144. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/_client_fixtures.py +0 -0
  145. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/_upstream.py +0 -0
  146. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/MANIFEST.yaml +0 -0
  147. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/hackernews-thread-001/content.bin +0 -0
  148. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/hackernews-thread-001/manifest.yaml +0 -0
  149. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/hackernews-thread-001/snapshot.json +0 -0
  150. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/journal-entry-001/content.bin +0 -0
  151. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/journal-entry-001/manifest.yaml +0 -0
  152. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/journal-entry-001/snapshot.json +0 -0
  153. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/mastodon-thread-001/content.bin +0 -0
  154. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/mastodon-thread-001/manifest.yaml +0 -0
  155. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/mastodon-thread-001/snapshot.json +0 -0
  156. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/mcp-memory-export-001/content.bin +0 -0
  157. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/mcp-memory-export-001/manifest.yaml +0 -0
  158. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/mcp-memory-export-001/snapshot.json +0 -0
  159. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/nomisma-concept-001/content.bin +0 -0
  160. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/nomisma-concept-001/manifest.yaml +0 -0
  161. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/nomisma-concept-001/snapshot.json +0 -0
  162. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/numista-coin-001/content.bin +0 -0
  163. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/numista-coin-001/manifest.yaml +0 -0
  164. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/numista-coin-001/snapshot.json +0 -0
  165. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/numista-issuer-001/content.bin +0 -0
  166. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/numista-issuer-001/manifest.yaml +0 -0
  167. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/numista-issuer-001/snapshot.json +0 -0
  168. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/numista-listing-001/content.bin +0 -0
  169. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/numista-listing-001/manifest.yaml +0 -0
  170. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/numista-listing-001/snapshot.json +0 -0
  171. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/rdf-graph-001/content.bin +0 -0
  172. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/rdf-graph-001/manifest.yaml +0 -0
  173. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/rdf-graph-001/snapshot.json +0 -0
  174. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/reddit-thread-001/content.bin +0 -0
  175. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/reddit-thread-001/manifest.yaml +0 -0
  176. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/reddit-thread-001/snapshot.json +0 -0
  177. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/taxonomy-coins-001/content.bin +0 -0
  178. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/taxonomy-coins-001/manifest.yaml +0 -0
  179. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/taxonomy-coins-001/snapshot.json +0 -0
  180. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/trust-lens-001/content.bin +0 -0
  181. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/trust-lens-001/manifest.yaml +0 -0
  182. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/trust-lens-001/snapshot.json +0 -0
  183. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/web-article-001/content.bin +0 -0
  184. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/web-article-001/manifest.yaml +0 -0
  185. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/web-article-001/snapshot.json +0 -0
  186. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/web-article-002/content.bin +0 -0
  187. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/web-article-002/manifest.yaml +0 -0
  188. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/web-article-002/snapshot.json +0 -0
  189. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/web-essay-001/content.bin +0 -0
  190. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/web-essay-001/manifest.yaml +0 -0
  191. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/web-essay-001/snapshot.json +0 -0
  192. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/web-interview-001/content.bin +0 -0
  193. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/web-interview-001/manifest.yaml +0 -0
  194. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/web-interview-001/snapshot.json +0 -0
  195. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/wikidata-entity-001/content.bin +0 -0
  196. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/wikidata-entity-001/manifest.yaml +0 -0
  197. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conformance/fixtures/wikidata-entity-001/snapshot.json +0 -0
  198. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/conftest.py +0 -0
  199. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/fixtures/scope/normative-rule-file.md +0 -0
  200. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/fixtures/scope/normative-rule-file.recorded.json +0 -0
  201. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/fixtures/scope/rule-file-agents-md.md +0 -0
  202. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/fixtures/scope/rule-file-agents-md.recorded.json +0 -0
  203. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/test_calibration_isotonic.py +0 -0
  204. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/test_confidence.py +0 -0
  205. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/test_config_client_sections.py +0 -0
  206. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/test_config_sample.py +0 -0
  207. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/test_conformance_fixture_gen.py +0 -0
  208. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/test_conformance_similarity_vectors.py +0 -0
  209. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/test_conformance_validator.py +0 -0
  210. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/test_core_contradiction_disclosure.py +0 -0
  211. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/test_core_held_release.py +0 -0
  212. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/test_core_provenance.py +0 -0
  213. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/test_core_vocabulary.py +0 -0
  214. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/test_egress_policy.py +0 -0
  215. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/test_extraction_vision.py +0 -0
  216. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/test_interchange_schema.py +0 -0
  217. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/test_llm_batch_budget.py +0 -0
  218. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/test_llm_logprobs.py +0 -0
  219. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/test_llm_pool.py +0 -0
  220. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/test_llm_replay.py +0 -0
  221. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/test_schema.py +0 -0
  222. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/test_secrets.py +0 -0
  223. {linkedparticles_core-1.192.2 → linkedparticles_core-1.192.28}/tests/test_url_canonical.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: linkedparticles-core
3
- Version: 1.192.2
3
+ Version: 1.192.28
4
4
  Summary: Store-free client library for Particles: define, validate, serialize, and extract provenance-tracked belief particles
5
5
  Project-URL: Homepage, https://linkedparticles.org
6
6
  Project-URL: Documentation, https://docs.linkedparticles.org
@@ -29,7 +29,7 @@ Classifier: Topic :: Software Development :: Libraries
29
29
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
30
30
  Classifier: Typing :: Typed
31
31
  Requires-Python: >=3.11
32
- Requires-Dist: anthropic>=0.28
32
+ Requires-Dist: anthropic>=1.0
33
33
  Requires-Dist: html2text>=2024.2
34
34
  Requires-Dist: httpx>=0.27
35
35
  Requires-Dist: jsonschema>=4.22
@@ -37,17 +37,17 @@ Requires-Dist: lxml-stubs>=0.5.1
37
37
  Requires-Dist: numpy>=1.26
38
38
  Requires-Dist: opentelemetry-api>=1.27
39
39
  Requires-Dist: pydantic>=2.7
40
- Requires-Dist: pyld<4,>=2.0
41
- Requires-Dist: pypdf>=6.15.0
42
- Requires-Dist: pyshacl<0.32,>=0.26
40
+ Requires-Dist: pyld<4,>=3.0
41
+ Requires-Dist: pypdf>=6.19.0
42
+ Requires-Dist: pyshacl<0.41,>=0.40
43
43
  Requires-Dist: pyyaml>=6.0
44
44
  Requires-Dist: rdflib<8,>=7.0
45
45
  Requires-Dist: scipy>=1.13
46
- Requires-Dist: sentence-transformers>=2.7
47
- Requires-Dist: sqlalchemy[asyncio]<2.2,>=2.0
46
+ Requires-Dist: sentence-transformers>=5.6.0
47
+ Requires-Dist: sqlalchemy[asyncio]<2.2,>=2.1
48
48
  Requires-Dist: torch
49
49
  Provides-Extra: vision
50
- Requires-Dist: pypdfium2>=4.30; extra == 'vision'
50
+ Requires-Dist: pypdfium2>=5.0; extra == 'vision'
51
51
  Description-Content-Type: text/markdown
52
52
 
53
53
  # linkedparticles-core
@@ -10,7 +10,7 @@ annotated-doc==0.0.5
10
10
  # typer
11
11
  annotated-types==0.8.0
12
12
  # via pydantic
13
- anthropic==1.11.0
13
+ anthropic==1.13.0
14
14
  # via linkedparticles
15
15
  anyio==4.14.2 ; python_full_version < '3.15'
16
16
  # via
@@ -42,7 +42,7 @@ attrs==26.1.0
42
42
  # referencing
43
43
  cachetools==7.2.0
44
44
  # via pyld
45
- certifi==2026.7.22
45
+ certifi==2026.10.19
46
46
  # via
47
47
  # httpcore
48
48
  # httpx
@@ -67,9 +67,9 @@ distro==1.9.0
67
67
  # via langsmith
68
68
  docstring-parser==0.18.0
69
69
  # via anthropic
70
- fastapi==0.142.2
70
+ fastapi==0.143.0
71
71
  # via linkedparticles
72
- filelock==4.0.12
72
+ filelock==4.1.1
73
73
  # via
74
74
  # huggingface-hub
75
75
  # linkedparticles
@@ -148,7 +148,7 @@ jsonschema==4.26.0
148
148
  # mcp
149
149
  jsonschema-specifications==2025.9.1
150
150
  # via jsonschema
151
- langchain-core==1.6.7
151
+ langchain-core==1.6.10
152
152
  # via linkedparticles
153
153
  langchain-protocol==0.0.19
154
154
  # via langchain-core
@@ -266,7 +266,7 @@ protobuf==7.36.2
266
266
  # opentelemetry-proto
267
267
  pycparser==3.0 ; implementation_name != 'PyPy' and platform_python_implementation != 'PyPy'
268
268
  # via cffi
269
- pydantic==2.13.5
269
+ pydantic==2.14.1
270
270
  # via
271
271
  # anthropic
272
272
  # fastapi
@@ -275,7 +275,7 @@ pydantic==2.13.5
275
275
  # linkedparticles
276
276
  # mcp
277
277
  # pydantic-settings
278
- pydantic-core==2.46.5
278
+ pydantic-core==2.50.1
279
279
  # via pydantic
280
280
  pydantic-settings==2.15.0
281
281
  # via mcp
@@ -287,7 +287,7 @@ pyld==3.3.0
287
287
  # via linkedparticles
288
288
  pyparsing==3.3.3
289
289
  # via rdflib
290
- pypdf==6.19.0
290
+ pypdf==6.20.1
291
291
  # via linkedparticles
292
292
  pypdfium2==5.14.0
293
293
  # via linkedparticles
@@ -356,7 +356,7 @@ sniffio==1.3.1
356
356
  # via
357
357
  # anthropic
358
358
  # langsmith
359
- sqlalchemy==2.1.3
359
+ sqlalchemy==2.1.4
360
360
  # via
361
361
  # alembic
362
362
  # linkedparticles
@@ -595,6 +595,27 @@ _SHIPPED_EXTRACTION_OUTPUT_TOKENS: dict[str, int] = {
595
595
  "claude-sonnet-4-6": 5_363,
596
596
  }
597
597
 
598
+ # Cache-read multipliers that differ from ``llm.cache_read_price_multiplier``'s
599
+ # standard 0.1x, from the pricing page's footnotes (read 2026-10-18): cache hits
600
+ # on claude-opus-5-5 and claude-sonnet-5-5 bill at 0.05x base input, and on
601
+ # claude-fable-5-1 at 0.025x. Every other model uses the standard multiplier.
602
+ _SHIPPED_CACHE_READ_MULTIPLIER: dict[str, float] = {
603
+ "claude-fable-5-1": 0.025,
604
+ "claude-opus-5-5": 0.05,
605
+ "claude-sonnet-5-5": 0.05,
606
+ }
607
+
608
+ # How much less output a source type's extraction calls write per character
609
+ # than the documents ``audit.estimate_output_tokens_per_source_char`` was
610
+ # fitted on (memory notes and design records, about 5 claims per 1,000
611
+ # characters). A Claude Code transcript states about 1.9 per 1,000 (the
612
+ # general extractor's transcript deltas), so 0.4. Checked against
613
+ # the five metered consolidation nights of 2026-10-12 to 10-16 on the owner's
614
+ # store (claude-sonnet-5-5, mostly CONVERSATION snapshots): the expected
615
+ # output came out 1.1 to 1.9 times what the provider reported, before the
616
+ # estimate's high end adds its spread.
617
+ _SHIPPED_OUTPUT_SCALE_BY_SOURCE_TYPE: dict[str, float] = {"CONVERSATION": 0.4}
618
+
598
619
  _T = TypeVar("_T")
599
620
 
600
621
 
@@ -731,6 +752,39 @@ class LLMConfig(BaseModel):
731
752
  # at 1.25x input and a cache read at 0.1x.
732
753
  cache_write_price_multiplier: float = Field(default=1.25, ge=0.0)
733
754
  cache_read_price_multiplier: float = Field(default=0.1, ge=0.0)
755
+ # Per-model cache-read multipliers, keyed like ``price_per_mtok``, for the
756
+ # models the pricing page bills below the standard 0.1x
757
+ # (``_SHIPPED_CACHE_READ_MULTIPLIER``). Entries set here merge over the
758
+ # shipped ones; a model with no entry uses ``cache_read_price_multiplier``.
759
+ cache_read_price_multiplier_by_model: dict[str, float] = Field(
760
+ default_factory=lambda: dict(_SHIPPED_CACHE_READ_MULTIPLIER)
761
+ )
762
+
763
+ @field_validator("cache_read_price_multiplier_by_model", mode="before")
764
+ @classmethod
765
+ def _merge_over_shipped_cache_reads(cls, value: Any) -> Any:
766
+ """Operator entries replace the shipped ones key by key; the rest stay."""
767
+ if isinstance(value, dict):
768
+ return {**_SHIPPED_CACHE_READ_MULTIPLIER, **value}
769
+ return value
770
+
771
+ @field_validator("cache_read_price_multiplier_by_model")
772
+ @classmethod
773
+ def _non_negative_cache_reads(cls, value: dict[str, float]) -> dict[str, float]:
774
+ """A cache-read multiplier is a price ratio: zero or more."""
775
+ for key, factor in value.items():
776
+ if factor < 0:
777
+ raise ValueError(
778
+ f"llm.cache_read_price_multiplier_by_model[{key!r}] must be >= 0, got {factor}"
779
+ )
780
+ return value
781
+
782
+ def cache_read_multiplier_for(self, selection: ProviderSelection) -> float:
783
+ """The cache-read price as a multiple of ``selection``'s input price."""
784
+ factor, _ = lookup_by_model(
785
+ self.cache_read_price_multiplier_by_model, selection.provider, selection.model
786
+ )
787
+ return self.cache_read_price_multiplier if factor is None else factor
734
788
 
735
789
  @field_validator("price_per_mtok", mode="before")
736
790
  @classmethod
@@ -936,6 +990,14 @@ class ExtractionConfig(BaseModel):
936
990
  # alone. On a short source it rivals the source itself. Read by the audit
937
991
  # estimate and the memory-rot benchmark.
938
992
  estimate_prompt_overhead_tokens: int = Field(default=2500, ge=0)
993
+ # The part of that system prompt sent as a prompt-cache prefix,
994
+ # which a batch of extraction calls writes once and then reads at the
995
+ # model's cache-read price (``llm.cache_read_price_multiplier_by_model``).
996
+ # The consolidation estimate adds it per call on top of the overhead above;
997
+ # with ``llm.prompt_cache.enabled`` off it is priced as plain input.
998
+ # Measured: claude-sonnet-5-5 wrote a 2,868-token prefix on each of the
999
+ # metered nights of 2026-10-12 to 10-16.
1000
+ estimate_cached_prefix_tokens: int = Field(default=2900, ge=0)
939
1001
  # The bulk extraction paths skip an unextracted snapshot of a MUTABLE
940
1002
  # entry once a newer extractable snapshot of the same entry exists: the
941
1003
  # generation cascade would retire the older generation's beliefs anyway,
@@ -956,6 +1018,12 @@ class ExtractionConfig(BaseModel):
956
1018
  # The largest delta chunk sent in one call. Each chunk after the first
957
1019
  # takes the end of the chunk before it as its context.
958
1020
  append_chunk_chars: int = Field(default=7500, ge=1000)
1021
+ # the agent extractor's prompt (the Stop hook's ask and its API
1022
+ # proxy) tells the model how many claims text of its size usually yields,
1023
+ # as a thoroughness guide, never a quota. 1.9 is the median claims per
1024
+ # 1000 characters of the general extractor's transcript deltas in the
1025
+ # maintainers' store (262 reads, measured 2026-10-16).
1026
+ agent_expected_claims_per_kchar: float = Field(default=1.9, gt=0.0)
959
1027
  # a pair the §6.6 contradiction probe confirms is read a second
960
1028
  # time, before the write loop, with each claim's source kind, name, time
961
1029
  # and passage (on ``llm.verification``). A pair the reading does not
@@ -1627,6 +1695,15 @@ class UtilityMiningConfig(BaseModel):
1627
1695
  behavioural_matching: bool = True
1628
1696
  max_behavioural_calls: int = Field(default=150, ge=0)
1629
1697
  behavioural_candidate_limit: int = Field(default=200, ge=0)
1698
+ # Characters per input token the consolidation estimate prices a judge
1699
+ # prompt at. The prompt is mostly tool-call lines (paths, ids, shell
1700
+ # commands), which tokenize denser than prose: on the metered nights of
1701
+ # 2026-10-12 to 10-16, claude-sonnet-5-5 billed 3,560 to 4,240 input
1702
+ # tokens a call for prompts of about 9,100 characters (the action
1703
+ # evidence is capped, so the size holds night to night), 2.1 to 2.6
1704
+ # characters a token. The lower figure keeps the estimate above the bill.
1705
+ # The estimate uses the smaller of this and the model's own factor.
1706
+ estimate_chars_per_token: float = Field(default=2.1, gt=0.0)
1630
1707
 
1631
1708
 
1632
1709
  class UtilityConfig(BaseModel):
@@ -2085,6 +2162,21 @@ class RedditConfig(BaseModel):
2085
2162
  top_comment_count: int = 200
2086
2163
  # Raised 500→1000 for parity with the gist comment body limit.
2087
2164
  comment_body_limit: int = 1000
2165
+ # Where the importer fetches a thread's post and comment tree from. Reddit
2166
+ # bot-walls its own ``.json`` endpoint for unauthenticated clients (403), so
2167
+ # the default reads the Arctic Shift archive, a free third-party mirror that
2168
+ # serves Reddit's own field names. ``reddit`` keeps the direct curl fetch for
2169
+ # an operator whose network can still reach it. The share-link redirect is
2170
+ # always resolved through reddit.com, whichever backend is chosen.
2171
+ fetch_backend: Literal["arctic_shift", "reddit"] = "arctic_shift"
2172
+ # Base URL of the Arctic Shift API host (no trailing path).
2173
+ arctic_shift_base_url: str = "https://arctic-shift.photon-reddit.com"
2174
+ # ``limit`` passed to the archive's comment-tree endpoint: the most comments
2175
+ # one deposit pulls. The extractor still sends only ``top_comment_count``.
2176
+ arctic_shift_comment_limit: int = Field(default=25000, ge=1)
2177
+ # Longest wait honoured on an HTTP 429 before the one retry. A reset further
2178
+ # away than this fails the deposit at once instead of stalling it.
2179
+ arctic_shift_max_wait_seconds: float = Field(default=30.0, ge=0.0)
2088
2180
 
2089
2181
 
2090
2182
  class HackerNewsConfig(BaseModel):
@@ -2216,6 +2308,19 @@ class QueryConfig(BaseModel):
2216
2308
  # default follows the leakage measurement recorded in
2217
2309
  # docs/benchmarks/leakage.md.
2218
2310
  grounded_answers: bool = False
2311
+ # Keep each store's ACTIVE candidates decoded in memory between reads,
2312
+ # re-reading only the rows the store's change journal names as changed
2313
+ # (particles/store/candidate_cache.py). Semantic query, extraction, the
2314
+ # write-time reconciliation seam and the maintenance passes read through
2315
+ # it. Results are identical either way; off reads and decodes every
2316
+ # candidate on every read. The cache holds one decoded row per ACTIVE
2317
+ # belief for the life of the process.
2318
+ candidate_cache: bool = True
2319
+ # After a full build of a cache this many rows or larger, exempt the
2320
+ # process's live objects from Python's cyclic garbage collector
2321
+ # (gc.freeze), which otherwise walks the whole cache on most queries
2322
+ # (about 0.3 s at 56k rows). Process-wide; 0 never freezes.
2323
+ candidate_cache_freeze_rows: int = Field(default=10_000, ge=0)
2219
2324
 
2220
2325
 
2221
2326
  class ContestednessConfig(BaseModel):
@@ -2801,12 +2906,21 @@ class AgentExtractionConfig(BaseModel):
2801
2906
  # An ask unanswered after this many seconds is expired.
2802
2907
  ask_ttl_seconds: int = Field(default=900, ge=60)
2803
2908
  # A submission covers its span only with at least this many accepted claims
2804
- # per 1000 characters. 2.7 is the 10th percentile of the general
2805
- # extractor's own transcript deltas in the maintainers' store.
2806
- min_claims_per_kchar: float = Field(default=2.7, ge=0.0)
2909
+ # per 1000 characters. 1.27 is the 10th percentile of the general
2910
+ # extractor's own transcript deltas in the maintainers' store (262 delta
2911
+ # reads, measured 2026-10-16; it was 2.7 on 2026-10-09, before the
2912
+ # session-state and tool-turn filters thinned transcript extraction).
2913
+ min_claims_per_kchar: float = Field(default=1.27, ge=0.0)
2807
2914
  # ...and with no more than this share of candidates dropped for evidence
2808
2915
  # not found in the span. Placeholder until the shadow week measures it.
2809
2916
  max_evidence_drop_ratio: float = Field(default=0.5, ge=0.0, le=1.0)
2917
+ # In shadow mode, keep each submission's accepted candidates (claim text,
2918
+ # stated confidence, evidence) in a local JSONL file, so a later labelling
2919
+ # pass can fit a calibration map on real session claims. The
2920
+ # operator event log keeps counts only. Off writes nothing.
2921
+ keep_shadow_candidates: bool = True
2922
+ # None ⇒ ``<claude_code.state_dir>/agent-extraction-shadow.jsonl``.
2923
+ shadow_candidates_path: str | None = None
2810
2924
 
2811
2925
  @field_validator("mode", mode="before")
2812
2926
  @classmethod
@@ -3210,6 +3324,36 @@ class AuditConfig(BaseModel):
3210
3324
  # Wall time per second reading: the measured runs read 22 flags in ~41 s.
3211
3325
  estimate_seconds_per_verification: float = Field(default=2.0, ge=0.0)
3212
3326
 
3327
+ # Multiplies the per-character output rate above for a source type that
3328
+ # states fewer (or more) claims per character than the documents it was
3329
+ # fitted on, keyed by corpus-entry source type (``CONVERSATION``). The
3330
+ # per-call base is unchanged. Read by the consolidation estimate, which
3331
+ # knows each snapshot's source type. Ships with a measured entry for
3332
+ # transcripts (``_SHIPPED_OUTPUT_SCALE_BY_SOURCE_TYPE``); operator entries
3333
+ # merge over it key by key, and a type with no entry is scaled by 1.
3334
+ estimate_output_scale_by_source_type: dict[str, float] = Field(
3335
+ default_factory=lambda: dict(_SHIPPED_OUTPUT_SCALE_BY_SOURCE_TYPE)
3336
+ )
3337
+
3338
+ @field_validator("estimate_output_scale_by_source_type", mode="before")
3339
+ @classmethod
3340
+ def _merge_over_shipped_type_scales(cls, value: Any) -> Any:
3341
+ """Operator entries replace the shipped ones key by key; the rest stay."""
3342
+ if isinstance(value, dict):
3343
+ return {**_SHIPPED_OUTPUT_SCALE_BY_SOURCE_TYPE, **value}
3344
+ return value
3345
+
3346
+ @field_validator("estimate_output_scale_by_source_type")
3347
+ @classmethod
3348
+ def _non_negative_type_scale(cls, value: dict[str, float]) -> dict[str, float]:
3349
+ """Every per-type output scale is a multiplier: zero or more."""
3350
+ for key, scale in value.items():
3351
+ if scale < 0:
3352
+ raise ValueError(
3353
+ f"audit.estimate_output_scale_by_source_type[{key!r}] must be >= 0, got {scale}"
3354
+ )
3355
+ return value
3356
+
3213
3357
  @field_validator("estimate_output_scale_by_model", mode="before")
3214
3358
  @classmethod
3215
3359
  def _merge_over_shipped_scales(cls, value: Any) -> Any:
@@ -24,13 +24,20 @@ A transition is one of three kinds:
24
24
  and *autonomous* otherwise: the trust ladder, update supersession,
25
25
  duplicate merge, re-anchor, staleness, abstraction revalidation.
26
26
  - ``contradiction_closed``: an INCONSISTENCY record closed, by a review
27
- (gesture) or by the nightly disclosure pass (autonomous).
27
+ (gesture) or by a nightly pass (autonomous): the disclosure pass
28
+ or the held-claim release.
28
29
  - ``promotion``: an abstraction asserted, by the accept gesture or
29
30
  by the pass itself in ``auto`` mode.
30
31
 
31
32
  A gesture is an explicit write verb, whether an operator or an agent ran it;
32
33
  ``gesture_by_actor`` keeps the two apart for a reader who needs them apart.
33
34
 
35
+ An autonomous closure is also tallied by its cause, and by whether it
36
+ returned a held claim to the store (:func:`tally_closures`). Most causes
37
+ release nothing: a lapsed or regrouped census record held no claim. A
38
+ withdrawn extraction record and a partner-retired one each restore the
39
+ claim the record held, and that release is the one no person approved.
40
+
34
41
  Pure: the Engine gathers plain values and this module decides the tally.
35
42
  Nothing here is stored on a particle; the result rides the
36
43
  ``CONSOLIDATION_RUN`` event.
@@ -39,8 +46,9 @@ Nothing here is stored on a particle; the result rides the
39
46
  from __future__ import annotations
40
47
 
41
48
  from collections import Counter
42
- from collections.abc import Sequence
49
+ from collections.abc import Mapping, Sequence
43
50
  from dataclasses import dataclass, field
51
+ from typing import Any
44
52
 
45
53
  #: Event types whose PARTICLE refs name the beliefs an explicit write retired.
46
54
  GESTURE_RETIREMENT_EVENTS: frozenset[str] = frozenset(
@@ -55,6 +63,10 @@ TRANSITION_KINDS: tuple[str, ...] = ("retirement", "contradiction_closed", "prom
55
63
  #: The reason key for a retired row that carries no ``status_reason``.
56
64
  UNSPECIFIED_REASON = "UNSPECIFIED"
57
65
 
66
+ #: The ``INCONSISTENCY_CLOSED`` payload keys that name the claim a close
67
+ #: returned to the store: a minted successor, or the ACTIVE twin it folded into.
68
+ RELEASE_KEYS: tuple[str, ...] = ("successor", "folded_into")
69
+
58
70
 
59
71
  @dataclass(frozen=True)
60
72
  class Retirement:
@@ -73,6 +85,51 @@ class GestureEvent:
73
85
  particle_ids: tuple[str, ...] = ()
74
86
 
75
87
 
88
+ @dataclass(frozen=True)
89
+ class Closure:
90
+ """One autonomous ``INCONSISTENCY_CLOSED`` event, reduced to what the tally reads."""
91
+
92
+ cause: str
93
+ #: Whether the close returned a held claim to the store.
94
+ released: bool = False
95
+
96
+
97
+ def closure_from_payload(payload: Mapping[str, Any] | None) -> Closure:
98
+ """Read one close event's cause and whether it released a held claim."""
99
+ data = payload or {}
100
+ cause = data.get("cause")
101
+ return Closure(
102
+ cause=cause if isinstance(cause, str) and cause else UNSPECIFIED_REASON,
103
+ released=any(data.get(key) for key in RELEASE_KEYS),
104
+ )
105
+
106
+
107
+ @dataclass(frozen=True)
108
+ class ClosureTally:
109
+ """Autonomous closures by cause, and the held-claim releases among them."""
110
+
111
+ closed_by_cause: dict[str, int] = field(default_factory=dict)
112
+ released_by_cause: dict[str, int] = field(default_factory=dict)
113
+
114
+ @property
115
+ def closed(self) -> int:
116
+ return sum(self.closed_by_cause.values())
117
+
118
+ @property
119
+ def released(self) -> int:
120
+ return sum(self.released_by_cause.values())
121
+
122
+
123
+ def tally_closures(closures: Sequence[Closure]) -> ClosureTally:
124
+ """Count closures and held-claim releases per cause, causes sorted."""
125
+ closed = Counter(c.cause for c in closures)
126
+ released = Counter(c.cause for c in closures if c.released)
127
+ return ClosureTally(
128
+ closed_by_cause=dict(sorted(closed.items())),
129
+ released_by_cause=dict(sorted(released.items())),
130
+ )
131
+
132
+
76
133
  @dataclass(frozen=True)
77
134
  class TransitionTally:
78
135
  """Lifecycle transitions in one window, split autonomous versus gesture."""
@@ -31,6 +31,42 @@ from particles.core.status import Status, StatusReason
31
31
 
32
32
  _TERMINAL = (Status.RETRACTED, Status.SUPERSEDED)
33
33
 
34
+ CLOSED_RECORD: tuple[Status, StatusReason] = (Status.RETRACTED, StatusReason.CONFLICT_RESOLVED)
35
+ """The state every non-DEFER resolution leaves an INCONSISTENCY record in.
36
+
37
+ The §6.6 wrapper-terminal rule: review, the trust cascade, the held-claim
38
+ release and the disclosure sweep all close a record this way. A record the
39
+ cascade closed before 1.192.15 was left ``PROVENANCE_STALE`` /
40
+ ``CONFLICT_RESOLVED`` instead; migration 053 moves those here."""
41
+
42
+ CASCADE_ACTOR = "trust-cascade"
43
+ """``asserted_by`` on the REVIEW particle the trust cascade writes per record."""
44
+
45
+ _CASCADE_REVIEW_PREFIX = "CASCADE REVIEW: auto-resolved INCONSISTENCY "
46
+
47
+
48
+ def cascade_review_content(
49
+ inconsistency_id: str, winner_id: str, loser_id: str, statement_id: str
50
+ ) -> str:
51
+ """The content of the REVIEW particle the trust cascade writes for one record."""
52
+ return (
53
+ f"{_CASCADE_REVIEW_PREFIX}{inconsistency_id}. "
54
+ f"Winner: {winner_id}. Loser: {loser_id}. "
55
+ f"Triggered by trust statement {statement_id}."
56
+ )
57
+
58
+
59
+ def cascade_reviewed_record_id(content: str) -> str | None:
60
+ """The record a cascade REVIEW particle's content names, or None for other content.
61
+
62
+ The cascade writes no operator event, so its REVIEW particle is the one
63
+ trace that says which closed records it closed.
64
+ """
65
+ if not content.startswith(_CASCADE_REVIEW_PREFIX):
66
+ return None
67
+ record_id, sep, _rest = content[len(_CASCADE_REVIEW_PREFIX) :].partition(". ")
68
+ return record_id if sep and record_id else None
69
+
34
70
 
35
71
  class Demotion(Enum):
36
72
  """What demoting a conflict's losing claim takes, given its current status."""
@@ -76,8 +76,7 @@ DISCLOSURE_ACTOR = "memory-consolidate"
76
76
  READING_STANDING = "standing/1"
77
77
  #: The instruction for a pair with a claim from a time-stamped record (a
78
78
  #: session transcript, another append-only log): a value observed at a later
79
- #: time may be a change, and only the same moment or a fixed fact confirms
80
- #:.
79
+ #: time may be a change, and only the same moment or a fixed fact confirms.
81
80
  READING_OBSERVED = "observed/1"
82
81
 
83
82
  _EXCERPT_CHARS = 120
@@ -420,7 +419,7 @@ class CloseCause(StrEnum):
420
419
  REVIEW = "review"
421
420
  """A review action: a ``REVIEW_RESOLVED`` event names the record."""
422
421
  CASCADE = "cascade"
423
- """The trust cascade: ``PROVENANCE_STALE``, with no event."""
422
+ """The trust cascade: no event; its REVIEW particle names the record."""
424
423
  LAPSED = "lapsed"
425
424
  """The lapse sweep: a side has no member left."""
426
425
  REGROUPED = "regrouped"
@@ -31,9 +31,9 @@ from particles.core.observer_scope import PairPrecondition
31
31
  from particles.core.schema import Particle
32
32
  from particles.core.status import Status, StatusReason
33
33
 
34
- #: The retirements an autonomous pass writes: rung 2.5 and its sweep
35
- #:, the re-anchor pass, and the document sweep
36
- #:. A claim a person retired is not among them, and a record
34
+ #: The retirements an autonomous pass writes: rung 2.5 and its sweep,
35
+ #: the re-anchor pass, and the document sweep.
36
+ #: A claim a person retired is not among them, and a record
37
37
  #: behind one stays with review.
38
38
  AUTONOMOUS_RETIREMENTS: frozenset[StatusReason] = frozenset(
39
39
  {
@@ -158,6 +158,26 @@ def meet(premises: Iterable[BeliefScope]) -> BeliefScope:
158
158
  return BeliefScope(keys=keys, lapsed=lapsed and not keys)
159
159
 
160
160
 
161
+ def join(members: Iterable[BeliefScope]) -> BeliefScope:
162
+ """The scope a ``CO_EVIDENTIAL`` merge gives every sourced member: their union.
163
+
164
+ The join pools the group's attesting entries, so the merged
165
+ scope is global as soon as one member is, and otherwise the union of their
166
+ keys. A union with no keys is in view for no project; it is reported as
167
+ lapsed when some member was lapsed, which is a reading for display only,
168
+ since the pooled entries decide between lapsed and unattributed. Applies to
169
+ sourced beliefs: a derived belief keeps the meet of its premises.
170
+ """
171
+ keys: set[str] = set()
172
+ lapsed = False
173
+ for member in members:
174
+ if member.is_global:
175
+ return GLOBAL_SCOPE
176
+ keys |= member.keys
177
+ lapsed = lapsed or member.lapsed
178
+ return BeliefScope(keys=frozenset(keys), lapsed=lapsed and not keys)
179
+
180
+
161
181
  def visible(scope: BeliefScope, observer_project: str | None, *, widened: bool = False) -> bool:
162
182
  """The predicate: is a belief with ``scope`` in view for this observer?
163
183
 
@@ -187,6 +207,30 @@ def share_an_observer(a: BeliefScope, b: BeliefScope) -> bool:
187
207
  return bool(a.keys & b.keys)
188
208
 
189
209
 
210
+ def same_scope(a: BeliefScope, b: BeliefScope) -> bool:
211
+ """Whether two beliefs have exactly the same observers: the agent link test.
212
+
213
+ A ``CO_EVIDENTIAL`` edge unions the two groups' sources, so it leaves both
214
+ scopes unchanged only when they are already equal. Visibility is not
215
+ enough: a project's belief linked to a global one becomes global. Lapsed
216
+ and unattributed are distinct readings and only equal themselves.
217
+ """
218
+ if a.is_global or b.is_global:
219
+ return a.is_global and b.is_global
220
+ if a.keys or b.keys:
221
+ return a.keys == b.keys
222
+ return a.lapsed == b.lapsed
223
+
224
+
225
+ def describe_scope(scope: BeliefScope) -> str:
226
+ """A short operator-facing reading of a scope, for refusals and reports."""
227
+ if scope.is_global:
228
+ return "global"
229
+ if scope.keys:
230
+ return "project " + ", ".join(sorted(scope.keys))
231
+ return "lapsed" if scope.lapsed else "unattributed"
232
+
233
+
190
234
  class PairPrecondition(StrEnum):
191
235
  """Whether a (candidate, existing) pair may be reconciled."""
192
236
 
@@ -81,8 +81,11 @@ _ALLOWED: frozenset[tuple[Status | None, Status]] = frozenset(
81
81
  (Status.ACTIVE, Status.SUPERSEDED),
82
82
  (Status.ACTIVE, Status.RETRACTED),
83
83
  (Status.ACTIVE, Status.PROVENANCE_STALE),
84
- (Status.INCONSISTENCY, Status.PROVENANCE_STALE), # PREFER A/B: loser demoted
85
- (Status.INCONSISTENCY, Status.RETRACTED), # BOTH VALID: INCONSISTENCY particle retracted
84
+ # Every non-DEFER resolution retracts the record (§6.6 wrapper-terminal
85
+ # rule): review, the trust cascade, the held-claim release and the
86
+ # disclosure sweep. There is no INCONSISTENCY → PROVENANCE_STALE edge;
87
+ # the cascade wrote one until 1.192.15 (migration 053).
88
+ (Status.INCONSISTENCY, Status.RETRACTED),
86
89
  (Status.INCONSISTENCY, Status.INCONSISTENCY), # DEFER: re-set same status
87
90
  (Status.PROVENANCE_STALE, Status.SUPERSEDED), # Reindex supersession of stale particle
88
91
  (Status.PROVENANCE_STALE, Status.RETRACTED), # Explicit operator cleanup
@@ -26,6 +26,7 @@ from __future__ import annotations
26
26
  import contextlib
27
27
  import logging
28
28
  import threading
29
+ from collections.abc import Sequence
29
30
  from dataclasses import dataclass
30
31
  from typing import Any
31
32
 
@@ -134,6 +135,31 @@ def cosine_similarity(
134
135
  return max(0.0, min(1.0, raw))
135
136
 
136
137
 
138
+ def cosine_similarities(
139
+ query: np.ndarray[Any, np.dtype[np.floating[Any]]],
140
+ vectors: Sequence[np.ndarray[Any, np.dtype[np.floating[Any]]]],
141
+ ) -> list[float]:
142
+ """:func:`cosine_similarity` of ``query`` against each vector, in one pass.
143
+
144
+ The same formula, the same ``1e-10`` epsilon and the same ``[0, 1]`` clamp,
145
+ computed over a stacked matrix instead of once per pair: on the
146
+ query-latency baseline the per-pair loop cost 81 ms at 56k candidates and
147
+ the matrix form 6 ms. A value can differ from the per-pair one in its last
148
+ bit, because the dot products are summed in another order. Each row is
149
+ reduced by the same loop wherever it sits in the matrix (``einsum``, not a
150
+ BLAS kernel that blocks rows by position), so two identical vectors always
151
+ get identical scores and keep their tie.
152
+ """
153
+ if not vectors:
154
+ return []
155
+ matrix = np.stack(vectors)
156
+ dots = np.einsum("ij,j->i", matrix, query)
157
+ norms = np.sqrt(np.einsum("ij,ij->i", matrix, matrix))
158
+ raw = dots / (norms * np.linalg.norm(query) + 1e-10)
159
+ clamped: list[float] = np.clip(raw, 0.0, 1.0).tolist()
160
+ return clamped
161
+
162
+
137
163
  class EmbeddingModel:
138
164
  """Protocol-compatible wrapper around sentence-transformers SentenceTransformer."""
139
165