Skip to content

Commit 1c5c673

Browse files
committed
fix(indicium): take the asb: base from indicium instead of hardcoding it
1 parent 547cfac commit 1c5c673

12 files changed

Lines changed: 64 additions & 30 deletions

File tree

pyproject.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ graph = [
134134
"pyoxigraph>=0.4",
135135
]
136136

137-
# The indicium stack — indicium, indicium-adapters,
137+
# The indicium stack — indicium-standard, indicium-adapters,
138138
# indicium-adapters-metabolomics — backs validate_claims(), build_claim_graph()
139139
# and export_astra(). Those packages are private and absent from PyPI, so they
140140
# are deliberately NOT declared here, in any extra, and NOT wired up through
@@ -146,7 +146,9 @@ graph = [
146146
# `uv-resolve` CI job fail if they come back.
147147
#
148148
# The PyPI name `indicium` belongs to an unrelated project (a key-value store,
149-
# latest 0.1.0a3), so a bare `indicium` requirement installs the wrong package.
149+
# latest 0.1.0a3) that also ships a top-level `indicium` module, so a bare
150+
# `indicium` requirement installs the wrong package and clobbers the import.
151+
# The standard publishes as `indicium-standard`; the import name is unchanged.
150152
#
151153
# Maintainers holding the private sibling checkouts install them into the
152154
# synced venv, without editing this file:

src/perspicacite/indicium_layer/queries.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,17 @@
1010

1111
from typing import Any
1212

13+
from indicium import ASB_BASE
14+
1315
# ---------- Namespaces (single source of truth across the package) ----------
1416

15-
ASB_NS = "https://asb.holobiomics.org/ns/asb#"
17+
# Read from indicium rather than restated, so the graphs this package builds are
18+
# always targeted by the SHACL shapes that validate them. A hardcoded copy went
19+
# stale when indicium 2.0.0 moved the base: the shapes then matched nothing and
20+
# every claim graph reported conforms=True without a single constraint checked.
21+
# Importing indicium at module scope is safe here — this package is the indicium
22+
# adapter layer, and its builder already requires it.
23+
ASB_NS = ASB_BASE
1624
CITO_NS = "http://purl.org/spar/cito/"
1725
PROV_NS = "http://www.w3.org/ns/prov#"
1826
FABIO_NS = "http://purl.org/spar/fabio/"

src/perspicacite/indicium_layer/store.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,16 @@
2525
from rdflib import Dataset, URIRef
2626
from rdflib import Literal as RdflibLiteral
2727

28+
from perspicacite.indicium_layer.queries import ASB_NS
29+
2830
# rdflib ≥ 6.0 deprecates ConjunctiveGraph in favour of Dataset.
2931
# Dataset is a drop-in for our usage (add/query named graphs).
3032
ConjunctiveGraph = Dataset # alias for backward compat in type hints below
3133

3234
_RDF_SUBJECT = URIRef("http://www.w3.org/1999/02/22-rdf-syntax-ns#subject")
3335
_RDF_PREDICATE = URIRef("http://www.w3.org/1999/02/22-rdf-syntax-ns#predicate")
3436
_RDF_OBJECT = URIRef("http://www.w3.org/1999/02/22-rdf-syntax-ns#object")
35-
_ASB_CONFIDENCE = URIRef("https://asb.holobiomics.org/ns/asb#confidence")
37+
_ASB_CONFIDENCE = URIRef(f"{ASB_NS}confidence")
3638
_PROV_WAS_GENERATED_BY = URIRef("http://www.w3.org/ns/prov#wasGeneratedBy")
3739
_XSD_DECIMAL = "http://www.w3.org/2001/XMLSchema#decimal"
3840

src/perspicacite/pipeline/claims.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -140,18 +140,21 @@ def _coerce_claim(c: dict, qualifiers: frozenset[str] = _QUALIFIERS) -> dict | N
140140
return claim
141141

142142

143-
_ASB = "https://asb.holobiomics.org/ns/asb#"
144-
145-
146143
def claims_to_graph(claims: list[dict]):
147144
"""Serialize claim dicts to an rdflib Graph using the asb: vocabulary that
148145
indicium's SHACL targets (a asb:Claim with asb:context/subject/...)."""
149146
import rdflib
150147

148+
# Imported lazily beside rdflib: this module is core pipeline and must stay
149+
# importable without the optional indicium stack. Taking the base from
150+
# indicium keeps the graph in the namespace its SHACL shapes target; a
151+
# hardcoded copy goes stale on a base move and then validates vacuously.
152+
from indicium import ASB_BASE, INDICIUM_BASE
153+
151154
g = rdflib.Graph()
152-
asb = rdflib.Namespace(_ASB)
155+
asb = rdflib.Namespace(ASB_BASE)
153156
SSSOM = rdflib.Namespace("https://w3id.org/sssom/")
154-
INDICIUM = rdflib.Namespace("https://w3id.org/indicium/")
157+
INDICIUM = rdflib.Namespace(INDICIUM_BASE)
155158
DCT = rdflib.Namespace("http://purl.org/dc/terms/")
156159
for i, c in enumerate(claims):
157160
cid = c.get("id") or f"pos:{i}"

src/perspicacite/rag/modes/reasoning/provenance.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
if TYPE_CHECKING:
2828
from collections.abc import AsyncIterator
2929

30+
from perspicacite.indicium_layer.queries import ASB_NS
3031
from perspicacite.logging import get_logger
3132
from perspicacite.models.rag import RAGRequest, SourceReference, StreamEvent
3233
from perspicacite.rag.modes.base import BaseRAGMode
@@ -58,7 +59,7 @@
5859
# Internal helpers
5960
# ---------------------------------------------------------------------------
6061

61-
_CLAIM_NS = "https://asb.holobiomics.org/ns/asb#"
62+
_CLAIM_NS = ASB_NS
6263
_RDF_TYPE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"
6364

6465

src/perspicacite/rag/modes/reasoning/typed_contradiction.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
if TYPE_CHECKING:
3434
from collections.abc import AsyncIterator
3535

36+
from perspicacite.indicium_layer.queries import ASB_NS
3637
from perspicacite.logging import get_logger
3738
from perspicacite.models.rag import RAGRequest, SourceReference, StreamEvent
3839
from perspicacite.rag.modes.base import BaseRAGMode
@@ -78,7 +79,7 @@
7879
# Internal helpers — shared with provenance pattern
7980
# ---------------------------------------------------------------------------
8081

81-
_CLAIM_NS = "https://asb.holobiomics.org/ns/asb#"
82+
_CLAIM_NS = ASB_NS
8283
_RDF_TYPE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"
8384

8485

tests/unit/test_anchor_builder.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import pytest
55

66
from perspicacite.indicium_layer.builder import build_claim_graph
7+
from perspicacite.indicium_layer.queries import ASB_NS
78
from perspicacite.indicium_layer.store import ClaimGraphStore
89

910

@@ -57,13 +58,13 @@ async def test_builder_binds_quote_to_content_matched_passage(tmp_path, monkeypa
5758
)
5859
# The verified quote_exact must be present on some Evidence node.
5960
rows = store.select(
60-
'SELECT ?q WHERE { ?e <https://asb.holobiomics.org/ns/asb#quoteExact> ?q }'
61+
f'SELECT ?q WHERE {{ ?e <{ASB_NS}quoteExact> ?q }}'
6162
)
6263
quotes = {r["q"] for r in rows}
6364
assert "compound A inhibits enzyme B" in quotes
6465
# anchorStatus verified is recorded.
6566
status_rows = store.select(
66-
'SELECT ?s WHERE { ?e <https://asb.holobiomics.org/ns/asb#anchorStatus> ?s }'
67+
f'SELECT ?s WHERE {{ ?e <{ASB_NS}anchorStatus> ?s }}'
6768
)
6869
assert "verified" in {r["s"] for r in status_rows}
6970
finally:
@@ -109,12 +110,12 @@ async def test_builder_does_not_launder_unverified_quote(tmp_path, monkeypatch):
109110
)
110111
# No quoteExact may be laundered onto any Evidence node.
111112
quote_rows = store.select(
112-
'SELECT ?q WHERE { ?e <https://asb.holobiomics.org/ns/asb#quoteExact> ?q }'
113+
f'SELECT ?q WHERE {{ ?e <{ASB_NS}quoteExact> ?q }}'
113114
)
114115
assert quote_rows == []
115116
# The claim is still kept (fail-open) and tagged unverified.
116117
status_rows = store.select(
117-
'SELECT ?s WHERE { ?e <https://asb.holobiomics.org/ns/asb#anchorStatus> ?s }'
118+
f'SELECT ?s WHERE {{ ?e <{ASB_NS}anchorStatus> ?s }}'
118119
)
119120
assert "unverified" in {r["s"] for r in status_rows}
120121
finally:

tests/unit/test_claim_graph_store.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
"""Unit tests for indicium_layer.store.ClaimGraphStore (rdflib backend)."""
22

3+
from perspicacite.indicium_layer.queries import ASB_NS
34
from perspicacite.indicium_layer.store import ClaimGraphStore
45

56
PROV_WAS_GEN_BY = "http://www.w3.org/ns/prov#wasGeneratedBy"
6-
ASB_CONFIDENCE = "https://asb.holobiomics.org/ns/asb#confidence"
7+
ASB_CONFIDENCE = f"{ASB_NS}confidence"
78
RDF_SUBJECT = "http://www.w3.org/1999/02/22-rdf-syntax-ns#subject"
89
CITO_SUPPORTS = "http://purl.org/spar/cito/supports"
910

@@ -32,11 +33,11 @@ def test_literal_with_datatype():
3233
xsd_decimal = "http://www.w3.org/2001/XMLSchema#decimal"
3334
store.add(
3435
"kb://kbA/x",
35-
"https://asb.holobiomics.org/ns/asb#confidence",
36+
ASB_CONFIDENCE,
3637
("literal", "0.82", xsd_decimal),
3738
)
3839
rows = store.select(
39-
"SELECT ?v WHERE { <kb://kbA/x> <https://asb.holobiomics.org/ns/asb#confidence> ?v }"
40+
f"SELECT ?v WHERE {{ <kb://kbA/x> <{ASB_CONFIDENCE}> ?v }}"
4041
)
4142
assert rows[0]["v"] == "0.82"
4243
store.close()

tests/unit/test_claims_extraction.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -309,8 +309,10 @@ def enrich_claim(self, c): return c
309309
def test_validate_claims_enrichment_and_shacl_combined():
310310
"""validate_claims() with an adapter that has both enrich_claim and shacl_shapes
311311
must call shacl_shapes() once (wiring integration check)."""
312-
import rdflib
313312
from unittest.mock import MagicMock, patch
313+
314+
import rdflib
315+
314316
from perspicacite.pipeline.claims import validate_claims
315317

316318
mock_graph = rdflib.Graph()
@@ -346,6 +348,7 @@ async def test_extract_claims_mcp_tool_passes_adapter_not_manual_enrich():
346348
"""
347349
import json
348350
from unittest.mock import AsyncMock, MagicMock, patch
351+
349352
from perspicacite.pipeline.claims import extract_claims
350353

351354
adapter = _MockAdapter()
@@ -383,8 +386,9 @@ async def test_extract_claims_mcp_tool_passes_adapter_not_manual_enrich():
383386
def test_claims_to_graph_serializes_ontology_terms():
384387
"""ontology_terms in a claim dict must appear as asb:{slot}_ontology_term triples."""
385388
import rdflib
389+
from indicium import ASB_BASE as _ASB
390+
386391
from perspicacite.pipeline.claims import claims_to_graph
387-
_ASB = "https://asb.holobiomics.org/ns/asb#"
388392
asb = rdflib.Namespace(_ASB)
389393
claim = {
390394
"context": "in vivo", "subject": "glucose", "qualifier": "quantifies",
@@ -403,8 +407,9 @@ def test_claims_to_graph_serializes_ontology_terms():
403407
def test_claims_to_graph_no_ontology_terms_no_error():
404408
"""Claims without ontology_terms must not raise and produce no extra triples."""
405409
import rdflib
410+
from indicium import ASB_BASE as _ASB
411+
406412
from perspicacite.pipeline.claims import claims_to_graph
407-
_ASB = "https://asb.holobiomics.org/ns/asb#"
408413
asb = rdflib.Namespace(_ASB)
409414
claim = {
410415
"context": "in vivo", "subject": "glucose", "qualifier": "quantifies",
@@ -419,8 +424,9 @@ def test_claims_to_graph_no_ontology_terms_no_error():
419424
def test_claims_to_graph_skips_none_ontology_terms():
420425
"""None values in ontology_terms must be skipped — not serialized as literal 'None'."""
421426
import rdflib
427+
from indicium import ASB_BASE as _ASB
428+
422429
from perspicacite.pipeline.claims import claims_to_graph
423-
_ASB = "https://asb.holobiomics.org/ns/asb#"
424430
asb = rdflib.Namespace(_ASB)
425431
claim = {
426432
"context": "in vivo", "subject": "glucose", "qualifier": "quantifies",
@@ -533,10 +539,11 @@ def test_compose_adapters_all_unknown_gives_none():
533539
@pytest.mark.unit
534540
def test_claims_to_graph_emits_anchor_status_and_quote_exact():
535541
import rdflib
542+
from indicium import ASB_BASE
536543

537544
from perspicacite.pipeline.claims import claims_to_graph
538545

539-
asb = rdflib.Namespace("https://asb.holobiomics.org/ns/asb#")
546+
asb = rdflib.Namespace(ASB_BASE)
540547
claims = [{
541548
"id": "c1", "context": "in vitro", "subject": "A",
542549
"qualifier": "inhibits", "relation": "inhibits", "object": "B",
@@ -551,10 +558,11 @@ def test_claims_to_graph_emits_anchor_status_and_quote_exact():
551558
@pytest.mark.unit
552559
def test_claims_to_graph_unverified_emits_status_but_not_quote():
553560
import rdflib
561+
from indicium import ASB_BASE
554562

555563
from perspicacite.pipeline.claims import claims_to_graph
556564

557-
asb = rdflib.Namespace("https://asb.holobiomics.org/ns/asb#")
565+
asb = rdflib.Namespace(ASB_BASE)
558566
claims = [{
559567
"id": "c2", "context": "in vitro", "subject": "A",
560568
"qualifier": "inhibits", "relation": "inhibits", "object": "B",

tests/unit/test_claims_validation.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import pytest
22
import rdflib
3+
34
from perspicacite.pipeline.claims import claims_to_graph, validate_claims
45

5-
ASB = rdflib.Namespace("https://asb.holobiomics.org/ns/asb#")
6+
indicium = pytest.importorskip("indicium")
7+
ASB = rdflib.Namespace(indicium.ASB_BASE)
68

79

810
@pytest.mark.unit
@@ -58,12 +60,13 @@ def test_claim_missing_slots_is_rejected():
5860

5961
@pytest.mark.unit
6062
def test_claims_to_graph_emits_sssom_mapping():
61-
import rdflib
6263
import indicium
64+
import rdflib
65+
6366
from perspicacite.pipeline.claims import claims_to_graph
6467

6568
SSSOM = rdflib.Namespace("https://w3id.org/sssom/")
66-
asb = rdflib.Namespace("https://asb.holobiomics.org/ns/asb#")
69+
asb = ASB
6770

6871
pytest.importorskip("indicium")
6972

0 commit comments

Comments
 (0)