Skip to content

Europass / ELM European Digital Credential consumption — verify, hold, display (issuance deferred)

Status: accepted (2026-06-26)

Authored via /grill-with-docs (13 decisions). Implementation plan: docs/design/europass-edc-consumption-plan.md.

emergent already runs a full W3C VC credential rail — issuer, holder/wallet, host (Badge Connect + OAuth2 AS), verifier, displayer — for Open Badges 3.0 / CLR 2.0 (ADR-0013 / 0015 / 0016 / 0017 / 0028 / 0036 / 0038). This ADR adds support for the EU’s European Digital Credentials (EDC), expressed in the European Learning Model (ELM) v3.3 and sealed with an eIDAS JAdES e-seal. The conform-ed library ships the consumable pieces (conform-ed ADR-0019): typed contracts (@conform-ed/contracts/elm/v3_3), profile-agnostic SHACL validation + JAdES seal verification, a one-call verifyEdc orchestrator (@conform-ed/credential-verification), and a framework-light renderer (@conform-ed/elm-render). What conform-ed does not ship is seal production — it verifies seals, there is no signing code. That asymmetry, plus the fact that a conformant EDC requires a Qualified Trust Service Provider (QTSP) certificate emergent does not have, shapes the scope below.

1. v1 scope = Verify + Hold + Display; issuance deferred (QTSP-gated)

Section titled “1. v1 scope = Verify + Hold + Display; issuance deferred (QTSP-gated)”

emergent consumes EU-issued EDCs (verify the seal → hold in the wallet → render), and does not issue them in v1. Conformant issuance is blocked on (a) an eIDAS qualified certificate from a QTSP + a timestamping authority — procurement/legal, not code — and (b) a JAdES signing addition upstream in conform-ed. Recorded as a future gated lane. Issuing an “ELM-as-VC-JOSE” credential signed with emergent’s existing Ed25519 is rejected as a default: it is not a conformant EDC and undermines EDC’s whole purpose (eIDAS legal recognition); kept on the shelf only for a concrete future need. Hosting (serving EDCs over an EU wallet / EBSI rail) is out of v1.

2. Profiles: EDC full stack; LOQ/PID/AMS validate-only; domain import deferred

Section titled “2. Profiles: EDC full stack; LOQ/PID/AMS validate-only; domain import deferred”

v1 supports the EDC profile end-to-end (verify/hold/display) and offers validate-only for the plain ELM profiles LOQ / PID / AMS — upload a document, run validateAgainstProfile against the right shape, show conforms/violations (a tooling capability in the admin verifier, no DB, no mapping). Importing/mapping ELM into the emergent domain (LOQ → Offerings/Qualifications/Achievements; PID → Identity Bindings) is deferred to a dedicated crosswalk/catalog lane — it depends on the same large ELM↔domain crosswalk that issuance needs. Standalone PID import is judged low-value: the Person emergent needs arrives embedded inside the EDC (ELM Core), used for wallet subject-matching.

3. Trust anchoring: cryptographic + structural only; honest per-axis display; EU Trusted Lists deferred

Section titled “3. Trust anchoring: cryptographic + structural only; honest per-axis display; EU Trusted Lists deferred”

v1 verifies an EDC’s seal cryptographically — JWS signature/tamper-check, x5c chain internal consistency + validity windows, RFC-3161 timestamp — plus full SHACL structural conformance. The trust-anchor set stays host-injected and pluggable but empty by default: emergent is a wallet/displayer, not an official validation authority. The UI reports trust honestly, per axis (“seal intact ✓ / timestamp valid ✓ / EU-qualified trust: not verified”). All EU LOTL → national Trusted List work (ETSI TS 119 612/615; the reference implementation is the EU’s Java DSS, with no turnkey JS equivalent) — static snapshot or live — is deferred to a dedicated “EU trust anchoring” lane. Because the anchor set is already a seam, the upgrade is purely additive. The conform-ed verifyEdc orchestrator already surfaces trustAnchored as a separate axis that never downgrades the rollup, so this is honest out of the box.

4. Storage: reuse held_credential as-is — no migration

Section titled “4. Storage: reuse held_credential as-is — no migration”

An EDC is a VC; the generic wallet table absorbs it. rawPayload ← the sealed JAdES {payload, signatures} JSON as received (server-only, unbounded TEXT); credential_type = "EuropeanDigitalCredential" routes import/verify to the JAdES path; verificationMeta ← the per-axis seal verdict; display_meta ← derived from the elm-render view-model; subject_id / subject_match ← reuse identity matching, extracting the embedded ELM Person identifier instead of the OB IdentityObject. No EDC-specific columns, no new table.

5. Import gate = seal-intact only; subject-match by ELM Person extraction; accept broad

Section titled “5. Import gate = seal-intact only; subject-match by ELM Person extraction; accept broad”

The single hard import gate is seal cryptographically intact (signature math over the exact payload + x5c chain internally consistent); a tampered/broken seal is rejected. Everything else — SHACL conformance, validity window, timestamp anomalies, trust-not-anchored — is cached display state in verificationMeta and never blocks import. This mirrors the OB/CLR “gate on integrity, cache the rest” policy. subject_match: extract the ELM Person email/identifier → match the learner’s verified email ⇒ verified_email, else self_asserted; hold regardless of match (accept-broad), labelled honestly.

6. Display via the elm-render view-model in React/Mantine; trust shown without a migration

Section titled “6. Display via the elm-render view-model in React/Mantine; trust shown without a migration”

Render the conform-ed elm-render view-model with React/Mantine components (learner-web wallet detail + admin verifier panel), locale-aware so multilingual displayParameter/langStrings resolve; the framework-light HTML is a conformance reference, not shipped to users. The full per-axis verdict lives in (server-only) verificationMeta. For the synced wallet list, verification_status stays the coarse lifecycle hint (valid/expired/revoked — not trust; no enum change) and a small non-PII seal/trust summary goes in synced display_meta (e.g. {profile:"edc", sealIntact:true, trustAnchored:false}), so the list badge reads honestly (“EDC · seal intact · EU-trust not verified”) rather than a bare “verified ✓”. Validate-only for LOQ/PID/AMS lives in the admin verifier panel.

7. PII / erasure: no new class; no synced-display PII restriction

Section titled “7. PII / erasure: no new class; no synced-display PII restriction”

Reusing held_credential (decision 4) means its existing erasure class cascade (on person_id) fully covers EDCs — no new table, no new declaration. holder = device owner = our user (RLS scopes wallet sync to the learner’s own person_id), so syncing their own credential’s display data — their own name included — is fine; we do not shield a user from their own data on their own device. Other people’s PII is handled by the existing processor-not-controller stance: foreign-issued content rides the cascading server-only blob, out of scope, with no special machinery. The richer ELM Person identity is treated exactly like an OB import.

8. A minimal, honest EDC conformance overlay

Section titled “8. A minimal, honest EDC conformance overlay”

Add an EDC overlay over conform-ed’s EDC coverage maps, drift-gated like the OB/CLR overlays (ADR-0028 style). It declares what emergent satisfies (verifier/displayer role: SHACL structural verification, JAdES seal crypto verification, plain-profile validation, displayParameter rendering) and what is explicitly N/A or deferred, never silent (issuance / EDC-ISS — QTSP-gated; trust anchoring / qualified-status — deferred; hosting). If conform-ed lacks displayer-role requirement rows for EDC, add them upstream the way OB-DSP was.

9. Packaging: consume from the GitHub npm dev-channel; no release gate

Section titled “9. Packaging: consume from the GitHub npm dev-channel; no release gate”

emergent sources @conform-ed/* from the GitHub npm dev-channel — the canonical, guaranteed-fresh registry until emergent nears its own v1 — and commits @dev pins freely pre-launch. The conform-ed ELM lane (including verifyEdc) is published there, so there is no public-npm-release gate. v1 adds @conform-ed/elm-render (new dep) and bumps @conform-ed/contracts (→ elm/v3_3) and @conform-ed/credential-verification (→ jades/x509-chain/rfc3161/shacl-validate/verifyEdc). Fix-upstream-first: any needed library affordance is added in conform-ed → republished → re-pinned, never shimmed here.

10. Import accepts sealed JSON-LD only; PDF dropped (premise didn’t hold for v3)

Section titled “10. Import accepts sealed JSON-LD only; PDF dropped (premise didn’t hold for v3)”

v1 import (reusing the existing wallet file/URL endpoint) accepts the sealed EDC .json/.jsonld. PDF-embedded import was considered and dropped: verification found the current ELM v3 EDC is delivered as JSON-LD via wallet/email — Europass documents no PDF/PDF-A-3/embedded-file export, and the EU example repos ship JSON-LD (v3) + XML (v2) but zero PDFs. The PDF/A-3-embedded “European Digital Credential” document was the legacy EDCI (ELM v1/v2, XML) format, out of conform-ed’s v3-only scope. Revisit PDF only if real v3 PDF-wrapped EDCs appear in the wild, or as a separate legacy-v2/XML ingest lane. EBSI/wallet-protocol delivery is out of scope.

The JSON-LD verify path needs no further conform-ed change: emergent calls the verifyEdc orchestrator (seal + SHACL + window + status in one call), which is built and published on the dev-channel.

11. Presentation = plain re-export of the sealed EDC; VP presentation deferred

Section titled “11. Presentation = plain re-export of the sealed EDC; VP presentation deferred”

v1 lets the learner re-download/export the original sealed EDC (serve rawPayload); for a self-sealed, independently-verifiable EDC that is presentation. VP presentation of EDCs is deferred: the existing holder-VP enclosure model takes a compact-JWS string or a JSON-LD+Data-Integrity object, and a JAdES {payload, signatures} EDC fits neither — enclosing it is either non-standard (generic verifiers reject) or lossy (stripping the issuer seal defeats the purpose). The standards-aligned holder presentation path is EBSI/wallet (deferred). OB/CLR VP presentation is unchanged.

  • Issue EDCs in v1. Rejected: conformant issuance needs a QTSP qualified certificate (procurement/ legal), and non-conformant VC-JOSE “EDCs” defeat the standard’s purpose. Deferred behind the QTSP dependency; the signing code is the small part.
  • Full EU Trusted-List trust anchoring in v1 (static snapshot or live LOTL/TSL). Rejected for v1: large (ETSI 119 612/615; no turnkey JS), and emergent is a wallet, not a validation authority. The honest per-axis “EU-trust not verified” display is sufficient and the upgrade is additive.
  • A new EDC table / EDC-specific columns. Rejected: held_credential is generic (type discriminator + opaque server-only blob + derived display/verification JSONB) and absorbs EDC cleanly; no migration.
  • Tier-2 ELM import (map LOQ/PID into the domain) in v1. Rejected: depends on the large ELM↔domain crosswalk; its own lane.
  • VP presentation of held EDCs in v1. Rejected on code evidence (enclosure mismatch) — non-standard or lossy; re-export already delivers the value.
  • PDF-embedded EDC import (and PDF extraction upstream in conform-ed). Initially accepted, then rejected after verification: the current ELM v3 EDC has no PDF delivery format (JSON-LD via wallet/email); PDF/A-3-embedded was legacy v1/v2 XML, out of scope. No real v3 PDF fixture exists to build/test against. Deferred to a possible legacy-v2/XML lane.
  • A new credential family joins the wallet/verifier with no schema migration and no new erasure declarations — it rides the existing generic rail.
  • emergent gains a new dependency (@conform-ed/elm-render) and bumped contracts/verification packages from the dev-channel; the JSON-LD verify path needs no further conform-ed change — emergent’s verification is a single verifyEdc call.
  • The verifier/wallet/admin surfaces learn an “EDC / ELM document” mode; the verdict UI gains an honest trust axis that is “not verified” until the deferred trust-anchoring lane lands.
  • Four deferred lanes are now explicitly on record: EDC issuance (QTSP), EU trust anchoring (LOTL/TSL), ELM domain import + EBSI/wallet hosting & presentation, and legacy ELM-v2/XML + PDF-embedded ingest (only if importing pre-2023 Europass credentials becomes a goal).
  • The CONTEXT.md additions (ELM, EDC, eIDAS JAdES e-seal, EU-qualified trust) are the canonical language for this lane.