You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sourcelume.apache.org by an...@apache.org on 2026/10/11 16:17:06 UTC

(sourcelume-spec) branch canonical-context-iri created (now 856044c)

This is an automated email from the ASF dual-hosted git repository.

ANierbeck pushed a change to branch canonical-context-iri
in repository https://gitbox.apache.org/repos/asf/sourcelume-spec.git


      at 856044c  Pin the canonical context IRI and move to the stable namespace

This branch includes the following new commits:

     new 856044c  Pin the canonical context IRI and move to the stable namespace

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@sourcelume.apache.org
For additional commands, e-mail: commits-help@sourcelume.apache.org


(sourcelume-spec) 01/01: Pin the canonical context IRI and move to the stable namespace

Posted by an...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ANierbeck pushed a commit to branch canonical-context-iri
in repository https://gitbox.apache.org/repos/asf/sourcelume-spec.git

commit 856044c1b97e0c0fd3ebad692e75b33a79264778
Author: anierbeck <ac...@codecentric.de>
AuthorDate: Sun Oct 11 18:16:57 2026 +0200

    Pin the canonical context IRI and move to the stable namespace
    
    Records may only reference the canonical context IRI
    https://sourcelume.apache.org/context/0.0.1/sourcelume.jsonld. The JSON
    Schema rejects inline @context objects and other IRIs, so the meaning of
    a record is fixed by the published context.
    
    The term namespace moves from /ns/0.0.1# to the stable /ns#; the context
    URL stays versioned. tools/validate.py resolves the canonical context
    from the bundled copy instead of fetching it.
    
    Apache-ai: Yes
    Generated-by: pi (claude-haiku-5-5)
    Reviewed-by: anierbeck
---
 CHANGELOG.md                        |  8 ++++++++
 context/0.0.1/sourcelume.jsonld     |  2 +-
 schema/0.0.1/sourcelume.schema.json |  5 ++++-
 schema/0.0.1/sourcelume.shacl.ttl   |  2 +-
 spec/0.0.1/index.md                 |  8 ++++++++
 tests/test_validate.py              | 32 ++++++++++++++++++++++++++------
 tools/validate.py                   |  7 ++++++-
 7 files changed, 54 insertions(+), 10 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 56f1c67..1eb5288 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,14 @@ described in `VERSIONING.md`.
  
 ## [Unreleased]
  
+### Changed
+
+- Namespace moved to the stable `https://sourcelume.apache.org/ns#`. The context URL stays
+  versioned (`/context/0.0.1/`).
+- The JSON Schema accepts only the canonical context IRI as `@context`. Inline context objects
+  and other IRIs are rejected.
+- `tools/validate.py` resolves the canonical context from the bundled copy instead of fetching it.
+
 ### Added
 
 - Added intended usage for custodyChain in spec.
diff --git a/context/0.0.1/sourcelume.jsonld b/context/0.0.1/sourcelume.jsonld
index 10ab6c2..5196b85 100644
--- a/context/0.0.1/sourcelume.jsonld
+++ b/context/0.0.1/sourcelume.jsonld
@@ -1,7 +1,7 @@
 {
   "@context": {
     "@version": 1.1,
-    "sl": "https://sourcelume.apache.org/ns/0.0.1#",
+    "sl": "https://sourcelume.apache.org/ns#",
     "dct": "http://purl.org/dc/terms/",
     "schema": "https://schema.org/",
     "spdx": "http://spdx.org/rdf/terms#",
diff --git a/schema/0.0.1/sourcelume.schema.json b/schema/0.0.1/sourcelume.schema.json
index bd4ec48..3935b63 100644
--- a/schema/0.0.1/sourcelume.schema.json
+++ b/schema/0.0.1/sourcelume.schema.json
@@ -18,7 +18,10 @@
     "custodyChain"
   ],
   "properties": {
-    "@context": {},
+    "@context": {
+      "const": "https://sourcelume.apache.org/context/0.0.1/sourcelume.jsonld",
+      "description": "Only the canonical context IRI is accepted. Inline @context objects and any other IRI are rejected, so a record's meaning is fixed by the published context."
+    },
     "id": {
       "type": "string",
       "format": "uri"
diff --git a/schema/0.0.1/sourcelume.shacl.ttl b/schema/0.0.1/sourcelume.shacl.ttl
index fe5a920..2f62836 100644
--- a/schema/0.0.1/sourcelume.shacl.ttl
+++ b/schema/0.0.1/sourcelume.shacl.ttl
@@ -1,5 +1,5 @@
 @prefix sh: <http://www.w3.org/ns/shacl#> .
-@prefix sl: <https://sourcelume.apache.org/ns/0.0.1#> .
+@prefix sl: <https://sourcelume.apache.org/ns#> .
 @prefix dct: <http://purl.org/dc/terms/> .
 @prefix schema: <https://schema.org/> .
 @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
diff --git a/spec/0.0.1/index.md b/spec/0.0.1/index.md
index 947bdcb..4141bd4 100644
--- a/spec/0.0.1/index.md
+++ b/spec/0.0.1/index.md
@@ -33,6 +33,14 @@ this identity, was produced/curated/distributed by these parties, is licensed un
 came from this origin, and passed through this chain of custody.* Sourcelume does not verify the
 claim's accuracy — it gives the claim a consistent shape so it *can* be verified by someone else.
 
+## JSON-LD context
+
+Records reference the context with the canonical IRI
+`https://sourcelume.apache.org/context/0.0.1/sourcelume.jsonld` and no other `@context` value.
+The context is published at that address and is not modified after release. Its namespace is the
+stable `https://sourcelume.apache.org/ns#`, so term IRIs stay the same across versions. Inline
+`@context` objects and other context IRIs are rejected.
+
 ## Fields
 
 | Field | Required | Type | Description |
diff --git a/tests/test_validate.py b/tests/test_validate.py
index 74bef74..ff87921 100644
--- a/tests/test_validate.py
+++ b/tests/test_validate.py
@@ -41,13 +41,9 @@ class ValidateToolTests(unittest.TestCase):
         with tempfile.TemporaryDirectory() as tmp:
             example_path = Path(tmp) / "valid.jsonld"
 
-            # Load the context from the local file
-            context_path = REPO_ROOT / "context" / "0.0.1" / "sourcelume.jsonld"
-            context = json.loads(context_path.read_text())
-
-            # Create a minimal valid ProvenanceRecord with inline context
+            # Create a minimal valid ProvenanceRecord with the canonical context IRI
             valid_record = {
-                "@context": context["@context"],
+                "@context": validate.CANONICAL_CONTEXT_IRI,
                 "id": "https://example.org/records/test",
                 "type": "ProvenanceRecord",
                 "identifier": "https://example.org/datasets/test",
@@ -82,6 +78,30 @@ class ValidateToolTests(unittest.TestCase):
             result = validate.validate_example(example_path, validator, shapes_path)
             self.assertTrue(result)
 
+    def test_schema_pins_the_canonical_context_iri(self) -> None:
+        """The schema's @context const and the validator's constant must agree."""
+        schema = validate.load_validator().schema
+        self.assertEqual(schema["properties"]["@context"]["const"], validate.CANONICAL_CONTEXT_IRI)
+
+    def test_inline_context_is_rejected(self) -> None:
+        """An inline @context object is not accepted, only the canonical IRI."""
+        validator = validate.load_validator()
+        context = json.loads((REPO_ROOT / "context" / "0.0.1" / "sourcelume.jsonld").read_text())
+        record = {"@context": context["@context"], "id": "https://example.org/records/test", "type": "ProvenanceRecord"}
+        errors = [e for e in validator.iter_errors(record) if list(e.path) == ["@context"]]
+        self.assertEqual(len(errors), 1)
+
+    def test_foreign_context_iri_is_rejected(self) -> None:
+        """A context IRI on another host is rejected, even if the path looks the same."""
+        validator = validate.load_validator()
+        record = {
+            "@context": "https://example.org/context/0.0.1/sourcelume.jsonld",
+            "id": "https://example.org/records/test",
+            "type": "ProvenanceRecord",
+        }
+        errors = [e for e in validator.iter_errors(record) if list(e.path) == ["@context"]]
+        self.assertEqual(len(errors), 1)
+
     def test_validate_example_with_invalid_json_schema(self) -> None:
         """Test that an invalid record fails JSON Schema validation."""
         with tempfile.TemporaryDirectory() as tmp:
diff --git a/tools/validate.py b/tools/validate.py
index 2bc9e89..a965c53 100644
--- a/tools/validate.py
+++ b/tools/validate.py
@@ -29,6 +29,8 @@ SPEC_VERSION = "0.0.1"
 BASE = Path(__file__).resolve().parent.parent
 SCHEMA_DIR = BASE / "schema" / SPEC_VERSION
 EXAMPLES_DIR = BASE / "examples"
+CANONICAL_CONTEXT_IRI = "https://sourcelume.apache.org/context/0.0.1/sourcelume.jsonld"
+CONTEXT_FILE = BASE / "context" / SPEC_VERSION / "sourcelume.jsonld"
 
 
 def load_validator() -> Draft202012Validator:
@@ -46,8 +48,11 @@ def validate_example(example: Path, validator: Draft202012Validator, shapes_path
             print(f"  - {list(err.path)}: {err.message}")
         return False
 
+    # The canonical context IRI is resolved from the bundled copy, never fetched.
+    # The schema has already guaranteed that @context is exactly that IRI.
+    context = json.loads(CONTEXT_FILE.read_text())["@context"]
     data_graph = Graph()
-    data_graph.parse(str(example), format="json-ld")
+    data_graph.parse(data=json.dumps({**doc, "@context": context}), format="json-ld")
 
     conforms, _, results_text = shacl_validate(
         data_graph,


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@sourcelume.apache.org
For additional commands, e-mail: commits-help@sourcelume.apache.org