Copy @direction when cloning the active context; guard the @direction null reset - #338
Conversation
5b65a42 to
d13ba5b
Compare
Rebased onto codex/255-split-iri-and-util-tests and retargeted the PR to it. Moved the tests from tests/test_jsonld.py into tests/jsonld/test_expand.py Lmk if there is anything else ya want changed😃 |
|
yes, could you rebase to master? :) I had a small fix in |
d13ba5b to
a849bce
Compare
Rebased onto master and thanks for the schema.org fix! |
|
Thx @skydudie looks good. @anatoly-scherbakov could you have a final look at this? |
anatoly-scherbakov
left a comment
There was a problem hiding this comment.
The W3C suites @direction: null inputs first establish a default direction, so they do not exercise the no-default-direction case that previously raised KeyError.
This focused test would cover the pop(..., None) behavior directly:
def test_default_direction_null_reset_is_noop_when_unset():
input = {
"@context": {"@direction": None},
"http://example.com/p": "v",
}
expected = [
{
"http://example.com/p": [
{"@value": "v"},
],
},
]
assert jsonld.expand(input) == expected
Good catch thanks @anatoly-scherbakov! Added your test in cf84515 |
## The bug (a deliberate one, until now) Context Processing starts each layer from a **copy of the active context** — which per §4.1 includes the default base direction — and modifies it only when the layer has an `@direction` entry (step 5.8). We deviated deliberately for byte-parity with jsonld.js, whose active-context clone copies `@base`/`@vocab`/`@language` but omits `@direction` ([jsonld.js#586](digitalbazaar/jsonld.js#586)): the default `@direction` was not inherited into scopes, and an explicit `@direction` set by a non-final array layer did not survive later layers. Both sites were commented "revisit when that issue is fixed." ## Why flip now The deviation existed to keep byte-parity with jsonld.js. PyLD shared the same omission ([pyld#337](digitalbazaar/pyld#337)) and has now merged a fix ([pyld#338](digitalbazaar/pyld#338)), while jsonld.js#586 remains open — so the implementations that parity targeted no longer produce the same bytes as each other. Ruby json-ld and Titanium JSON-LD already inherit per spec, making spec behavior the majority behavior. Both deviation sites in our code were annotated "revisit when that issue is fixed." ## The fix Both scoped-copy sites in `Expansion` (property-scoped `applyScopedContext`, type-scoped overlay) now inherit `defaultDirection` alongside `defaultLanguage`, and the per-layer `setDefaultDirection(null)` resets are removed. The default `@direction` survives into property-scoped, type-scoped, embedded-node and remote scoped contexts, and across array layers — exactly like the default `@language`. Explicit scoped `@direction` set/reset (including via remote contexts, guarded by presence flags) behaves as before. ## Blast radius - **Default-mode N-Quads, canonical hashes and signatures: unaffected.** `@direction` reaches RDF only under the opt-in, *non-normative* `rdfDirection` modes — no published VC Data Integrity suite uses them (w3c/vc-data-integrity#366 is still open on this). - Expanded JSON changes only for documents that set a default `@direction`; published VC context stacks set none. - Under `rdfDirection: i18n-datatype`, an in-scope literal now gets the i18n datatype instead of silently degrading to a plain language-tagged literal — removing an intra-document inconsistency where sibling literals inside and outside a scope serialized differently. ## Tests - The two `SafeModeTest` pins that locked in the parity behaviour (both marked "revisit when upstream fixes the clone") now assert the spec behaviour: inheritance into scopes, and survival of a non-final layer's explicit `@direction` across later layers (property-scoped, type-scoped, embedded-node shapes). - The W3C suite has no fixture for this — the gap that let both jsonld.js and PyLD diverge unnoticed. Proposed fixture `#tdi13` in our pending test contribution covers it; this processor now passes it (expand and toRdf, byte-identical to Ruby json-ld 3.3.2 and Titanium JSON-LD 1.4.1). ## Verification Full suite: **1767 passed, 15 skipped**, `pint` + `phpstan` clean. (Run locally against our fork's extended W3C fixtures, `#tdi13` included, the only failure is `#t0132` — the xsd:string fixture that lands with #59; the two branches are independent and CI runs the official suite.) W3C conformance counts on the official suite: unchanged.
Fixes #337.
What
Two fixes in
_process_context-related direction handling, mirroring jsonld.js semantics per JSON-LD 1.1 API Context Processing:_clone_active_contextnow copies@direction, matching the existing handling of@language/@base/@vocab. Previously any context layer processed on top of an existing active context — a second document-level layer, a property-/type-scoped context, an embedded node@context, or a remote context — silently dropped the inherited default base direction (while the default language survived), changing expanded output and, underrdfDirection, the emitted N-Quads. (Default @direction is not inherited across context layers — _clone_active_context omits @direction #337; same bug as Default @direction is not inherited across context layers — _cloneActiveContext omits @direction jsonld.js#586; matching PR Copy @direction when cloning the active context jsonld.js#587.)@direction: nullcontext entry is now a no-op when no default direction is set (rval.pop('@direction', None)instead of an unguardeddel). This mirrors JSdeletesemantics. Found while writing the tests: on released 3.3.0,jsonld.expand({'@context': {'@direction': None}, 'http://ex/p': 'v'})raisesKeyError: '@direction', and a legitimate scoped@direction: nullreset is rejected asinvalid scoped context.Tests
TestExpandmethods (per AGENTS.md, added to the existing class): default@directionsurvival across document-level layers, inheritance into a property-scoped context, and scoped override /nullreset.pytest, W3C suites included via thespecifications/submodules): 1885 passed / 18 failed / 29 skipped / 1 xfailed on this branch vs 1882 / 18 / 29 / 1 on unpatchedmaster— the failure lists are byte-identical (tests/test_cli.pyJSON-decode issues and a missingaiohttpmodule in this environment), i.e. +3 new passing tests, zero regressions.make lintclean.Compatibility note
This changes expanded output (and N-Quads under
rdfDirection) for documents that combine a default@directionwith multiple context layers or scoped contexts — flagging for the 4.0.0 changelog since RDFC hashes over such documents change. Related: w3c/vc-data-integrity#366.