linkedparticles-core 1.149.0__tar.gz → 1.151.18__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.
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/PKG-INFO +3 -3
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/README.md +1 -1
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/core/conflict_resolution.py +166 -2
- linkedparticles_core-1.151.18/particles/core/conflict_review.py +310 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/core/equivalence.py +46 -0
- linkedparticles_core-1.151.18/particles/core/provenance.py +38 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/extraction/journal.py +2 -2
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/pyproject.toml +5 -3
- linkedparticles_core-1.151.18/tests/test_core_provenance.py +81 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/LICENSE +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/NOTICE +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/artifacts/conformance/README.md +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/artifacts/conformance/profile.yaml +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/artifacts/conformance/similarity_vectors.json +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/artifacts/schemas/context.jsonld +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/artifacts/schemas/interchange.schema.json +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/artifacts/schemas/particle.schema.json +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/artifacts/schemas/shacl/CorpusSnapshotShape.ttl +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/artifacts/schemas/shacl/ParticleShape.ttl +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/artifacts/schemas/shacl/ProvenanceChainShape.ttl +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/artifacts/schemas/shacl/SubjectShape.ttl +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/artifacts/schemas/shacl/TrustStatementShape.ttl +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/artifacts/schemas/trust_lens.schema.json +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/constraints.txt +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/__init__.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/config.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/conformance/__init__.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/conformance/_resources.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/conformance/contract.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/conformance/fixtures.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/conformance/jsonschema.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/conformance/profile.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/conformance/runner.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/conformance/shacl.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/conformance/types.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/conformance/validator.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/core/__init__.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/core/_resources.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/core/cascade_gate.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/core/claims.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/core/duplicate_key.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/core/fingerprint.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/core/granularity.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/core/jsonld_context.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/core/observer_scope.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/core/schema.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/core/scoring/__init__.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/core/scoring/confidence.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/core/scoring/decay.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/core/scoring/relevance.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/core/scoring/utility.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/core/stance.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/core/status.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/embeddings.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/extraction/__init__.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/extraction/calibration.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/extraction/docstrings.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/extraction/general.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/extraction/github/__init__.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/extraction/github/_shared.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/extraction/github/gist.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/extraction/github/pages.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/extraction/github/repo.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/extraction/hackernews.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/extraction/incremental.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/extraction/mastodon.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/extraction/mcp_memory.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/extraction/migration_preview.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/extraction/nomisma.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/extraction/numista/__init__.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/extraction/numista/_shared.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/extraction/numista/coin.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/extraction/numista/issuer.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/extraction/numista/listing.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/extraction/polarity.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/extraction/property_keys.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/extraction/rdf.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/extraction/reddit.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/extraction/registry.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/extraction/scope.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/extraction/structure.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/extraction/subject_gate.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/extraction/subject_scope.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/extraction/taxonomy.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/extraction/tool_turns.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/extraction/trust_lens.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/extraction/wikidata.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/http.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/interchange/__init__.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/interchange/codec.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/interchange/jsonl.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/interchange/yaml_ld.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/llm/__init__.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/llm/adapters/__init__.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/llm/adapters/anthropic.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/llm/adapters/local.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/llm/adapters/openai_compat.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/llm/client.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/llm/errors.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/llm/fencing.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/llm/pool.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/llm/registry.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/py.typed +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/render/__init__.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/render/markdown.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/secrets.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/url_canonical.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/url_safety.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/__init__.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/_client_fixtures.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/_upstream.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/MANIFEST.yaml +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/hackernews-thread-001/content.bin +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/hackernews-thread-001/manifest.yaml +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/hackernews-thread-001/snapshot.json +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/journal-entry-001/content.bin +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/journal-entry-001/manifest.yaml +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/journal-entry-001/snapshot.json +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/mastodon-thread-001/content.bin +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/mastodon-thread-001/manifest.yaml +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/mastodon-thread-001/snapshot.json +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/mcp-memory-export-001/content.bin +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/mcp-memory-export-001/manifest.yaml +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/mcp-memory-export-001/snapshot.json +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/nomisma-concept-001/content.bin +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/nomisma-concept-001/manifest.yaml +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/nomisma-concept-001/snapshot.json +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/numista-coin-001/content.bin +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/numista-coin-001/manifest.yaml +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/numista-coin-001/snapshot.json +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/numista-issuer-001/content.bin +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/numista-issuer-001/manifest.yaml +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/numista-issuer-001/snapshot.json +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/numista-listing-001/content.bin +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/numista-listing-001/manifest.yaml +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/numista-listing-001/snapshot.json +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/rdf-graph-001/content.bin +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/rdf-graph-001/manifest.yaml +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/rdf-graph-001/snapshot.json +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/reddit-thread-001/content.bin +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/reddit-thread-001/manifest.yaml +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/reddit-thread-001/snapshot.json +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/taxonomy-coins-001/content.bin +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/taxonomy-coins-001/manifest.yaml +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/taxonomy-coins-001/snapshot.json +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/trust-lens-001/content.bin +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/trust-lens-001/manifest.yaml +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/trust-lens-001/snapshot.json +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/web-article-001/content.bin +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/web-article-001/manifest.yaml +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/web-article-001/snapshot.json +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/web-article-002/content.bin +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/web-article-002/manifest.yaml +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/web-article-002/snapshot.json +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/web-essay-001/content.bin +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/web-essay-001/manifest.yaml +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/web-essay-001/snapshot.json +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/web-interview-001/content.bin +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/web-interview-001/manifest.yaml +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/web-interview-001/snapshot.json +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/wikidata-entity-001/content.bin +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/wikidata-entity-001/manifest.yaml +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conformance/fixtures/wikidata-entity-001/snapshot.json +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/conftest.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/fixtures/scope/normative-rule-file.md +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/fixtures/scope/normative-rule-file.recorded.json +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/fixtures/scope/rule-file-agents-md.md +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/fixtures/scope/rule-file-agents-md.recorded.json +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/test_confidence.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/test_config_client_sections.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/test_conformance_fixture_gen.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/test_conformance_similarity_vectors.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/test_conformance_validator.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/test_core_observer_scope.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/test_embeddings.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/test_extraction_vision.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/test_interchange_schema.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/test_llm_pool.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/test_schema.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/test_secrets.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/test_url_canonical.py +0 -0
- {linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/tests/test_validity_extraction.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: linkedparticles-core
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.151.18
|
|
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
|
|
@@ -44,7 +44,7 @@ Requires-Dist: pyyaml>=6.0
|
|
|
44
44
|
Requires-Dist: rdflib<8,>=7.0
|
|
45
45
|
Requires-Dist: scipy>=1.13
|
|
46
46
|
Requires-Dist: sentence-transformers>=2.7
|
|
47
|
-
Requires-Dist: sqlalchemy
|
|
47
|
+
Requires-Dist: sqlalchemy[asyncio]<2.2,>=2.0
|
|
48
48
|
Requires-Dist: torch
|
|
49
49
|
Provides-Extra: vision
|
|
50
50
|
Requires-Dist: pypdfium2>=4.30; extra == 'vision'
|
|
@@ -76,7 +76,7 @@ wire format, and the extraction pipeline. Everything you need to *make* and
|
|
|
76
76
|
> an MCP server), install
|
|
77
77
|
> **[`linkedparticles`](https://pypi.org/project/linkedparticles/)** instead.
|
|
78
78
|
> It depends on this package and pulls it in automatically. This one is the
|
|
79
|
-
>
|
|
79
|
+
> foundation it is built on. Its repository,
|
|
80
80
|
> **[`particles-engine-py`](https://github.com/LinkedParticles/particles-engine-py)**,
|
|
81
81
|
> is the reference implementation's front door: issues, discussion, and
|
|
82
82
|
> documentation live there.
|
|
@@ -24,7 +24,7 @@ wire format, and the extraction pipeline. Everything you need to *make* and
|
|
|
24
24
|
> an MCP server), install
|
|
25
25
|
> **[`linkedparticles`](https://pypi.org/project/linkedparticles/)** instead.
|
|
26
26
|
> It depends on this package and pulls it in automatically. This one is the
|
|
27
|
-
>
|
|
27
|
+
> foundation it is built on. Its repository,
|
|
28
28
|
> **[`particles-engine-py`](https://github.com/LinkedParticles/particles-engine-py)**,
|
|
29
29
|
> is the reference implementation's front door: issues, discussion, and
|
|
30
30
|
> documentation live there.
|
{linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/core/conflict_resolution.py
RENAMED
|
@@ -12,10 +12,13 @@ trust inputs already resolved by the caller, return a ``ConflictVerdict``
|
|
|
12
12
|
saying what should happen. It also owns the constructor that builds an
|
|
13
13
|
``INCONSISTENCY`` ``Particle`` from a conflicting pair.
|
|
14
14
|
|
|
15
|
+
It also owns the overrides around the verdict (:func:`decide_ladder`): the
|
|
16
|
+
tri-state probe, the observer precondition, and which rung 2.5 input applies.
|
|
15
17
|
The *effect* half of the ladder — DB writes, trust-rank lookups, embedding
|
|
16
18
|
similarity computation, the LLM contradiction-signal call — stays in
|
|
17
|
-
``particles/
|
|
18
|
-
|
|
19
|
+
``particles/ingest/`` because it touches I/O: ``conflict_plan`` maps an outcome
|
|
20
|
+
to writes, and ``pipeline`` gathers the inputs and applies the plan. Core code
|
|
21
|
+
must remain pure (see ``particles/core/AGENTS.md``).
|
|
19
22
|
|
|
20
23
|
Ladder (normative, applied in order — §6.4, rung for rung):
|
|
21
24
|
|
|
@@ -83,9 +86,11 @@ Two extra verdicts are emitted by the pre-ladder gate the caller may apply:
|
|
|
83
86
|
|
|
84
87
|
from __future__ import annotations
|
|
85
88
|
|
|
89
|
+
from dataclasses import dataclass
|
|
86
90
|
from enum import StrEnum
|
|
87
91
|
from typing import Any
|
|
88
92
|
|
|
93
|
+
from particles.core.observer_scope import PairPrecondition
|
|
89
94
|
from particles.core.schema import (
|
|
90
95
|
SCHEMA_VERSION,
|
|
91
96
|
Confidence,
|
|
@@ -309,6 +314,165 @@ def resolve_conflict(
|
|
|
309
314
|
return ConflictVerdict.INCONSISTENT
|
|
310
315
|
|
|
311
316
|
|
|
317
|
+
@dataclass(frozen=True)
|
|
318
|
+
class RungInputs:
|
|
319
|
+
"""The gathered §6.4 rung inputs for one pair; the defaults are "no input".
|
|
320
|
+
|
|
321
|
+
The caller resolves these only when :func:`needs_rung_inputs` says the
|
|
322
|
+
ladder can consult them (a confirmed or fail-closed signal on a pair the
|
|
323
|
+
observer precondition does not decline).
|
|
324
|
+
"""
|
|
325
|
+
|
|
326
|
+
new_supersedes_existing: bool = False
|
|
327
|
+
"""Rung 1.5: ``new``'s document (transitively) supersedes ``existing``'s."""
|
|
328
|
+
existing_supersedes_new: bool = False
|
|
329
|
+
"""Rung 1.5 mirror."""
|
|
330
|
+
trust_score_new: float | None = None
|
|
331
|
+
"""Rung 2: the candidate's pre-resolved trust score."""
|
|
332
|
+
trust_score_existing: float | None = None
|
|
333
|
+
"""Rung 2: the existing particle's pre-resolved trust score."""
|
|
334
|
+
update_order: int | None = None
|
|
335
|
+
"""Rung 2.5: see :func:`resolve_conflict`'s ``update_order``."""
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
@dataclass(frozen=True)
|
|
339
|
+
class LadderOutcome:
|
|
340
|
+
"""What the §6.6 ladder decided for one pair, overrides included."""
|
|
341
|
+
|
|
342
|
+
verdict: ConflictVerdict | None
|
|
343
|
+
"""The verdict; ``None`` when the observer precondition declined the pair:
|
|
344
|
+
the candidate is written ``ACTIVE`` beside the existing claim."""
|
|
345
|
+
record_divergence: bool
|
|
346
|
+
"""Declined and the signal is set: the pair is recorded as a divergence."""
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
class UpdateOrderSource(StrEnum):
|
|
350
|
+
"""Which rung 2.5 input the caller gathers for a pair."""
|
|
351
|
+
|
|
352
|
+
UPDATE = "update"
|
|
353
|
+
"""Extraction's same-lineage update order."""
|
|
354
|
+
OWN_ASSERTION = "own_assertion"
|
|
355
|
+
"""The assertion pathway's own-assertion order."""
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def ladder_signal(probe: bool | None, *, fail_closed: bool) -> bool:
|
|
359
|
+
"""The contradiction signal the ladder runs on, from a tri-state probe.
|
|
360
|
+
|
|
361
|
+
``True``/``False`` is a verdict. ``None`` means the probe could not
|
|
362
|
+
complete: extraction stays fail-open (no signal), and the assertion pathway
|
|
363
|
+
fails closed (a signal, which :func:`forces_inconsistent` then overrides to
|
|
364
|
+
``INCONSISTENT``).
|
|
365
|
+
"""
|
|
366
|
+
if probe is None:
|
|
367
|
+
return fail_closed
|
|
368
|
+
return probe
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def forces_inconsistent(
|
|
372
|
+
probe: bool | None, *, fail_closed: bool, precondition: PairPrecondition
|
|
373
|
+
) -> bool:
|
|
374
|
+
"""Whether the verdict is ``INCONSISTENT`` whatever the rungs would say.
|
|
375
|
+
|
|
376
|
+
Two overrides: an incomplete probe under ``fail_closed``, and
|
|
377
|
+
a project's candidate contesting a global claim with a signal (
|
|
378
|
+
``REVIEW``). A ``DECLINE`` outranks both; :func:`decide_ladder` checks it
|
|
379
|
+
first.
|
|
380
|
+
"""
|
|
381
|
+
if probe is None and fail_closed:
|
|
382
|
+
return True
|
|
383
|
+
return precondition is PairPrecondition.REVIEW and ladder_signal(probe, fail_closed=fail_closed)
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def needs_rung_inputs(
|
|
387
|
+
probe: bool | None, *, fail_closed: bool, precondition: PairPrecondition
|
|
388
|
+
) -> bool:
|
|
389
|
+
"""Whether the caller should gather :class:`RungInputs` for the pair.
|
|
390
|
+
|
|
391
|
+
Only a set signal reaches the rungs, and a declined pair reaches none. A
|
|
392
|
+
forced ``INCONSISTENT`` still gathers: the domain read alongside the trust
|
|
393
|
+
scores is the INCONSISTENCY record's ``domain_hint``.
|
|
394
|
+
"""
|
|
395
|
+
if precondition is PairPrecondition.DECLINE:
|
|
396
|
+
return False
|
|
397
|
+
return ladder_signal(probe, fail_closed=fail_closed)
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def update_order_source(
|
|
401
|
+
*,
|
|
402
|
+
allow_update: bool,
|
|
403
|
+
allow_own_assertion: bool,
|
|
404
|
+
forced: bool,
|
|
405
|
+
) -> UpdateOrderSource | None:
|
|
406
|
+
"""Which rung 2.5 input applies, if any.
|
|
407
|
+
|
|
408
|
+
``allow_update`` is extraction's door with the config switch already folded
|
|
409
|
+
in; ``allow_own_assertion`` the assertion pathway's narrower one.
|
|
410
|
+
A forced ``INCONSISTENT`` consults neither.
|
|
411
|
+
"""
|
|
412
|
+
if forced:
|
|
413
|
+
return None
|
|
414
|
+
if allow_update:
|
|
415
|
+
return UpdateOrderSource.UPDATE
|
|
416
|
+
if allow_own_assertion:
|
|
417
|
+
return UpdateOrderSource.OWN_ASSERTION
|
|
418
|
+
return None
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def effective_single_trust_order(override: bool | None, store_mode: str) -> bool:
|
|
422
|
+
"""The trust regime the ladder runs under.
|
|
423
|
+
|
|
424
|
+
A caller's explicit ``override`` wins; otherwise the store mode decides.
|
|
425
|
+
"""
|
|
426
|
+
if override is not None:
|
|
427
|
+
return override
|
|
428
|
+
return store_mode == "single"
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def decide_ladder(
|
|
432
|
+
existing: Particle,
|
|
433
|
+
new: Particle,
|
|
434
|
+
*,
|
|
435
|
+
probe: bool | None,
|
|
436
|
+
fail_closed: bool,
|
|
437
|
+
precondition: PairPrecondition,
|
|
438
|
+
rung_inputs: RungInputs | None,
|
|
439
|
+
single_trust_order: bool,
|
|
440
|
+
trust_differential_threshold: float,
|
|
441
|
+
) -> LadderOutcome:
|
|
442
|
+
"""The §6.6 ladder for one pair, with every override applied in order.
|
|
443
|
+
|
|
444
|
+
Pure: the caller gathers the probe, the precondition and (when
|
|
445
|
+
:func:`needs_rung_inputs`) the rung inputs, then applies the outcome. The
|
|
446
|
+
override order is:
|
|
447
|
+
|
|
448
|
+
1. ``DECLINE``: no rung runs, whatever the probe said. The
|
|
449
|
+
divergence is recorded when the signal is set, a fail-closed
|
|
450
|
+
incomplete probe included.
|
|
451
|
+
2. a forced ``INCONSISTENT`` (:func:`forces_inconsistent`): the rungs are
|
|
452
|
+
skipped.
|
|
453
|
+
3. otherwise :func:`resolve_conflict` on the signal and the rung inputs.
|
|
454
|
+
"""
|
|
455
|
+
signal = ladder_signal(probe, fail_closed=fail_closed)
|
|
456
|
+
if precondition is PairPrecondition.DECLINE:
|
|
457
|
+
return LadderOutcome(verdict=None, record_divergence=signal)
|
|
458
|
+
if forces_inconsistent(probe, fail_closed=fail_closed, precondition=precondition):
|
|
459
|
+
return LadderOutcome(verdict=ConflictVerdict.INCONSISTENT, record_divergence=False)
|
|
460
|
+
inputs = rung_inputs if rung_inputs is not None else RungInputs()
|
|
461
|
+
verdict = resolve_conflict(
|
|
462
|
+
existing,
|
|
463
|
+
new,
|
|
464
|
+
has_contradiction_signal=signal,
|
|
465
|
+
new_supersedes_existing=inputs.new_supersedes_existing,
|
|
466
|
+
existing_supersedes_new=inputs.existing_supersedes_new,
|
|
467
|
+
trust_score_existing=inputs.trust_score_existing,
|
|
468
|
+
trust_score_new=inputs.trust_score_new,
|
|
469
|
+
trust_differential_threshold=trust_differential_threshold,
|
|
470
|
+
single_trust_order=single_trust_order,
|
|
471
|
+
update_order=inputs.update_order,
|
|
472
|
+
)
|
|
473
|
+
return LadderOutcome(verdict=verdict, record_divergence=False)
|
|
474
|
+
|
|
475
|
+
|
|
312
476
|
# the ``properties`` marker on an INCONSISTENCY record whose
|
|
313
477
|
# "Particle A" is a judgment-retired twin rather than a live claim. Its value
|
|
314
478
|
# is the retired twin's ``status_reason``. Review reads it to know that
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 The Particles authors
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
"""Pure INCONSISTENCY-resolution decisions shared by Review and the trust cascade.
|
|
6
|
+
|
|
7
|
+
An INCONSISTENCY wrapper names two conflicting claims, A and B. Two operations
|
|
8
|
+
close it: a human review (§9.6, :mod:`particles.operations.review`) and the
|
|
9
|
+
source trust cascade (Extension B, :mod:`particles.operations.cascade`). Both
|
|
10
|
+
decide the same things — which claim loses, what demoting it means given its
|
|
11
|
+
current status, whether a quarantined claim is promoted, and which corpus
|
|
12
|
+
entry a trust statement is keyed on — and those decisions are pure functions
|
|
13
|
+
of the wrapper, the two claims and (for the cascade) their trust ranks.
|
|
14
|
+
|
|
15
|
+
They live here so each rule exists once and is testable without a database
|
|
16
|
+
(D2). The operations keep the I/O half: they gather the wrapper and
|
|
17
|
+
the claims, call these functions, and apply the plan in their existing write
|
|
18
|
+
order. Same split as :mod:`particles.core.cascade_gate`.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from dataclasses import dataclass
|
|
24
|
+
from enum import Enum
|
|
25
|
+
|
|
26
|
+
from particles.core.conflict_resolution import RETIRED_VALUE_KEY
|
|
27
|
+
from particles.core.schema import Particle, ProvenanceRefType, ResolutionAction
|
|
28
|
+
from particles.core.status import Status, StatusReason
|
|
29
|
+
|
|
30
|
+
_TERMINAL = (Status.RETRACTED, Status.SUPERSEDED)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Demotion(Enum):
|
|
34
|
+
"""What demoting a conflict's losing claim takes, given its current status."""
|
|
35
|
+
|
|
36
|
+
NONE = "none"
|
|
37
|
+
"""Nothing to write: the claim is missing (a pre-ADR-0117 dangling ref),
|
|
38
|
+
already terminal, or stale for a reason other than a pending
|
|
39
|
+
conflict."""
|
|
40
|
+
|
|
41
|
+
REASON_FLIP = "reason_flip"
|
|
42
|
+
"""A quarantined claim: ``CONFLICT_PENDING → CONFLICT_RESOLVED``
|
|
43
|
+
in place, with no status transition."""
|
|
44
|
+
|
|
45
|
+
TRANSITION = "transition"
|
|
46
|
+
"""A live claim: transition to ``PROVENANCE_STALE`` / ``CONFLICT_RESOLVED``."""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass(frozen=True)
|
|
50
|
+
class AleatoryMark:
|
|
51
|
+
"""One BOTH_VALID claim to keep queryable with ``ALEATORY`` nature."""
|
|
52
|
+
|
|
53
|
+
particle: Particle
|
|
54
|
+
mint: bool
|
|
55
|
+
"""True for a quarantined claim, which is promoted to a new ACTIVE
|
|
56
|
+
particle carrying the nature; False to update the nature in place."""
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass(frozen=True)
|
|
60
|
+
class TrustJudgment:
|
|
61
|
+
"""The SourceTrustStatement a PREFER review writes, before it is minted."""
|
|
62
|
+
|
|
63
|
+
source_entry_id: str
|
|
64
|
+
"""Corpus entry the statement is keyed on."""
|
|
65
|
+
preferred_id: str
|
|
66
|
+
"""The preferred claim as stored. When the plan promotes it, the operation
|
|
67
|
+
names the minted particle instead."""
|
|
68
|
+
demoted_id: str | None
|
|
69
|
+
"""The other wrapper ref, which may dangle on a pre-ADR-0117 wrapper."""
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass(frozen=True)
|
|
73
|
+
class ResolutionPlan:
|
|
74
|
+
"""Every write a review resolution makes, decided up front.
|
|
75
|
+
|
|
76
|
+
The operation applies the fields in declaration order, then closes (or,
|
|
77
|
+
for DEFER, re-sets) the wrapper, and only then runs the trust cascade
|
|
78
|
+
when ``trust`` is set. The wrapper closes first so the cascade never
|
|
79
|
+
re-processes the resolution just made.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
demote: tuple[Particle, Demotion] | None
|
|
83
|
+
promote: Particle | None
|
|
84
|
+
aleatory: tuple[AleatoryMark, ...]
|
|
85
|
+
trust: TrustJudgment | None
|
|
86
|
+
"""None: no trust statement is written, and no cascade runs."""
|
|
87
|
+
close_wrapper: bool
|
|
88
|
+
"""False only for DEFER, the one action that leaves the wrapper open."""
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@dataclass(frozen=True)
|
|
92
|
+
class CascadeVerdict:
|
|
93
|
+
"""A trust-cascade resolution of one wrapper."""
|
|
94
|
+
|
|
95
|
+
winner: Particle
|
|
96
|
+
loser: Particle
|
|
97
|
+
loser_demotion: Demotion
|
|
98
|
+
promote_winner: bool
|
|
99
|
+
"""True when the winner is quarantined: it is promoted to a new ACTIVE
|
|
100
|
+
particle, as a PREFER_B review would."""
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def is_quarantined(particle: Particle) -> bool:
|
|
104
|
+
"""True for a §6.6 quarantined conflict loser."""
|
|
105
|
+
return (
|
|
106
|
+
particle.status is Status.PROVENANCE_STALE
|
|
107
|
+
and particle.status_reason is StatusReason.CONFLICT_PENDING
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def conflict_pair_ids(inconsistency: Particle) -> tuple[str | None, str | None]:
|
|
112
|
+
"""The ids of claims A and B named by an INCONSISTENCY wrapper.
|
|
113
|
+
|
|
114
|
+
By convention the first PARTICLE provenance ref is A and the second is B;
|
|
115
|
+
the referenced particle id travels in ``corpus_entry_id``. A missing ref
|
|
116
|
+
reads as None.
|
|
117
|
+
"""
|
|
118
|
+
refs = [
|
|
119
|
+
r.corpus_entry_id for r in inconsistency.provenance if r.type is ProvenanceRefType.PARTICLE
|
|
120
|
+
]
|
|
121
|
+
return (
|
|
122
|
+
refs[0] if len(refs) > 0 else None,
|
|
123
|
+
refs[1] if len(refs) > 1 else None,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def is_retired_value(inconsistency: Particle) -> bool:
|
|
128
|
+
"""Whether the wrapper records a retired value rather than a source conflict.
|
|
129
|
+
|
|
130
|
+
A retired-value record pairs a judgment-retired twin (A) with a held
|
|
131
|
+
re-assertion (B). Resolving it rules on the earlier judgment, not on a
|
|
132
|
+
source, so neither review nor the cascade writes a trust verdict for it.
|
|
133
|
+
"""
|
|
134
|
+
return bool(inconsistency.properties and inconsistency.properties.get(RETIRED_VALUE_KEY))
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def decide_demotion(loser: Particle | None) -> Demotion:
|
|
138
|
+
"""What demoting a conflict's losing claim takes.
|
|
139
|
+
|
|
140
|
+
A missing loser (a pre-ADR-0117 wrapper's dangling ref) and a terminal one
|
|
141
|
+
(a judgment-retired twin) need nothing: there is no legal
|
|
142
|
+
transition out of a terminal state. A stale loser is already demoted; only
|
|
143
|
+
a quarantined one has its reason flipped.
|
|
144
|
+
"""
|
|
145
|
+
if loser is None or loser.status in _TERMINAL:
|
|
146
|
+
return Demotion.NONE
|
|
147
|
+
if loser.status is Status.PROVENANCE_STALE:
|
|
148
|
+
if loser.status_reason is StatusReason.CONFLICT_PENDING:
|
|
149
|
+
return Demotion.REASON_FLIP
|
|
150
|
+
return Demotion.NONE
|
|
151
|
+
return Demotion.TRANSITION
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _source_entry_id(particle: Particle) -> str | None:
|
|
155
|
+
"""The corpus entry of the claim's first SOURCE provenance ref."""
|
|
156
|
+
ref = next((r for r in particle.provenance if r.type is ProvenanceRefType.SOURCE), None)
|
|
157
|
+
return ref.corpus_entry_id if ref is not None else None
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def trust_statement_source(preferred: Particle | None) -> str | None:
|
|
161
|
+
"""The corpus-entry id a PREFER judgment's trust statement is keyed on.
|
|
162
|
+
|
|
163
|
+
Every consumer of a SourceTrustStatement matches its ``source_ref``
|
|
164
|
+
against a corpus-entry id: the §6.4 layered cascade and the reviewer
|
|
165
|
+
confirmation gate. The statement is therefore keyed on
|
|
166
|
+
the corpus entry of the preferred claim's SOURCE provenance, never on the
|
|
167
|
+
particle id, which no consumer could look up.
|
|
168
|
+
|
|
169
|
+
Returns None, meaning no statement is written, when the preferred claim is
|
|
170
|
+
unknown (a dangling ref), is not ACTIVE (a retired claim is no source
|
|
171
|
+
recommendation), or has no SOURCE provenance (an agent-asserted or
|
|
172
|
+
derived claim names no corpus entry).
|
|
173
|
+
"""
|
|
174
|
+
if preferred is None or preferred.status is not Status.ACTIVE:
|
|
175
|
+
return None
|
|
176
|
+
return _source_entry_id(preferred)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def decide_resolution(
|
|
180
|
+
action: ResolutionAction,
|
|
181
|
+
inconsistency: Particle,
|
|
182
|
+
particle_a: Particle | None,
|
|
183
|
+
particle_b: Particle | None,
|
|
184
|
+
) -> ResolutionPlan:
|
|
185
|
+
"""Decide every write a review resolution makes (§9.6).
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
action: The reviewer's resolution.
|
|
189
|
+
inconsistency: The INCONSISTENCY wrapper.
|
|
190
|
+
particle_a: Claim A as stored, or None when its ref dangles.
|
|
191
|
+
particle_b: Claim B as stored, or None when its ref dangles.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
The plan the operation applies.
|
|
195
|
+
"""
|
|
196
|
+
a_id, b_id = conflict_pair_ids(inconsistency)
|
|
197
|
+
retired_value = is_retired_value(inconsistency)
|
|
198
|
+
|
|
199
|
+
match action:
|
|
200
|
+
case ResolutionAction.PREFER_A:
|
|
201
|
+
trust_source = trust_statement_source(particle_a)
|
|
202
|
+
trust = (
|
|
203
|
+
TrustJudgment(trust_source, particle_a.id, b_id)
|
|
204
|
+
if trust_source is not None and particle_a is not None and not retired_value
|
|
205
|
+
else None
|
|
206
|
+
)
|
|
207
|
+
return ResolutionPlan(
|
|
208
|
+
demote=_demote(particle_b),
|
|
209
|
+
promote=None,
|
|
210
|
+
aleatory=(),
|
|
211
|
+
trust=trust,
|
|
212
|
+
close_wrapper=True,
|
|
213
|
+
)
|
|
214
|
+
case ResolutionAction.PREFER_B:
|
|
215
|
+
# A quarantined B is minted as a new ACTIVE particle carrying its
|
|
216
|
+
# provenance verbatim, so the statement is keyed on B's source as
|
|
217
|
+
# if B were ACTIVE; any other B is judged as stored.
|
|
218
|
+
promote = particle_b if particle_b is not None and is_quarantined(particle_b) else None
|
|
219
|
+
trust_source = (
|
|
220
|
+
_source_entry_id(promote)
|
|
221
|
+
if promote is not None
|
|
222
|
+
else trust_statement_source(particle_b)
|
|
223
|
+
)
|
|
224
|
+
trust = (
|
|
225
|
+
TrustJudgment(trust_source, particle_b.id, a_id)
|
|
226
|
+
if trust_source is not None and particle_b is not None and not retired_value
|
|
227
|
+
else None
|
|
228
|
+
)
|
|
229
|
+
return ResolutionPlan(
|
|
230
|
+
demote=_demote(particle_a),
|
|
231
|
+
promote=promote,
|
|
232
|
+
aleatory=(),
|
|
233
|
+
trust=trust,
|
|
234
|
+
close_wrapper=True,
|
|
235
|
+
)
|
|
236
|
+
case ResolutionAction.BOTH_VALID:
|
|
237
|
+
marks = tuple(
|
|
238
|
+
AleatoryMark(particle=p, mint=is_quarantined(p))
|
|
239
|
+
for p in (particle_a, particle_b)
|
|
240
|
+
if p is not None
|
|
241
|
+
)
|
|
242
|
+
return ResolutionPlan(
|
|
243
|
+
demote=None, promote=None, aleatory=marks, trust=None, close_wrapper=True
|
|
244
|
+
)
|
|
245
|
+
case ResolutionAction.DEFER:
|
|
246
|
+
return ResolutionPlan(
|
|
247
|
+
demote=None, promote=None, aleatory=(), trust=None, close_wrapper=False
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _demote(loser: Particle | None) -> tuple[Particle, Demotion] | None:
|
|
252
|
+
return (loser, decide_demotion(loser)) if loser is not None else None
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def cascade_pair(
|
|
256
|
+
inconsistency: Particle, particle_a: Particle | None, particle_b: Particle | None
|
|
257
|
+
) -> tuple[Particle, Particle] | None:
|
|
258
|
+
"""The structural half of the cascade decision: is this wrapper rankable?
|
|
259
|
+
|
|
260
|
+
The trust cascade leaves a wrapper for a person when either claim is
|
|
261
|
+
missing (a pre-ADR-0117 dangling ref), when it records a retired value, or
|
|
262
|
+
when A has since left the surface by a terminal transition: source trust
|
|
263
|
+
cannot answer a question about an earlier judgment.
|
|
264
|
+
|
|
265
|
+
The operation calls this before looking up trust ranks, so a skipped
|
|
266
|
+
wrapper costs no rank lookups. :func:`decide_cascade` applies it again,
|
|
267
|
+
so it is a complete decision on its own.
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
``(A, B)`` to rank, or None when the wrapper is left open.
|
|
271
|
+
"""
|
|
272
|
+
if particle_a is None or particle_b is None:
|
|
273
|
+
return None
|
|
274
|
+
if is_retired_value(inconsistency) or particle_a.status in _TERMINAL:
|
|
275
|
+
return None
|
|
276
|
+
return particle_a, particle_b
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def decide_cascade(
|
|
280
|
+
inconsistency: Particle,
|
|
281
|
+
particle_a: Particle | None,
|
|
282
|
+
particle_b: Particle | None,
|
|
283
|
+
rank_a: float | None,
|
|
284
|
+
rank_b: float | None,
|
|
285
|
+
*,
|
|
286
|
+
differential_threshold: float,
|
|
287
|
+
) -> CascadeVerdict | None:
|
|
288
|
+
"""Decide whether the trust cascade resolves a wrapper, and for whom.
|
|
289
|
+
|
|
290
|
+
The claim whose source ranks higher wins, provided both ranks are known
|
|
291
|
+
and differ by at least ``differential_threshold``. On an exact tie that
|
|
292
|
+
clears a zero threshold, B wins.
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
The verdict, or None when the wrapper stays open for manual review.
|
|
296
|
+
"""
|
|
297
|
+
pair = cascade_pair(inconsistency, particle_a, particle_b)
|
|
298
|
+
if pair is None or rank_a is None or rank_b is None:
|
|
299
|
+
return None
|
|
300
|
+
diff = rank_a - rank_b
|
|
301
|
+
if abs(diff) < differential_threshold:
|
|
302
|
+
return None
|
|
303
|
+
a, b = pair
|
|
304
|
+
winner, loser = (a, b) if diff > 0 else (b, a)
|
|
305
|
+
return CascadeVerdict(
|
|
306
|
+
winner=winner,
|
|
307
|
+
loser=loser,
|
|
308
|
+
loser_demotion=decide_demotion(loser),
|
|
309
|
+
promote_winner=is_quarantined(winner),
|
|
310
|
+
)
|
{linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/core/equivalence.py
RENAMED
|
@@ -21,6 +21,10 @@ with cross-lingual detection and the ``language`` field.
|
|
|
21
21
|
|
|
22
22
|
from __future__ import annotations
|
|
23
23
|
|
|
24
|
+
from collections.abc import Iterable
|
|
25
|
+
|
|
26
|
+
from particles.core.schema import ParticleRelation
|
|
27
|
+
|
|
24
28
|
|
|
25
29
|
def effective_equivalence(link_confidence: float, *, observer_trust: float | None = None) -> float:
|
|
26
30
|
"""Observer-relative claim-equivalence strength in ``[0, 1]`` (§6.10).
|
|
@@ -38,3 +42,45 @@ def effective_equivalence(link_confidence: float, *, observer_trust: float | Non
|
|
|
38
42
|
if observer_trust is None:
|
|
39
43
|
return link_confidence
|
|
40
44
|
return max(0.0, min(1.0, link_confidence * observer_trust))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def co_evidential_components(
|
|
48
|
+
edges: Iterable[ParticleRelation], min_confidence: float
|
|
49
|
+
) -> dict[str, frozenset[str]]:
|
|
50
|
+
"""Group ids into CO_EVIDENTIAL components from the whole edge list at once.
|
|
51
|
+
|
|
52
|
+
The in-memory equivalent of running ``get_co_evidential_group`` per particle,
|
|
53
|
+
honouring the same ``effective_equivalence >= min_confidence`` gate, so a
|
|
54
|
+
caller reads the relation table once and decides "already linked" without a
|
|
55
|
+
session (D2). Ids with no qualifying edge are simply
|
|
56
|
+
absent: their component is the singleton the caller supplies.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
edges: CO_EVIDENTIAL edges, e.g. ``get_all_relations(session, CO_EVIDENTIAL)``.
|
|
60
|
+
min_confidence: Edges whose effective equivalence falls below this are
|
|
61
|
+
not traversed.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
Every id that sits on a qualifying edge, mapped to its whole component
|
|
65
|
+
(itself included).
|
|
66
|
+
"""
|
|
67
|
+
parent: dict[str, str] = {}
|
|
68
|
+
|
|
69
|
+
def find(x: str) -> str:
|
|
70
|
+
parent.setdefault(x, x)
|
|
71
|
+
while parent[x] != x:
|
|
72
|
+
parent[x] = parent[parent[x]]
|
|
73
|
+
x = parent[x]
|
|
74
|
+
return x
|
|
75
|
+
|
|
76
|
+
for e in edges:
|
|
77
|
+
if effective_equivalence(e.confidence) < min_confidence:
|
|
78
|
+
continue
|
|
79
|
+
a, b = find(e.particle_a), find(e.particle_b)
|
|
80
|
+
if a != b:
|
|
81
|
+
parent[a] = b
|
|
82
|
+
|
|
83
|
+
groups: dict[str, set[str]] = {}
|
|
84
|
+
for node in list(parent):
|
|
85
|
+
groups.setdefault(find(node), set()).add(node)
|
|
86
|
+
return {node: frozenset(members) for members in groups.values() for node in members}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 The Particles authors
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
"""The shape of an appended provenance observation (D1).
|
|
6
|
+
|
|
7
|
+
Provenance is append-only: a re-observation of a claim is a new ref, never an
|
|
8
|
+
edit of an old one. This module holds the pure rule for what that ref looks
|
|
9
|
+
like, so it is testable without a store; the write stays with
|
|
10
|
+
``particle_store.append_provenance_ref``, the one provenance writer.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from particles.core.schema import Particle, ProvenanceRef, ProvenanceRefType
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def reobservation_ref(particle: Particle, entry_id: str, snapshot_id: str) -> ProvenanceRef:
|
|
19
|
+
"""Return the ref recording that ``snapshot_id`` re-observed ``particle``.
|
|
20
|
+
|
|
21
|
+
The ref copies the particle's latest SOURCE ref to ``entry_id`` (its
|
|
22
|
+
location and chunk hash are the carried chunk's) and names the
|
|
23
|
+
re-observing snapshot. A particle with no SOURCE ref to the entry gets a
|
|
24
|
+
bare SOURCE ref naming the entry and snapshot.
|
|
25
|
+
"""
|
|
26
|
+
prior = next(
|
|
27
|
+
(
|
|
28
|
+
r
|
|
29
|
+
for r in reversed(particle.provenance)
|
|
30
|
+
if r.type is ProvenanceRefType.SOURCE and r.corpus_entry_id == entry_id
|
|
31
|
+
),
|
|
32
|
+
None,
|
|
33
|
+
)
|
|
34
|
+
if prior is not None:
|
|
35
|
+
return prior.model_copy(update={"snapshot_id": snapshot_id})
|
|
36
|
+
return ProvenanceRef(
|
|
37
|
+
type=ProvenanceRefType.SOURCE, corpus_entry_id=entry_id, snapshot_id=snapshot_id
|
|
38
|
+
)
|
{linkedparticles_core-1.149.0 → linkedparticles_core-1.151.18}/particles/extraction/journal.py
RENAMED
|
@@ -33,7 +33,7 @@ size — narrative grouping (one label, one sequence) is a whole-document concer
|
|
|
33
33
|
Over-length entries (> ``extraction.html_chunk_size``) are extracted in multiple
|
|
34
34
|
carry-forward passes and their per-chunk NARRATIVE fragments are
|
|
35
35
|
merged into one whole-entry NARRATIVE by the Engine-side post-pass
|
|
36
|
-
(:func:`particles.ingest.narrative_merge.
|
|
36
|
+
(:func:`particles.ingest.narrative_merge.collapse_narratives`).
|
|
37
37
|
"""
|
|
38
38
|
|
|
39
39
|
from __future__ import annotations
|
|
@@ -283,7 +283,7 @@ class JournalExtractor:
|
|
|
283
283
|
(2). Each cache-miss chunk yields its claims (chunk-local
|
|
284
284
|
``narrative_index``) plus one per-chunk NARRATIVE candidate; the Engine
|
|
285
285
|
post-pass
|
|
286
|
-
(:func:`particles.ingest.narrative_merge.
|
|
286
|
+
(:func:`particles.ingest.narrative_merge.collapse_narratives`)
|
|
287
287
|
collapses those fragments into one whole-entry NARRATIVE with a global
|
|
288
288
|
``SEQUENCE_IN`` order. Chunks beyond ``max_llm_calls_per_source`` emit
|
|
289
289
|
the shared ``CHUNK_TRUNCATION`` note — the new, much higher truncation
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
# version and advance together.
|
|
20
20
|
[project]
|
|
21
21
|
name = "linkedparticles-core"
|
|
22
|
-
version = "1.
|
|
22
|
+
version = "1.151.18"
|
|
23
23
|
description = "Store-free client library for Particles: define, validate, serialize, and extract provenance-tracked belief particles"
|
|
24
24
|
readme = "README.md"
|
|
25
25
|
requires-python = ">=3.11"
|
|
@@ -80,8 +80,10 @@ dependencies = [
|
|
|
80
80
|
# The extraction layer types its session parameters against SQLAlchemy's
|
|
81
81
|
# async session; the import is executed at module load even though no
|
|
82
82
|
# Client module opens a connection. Declared so a store-free install still
|
|
83
|
-
# imports
|
|
84
|
-
|
|
83
|
+
# imports, and with `[asyncio]` because that import needs greenlet, which
|
|
84
|
+
# SQLAlchemy stopped installing by default in 2.1. Bounded below the next
|
|
85
|
+
# minor: 2.1 changed ORM and typing behaviour this code depends on.
|
|
86
|
+
"sqlalchemy[asyncio]>=2.0,<2.2",
|
|
85
87
|
# JSON-LD / RDF / SHACL parsing stack — these handle attacker-influenced
|
|
86
88
|
# input (interchange import, conformance validation), so they carry tested
|
|
87
89
|
# next-breaking upper bounds in addition to the floor: a future breaking
|