Skip to content

Copy @direction when cloning the active context; guard the @direction null reset - #338

Merged
mielvds merged 4 commits into
digitalbazaar:masterfrom
skydudie:fix-direction-inheritance
Sep 22, 2026
Merged

mielvds merged 4 commits into
digitalbazaar:masterfrom
skydudie:fix-direction-inheritance

Conversation

@skydudie

@skydudie skydudie commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Fixes #337.

What

Two fixes in _process_context-related direction handling, mirroring jsonld.js semantics per JSON-LD 1.1 API Context Processing:

  1. _clone_active_context now 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, under rdfDirection, 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.)
  2. A @direction: null context entry is now a no-op when no default direction is set (rval.pop('@direction', None) instead of an unguarded del). This mirrors JS delete semantics. Found while writing the tests: on released 3.3.0, jsonld.expand({'@context': {'@direction': None}, 'http://ex/p': 'v'}) raises KeyError: '@direction', and a legitimate scoped @direction: null reset is rejected as invalid scoped context.

Tests

  • Three TestExpand methods (per AGENTS.md, added to the existing class): default @direction survival across document-level layers, inheritance into a property-scoped context, and scoped override / null reset.
  • Full local run (pytest, W3C suites included via the specifications/ submodules): 1885 passed / 18 failed / 29 skipped / 1 xfailed on this branch vs 1882 / 18 / 29 / 1 on unpatched master — the failure lists are byte-identical (tests/test_cli.py JSON-decode issues and a missing aiohttp module in this environment), i.e. +3 new passing tests, zero regressions.
  • make lint clean.

Compatibility note

This changes expanded output (and N-Quads under rdfDirection) for documents that combine a default @direction with 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.

@mielvds mielvds left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @skydudie. Could you rebase against codex/255-split-iri-and-util-tests?

Comment thread CHANGELOG.md Outdated
@skydudie
skydudie changed the base branch from master to codex/255-split-iri-and-util-tests September 15, 2026 21:59
@skydudie
skydudie force-pushed the fix-direction-inheritance branch from 5b65a42 to d13ba5b Compare September 15, 2026 21:59
@skydudie

Copy link
Copy Markdown
Contributor Author

Thanks @skydudie. Could you rebase against codex/255-split-iri-and-util-tests?

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😃

@mielvds
mielvds changed the base branch from codex/255-split-iri-and-util-tests to master September 16, 2026 07:41
@mielvds

mielvds commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator

yes, could you rebase to master? :) I had a small fix in

@skydudie
skydudie force-pushed the fix-direction-inheritance branch from d13ba5b to a849bce Compare September 17, 2026 03:33
@skydudie

Copy link
Copy Markdown
Contributor Author

yes, could you rebase to master? :) I had a small fix in

Rebased onto master and thanks for the schema.org fix!

@mielvds

mielvds commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator

Thx @skydudie looks good. @anatoly-scherbakov could you have a final look at this?

@anatoly-scherbakov anatoly-scherbakov left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@skydudie

Copy link
Copy Markdown
Contributor Author

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

@mielvds
mielvds merged commit 5c76499 into digitalbazaar:master Sep 22, 2026
17 of 18 checks passed
@skydudie
skydudie deleted the fix-direction-inheritance branch September 23, 2026 02:36
skydudie added a commit to Accredifysg/PHP-JSON-LD that referenced this pull request Sep 23, 2026
## 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Default @direction is not inherited across context layers — _clone_active_context omits @direction

3 participants