From 13c98ff1f198a3c3aa43c71b66300891afdf6d4e Mon Sep 17 00:00:00 2001 From: Francois Lanusse Date: Thu, 15 Oct 2026 13:39:57 +0200 Subject: [PATCH] Pin the RO-Crate version the crate is written in rocrate 0.16 moved its default from RO-Crate 1.2 to 1.3. The 1.3 context maps ComputationalWorkflow to a bioschemas term the Workflow RO-Crate and Run Crate profiles do not recognise, so the main workflow lost its type and the validator smoke test failed REQUIRED on every runner, whichever branch CI resolved it for. Write 1.1 explicitly instead of following the library's default. The rendered crate is now byte-identical under rocrate 0.15 and 0.16, and the descriptor's conformsTo is rocrate's own for 1.1 (RO-Crate 1.1 + Workflow RO-Crate 1.0) rather than a hand-set claim of a 1.2 context the document no longer emits. Co-Authored-By: Claude Opus 5.5 --- src/lightcone/engine/crate.py | 17 ++++++++--------- tests/test_crate.py | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/lightcone/engine/crate.py b/src/lightcone/engine/crate.py index cbed1b75..473b490c 100644 --- a/src/lightcone/engine/crate.py +++ b/src/lightcone/engine/crate.py @@ -48,6 +48,13 @@ #: undefined and silently dropped on JSON-LD expansion — typed but inert. _WORKFLOW_RUN_CONTEXT = "https://w3id.org/ro/terms/workflow-run" +#: The RO-Crate version written, pinned rather than left to rocrate's +#: default: the Workflow RO-Crate and Run Crate profiles are written +#: against 1.1's vocabulary, and the 1.3 context maps +#: ``ComputationalWorkflow`` to a term their shapes do not recognise, so +#: following the default turns the workflow into an untyped file. +_RO_CRATE_VERSION = "1.1" + #: What the root conforms to, each also declared as a CreativeWork in the #: graph — consumers resolve the reference inside the crate, not on the #: network. @@ -133,7 +140,7 @@ def __init__( self.license = license self.writer = writer self.keys = dict(keys) - self.crate = ROCrate() + self.crate = ROCrate(version=_RO_CRATE_VERSION) self.crate.metadata.extra_contexts.append(_WORKFLOW_RUN_CONTEXT) #: Every materialized task, sorted: the one iteration order. self.made: list[tuple[Key, assets.Manifest]] = sorted( @@ -190,14 +197,6 @@ def document(self) -> str: # ----- the workflow and its structure ----- def _workflow(self) -> Any: - # Before `add_workflow` rewrites the descriptor's conformsTo: 1.1 - # is what RO-Crate validators key on, 1.2 is the context actually - # emitted — the crate is structurally both, and saying only the - # newer one reads as conforming to neither. - self.crate.metadata["conformsTo"] = [ - {"@id": "https://w3id.org/ro/crate/1.1"}, - {"@id": "https://w3id.org/ro/crate/1.2"}, - ] lang = ComputerLanguage( self.crate, "#astra", diff --git a/tests/test_crate.py b/tests/test_crate.py index eef3b1cb..4878b3b7 100644 --- a/tests/test_crate.py +++ b/tests/test_crate.py @@ -185,6 +185,22 @@ def test_the_context_carries_the_workflow_run_vocabulary(project: Path) -> None: assert "https://w3id.org/ro/terms/workflow-run" in document["@context"] +def test_the_crate_version_is_pinned_not_rocrates_default(project: Path) -> None: + """rocrate 0.16 moved its default to 1.3, whose context maps + `ComputationalWorkflow` to a term the workflow profiles do not + recognise — so the version is ours, and both halves must agree.""" + _made(project, "baseline", "first", git_sha="aaa111") + document = _render(project, _graph(project)) + entities = _entities(document) + + assert "https://w3id.org/ro/crate/1.1/context" in document["@context"] + claimed = {ref["@id"] for ref in entities["ro-crate-metadata.json"]["conformsTo"]} + assert claimed == { + "https://w3id.org/ro/crate/1.1", + "https://w3id.org/workflowhub/workflow-ro-crate/1.0", + } + + def test_the_root_conforms_to_every_claimed_profile_and_declares_each( project: Path, ) -> None: