1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
//! JSON-LD context envelope: build / frame / strip a DPP passport payload.
use OnceLock;
use ;
/// Remote contexts this passport context references.
///
/// **A string entry in an `@context` array is fetched by the consumer at
/// expansion time.** One that does not resolve is not cosmetic: a conforming
/// processor fails the whole document with a remote-context load error, and a
/// lenient one drops every term it cannot define. Since our payload uses bare
/// keys, that means the `ld+json` door would convey no linked data at all —
/// worse than serving plain JSON, because the `@context` is itself a claim that
/// the document is semantically resolvable.
///
/// So this list is deliberately short and deliberately explicit: adding to it
/// means editing this constant *and* the test that pins it, which is the point
/// at which someone checks the URL. Two entries were removed on 2026-07-30 for
/// returning 404 — `https://ref.gs1.org/standards/digital-link/context/`, which
/// this crate referenced, and `https://odal-node.io/schemas/dpp/v1`, which the
/// resolver hand-rolled.
///
/// Term-to-IRI mappings are a different matter and are inlined below: a prefix
/// IRI names a vocabulary and is never dereferenced during expansion, so it
/// carries no such obligation.
pub const REMOTE_CONTEXTS: & = &;
/// Build the JSON-LD context for an Odal Node passport.
///
/// The vocabulary is **inlined** rather than hosted. Hosting a context document
/// is a commitment to keep a URL resolving for as long as any passport
/// referencing it exists — years, under ESPR retention — and that is an
/// operational obligation, not a library decision. An inline term map cannot
/// 404, and it can be adopted later without invalidating passports issued now.
///
/// The literal is built once and cloned per call — callers extend the
/// returned value (e.g. [`frame_passport`] merges passport fields into it),
/// so it must stay an owned, independently-mutable `Value` per call site.
/// The `@context` value alone, for a caller that already has a passport object
/// and needs to stamp the context onto it.
///
/// Exists so the resolver stops constructing its own: two definitions of one
/// context is how the served one came to reference a URL that 404s while this
/// one referenced a different URL that also 404s.
/// Wrap a passport JSON value in a JSON-LD envelope.
///
/// A non-object payload cannot be merged into the `@context` object; it is
/// returned **unchanged** rather than silently discarded into a bare, empty
/// envelope.
/// Extract the plain data from a JSON-LD framed passport (strip `@context`).