โ† All docs  ยท  Home

ADR 0011 โ€” Full JSON-LD via compaction against the pinned context

Context

ADR 0007 made ingest normalize any serialization expressed over the pinned Freedback/anno vocabulary: compact terms, prefixed IRIs, single-or-array shapes. That covers everything the W3C annotation ecosystem actually emits over the standard anno context. It does not cover a document that names the same concepts with a third party's own terms bound to the canonical IRIs by an inline @context:

{
  "@context": { "about": { "@id": "http://www.w3.org/ns/oa#hasTarget", "@type": "@id" }, โ€ฆ },
  "@type": "Rating",
  "about": "https://example.com/item/1",
  "scores": { "@type": "Stars", "stars": 4 }
}

The alias normalizer keys off term names (target, body, oa:hasTarget), so about/scores are invisible to it even though they expand to the exact IRIs we use. Resolving them needs a real JSON-LD processor that interprets the declared @context.

Decision

Add protocol-lib::jsonld_full::normalize_full (native, behind the jsonld feature) that compacts the incoming document against our pinned @context using the Haudebourg json-ld crate, then feeds the predictable result to the existing from_jsonld normalizer.

Compaction is the key trick. The processor first expands the input with whatever @context it declares โ€” turning about/scores/stars into the full IRIs oa:hasTarget/oa:hasBody/schema:ratingValue โ€” and then re-serializes using our term definitions, producing exactly the compact shape (target, body, motivation, ratingValue, โ€ฆ) that from_jsonld already parses. So we reuse the entire downstream pipeline (model โ†’ dedup id โ†’ signature โ†’ SHACL) unchanged, and a foreign vocabulary content-addresses identically to the canonical form (tested in jsonld_full::tests::normalizes_a_foreign_vocabulary_to_the_same_dedup_id).

The pinned context is embedded at build time via include_str! of ontology/context.jsonld โ€” the same document served at the stable URL โ€” so the compaction target can never drift from what clients pin.

Server wiring

feedback-server enables the jsonld feature and tries the fast path first: from_jsonld; on failure it falls back to normalize_full. The fast path keeps the common case allocation-light and offline; full compaction only runs for genuinely foreign documents. If both fail, the fast-path error is surfaced (with the compaction error appended) and the POST is a 400.

Why compaction, not expansion + a custom walker

Expanding and walking the expanded form ourselves would mean re-implementing value-object/@list/@id handling that compaction already does correctly, and the expanded shape (full IRIs, value-object wrappers) is further from what from_jsonld wants, not closer. Compacting to our own context lands precisely on the shape we already parse. The ExpandedDocument โ†’ JSON conversion also has no ergonomic path without standing up vocabulary machinery, whereas compact returns a json_syntax::Value directly.

Scope and limits

Consequences