← all docs

Delegated Access β€” Verifiably Build Plan

Branch: feat/delegated-access (off feat/inji-authcode-dynamic-issuance). Gitignored working doc.
Source design: ADR: A Reusable Delegated-Access Adapter (2026-06-24, updated 2026-06-25 Β§0).
DPI constraint: reusability + minimalism β€” one DPG-agnostic evaluator; minimal new surface.
Verified against: code e619453 + live VPS endpoints, 2026-06-25 (inji-certify 0.14.0,
inji-verify 0.16.0, walt.id 0.18.2, eSignet 1.5.1, credebl/Credo-TS 0.5.x).
BUILD ORDER (user directive 2026-06-25): by data-model tier across DPGs β€”
(T1) JSON-LD VCDM 2.0 across all 3 DPGs β†’ (T2) VCDM 1/1.1 where supported β†’ (T3) SD-JWT/SD-JWT-VC where supported.


0. Executive summary β€” verified findings (the 5 that shape the build)

  1. JSON-LD (ldp_vc) is the tier-1 lead (per directive) β€” with a verification-routing caveat.
    ldp_vc issuance is fine; ldp_vc verification through MOSIP vc-verifier is broken on this
    stack (URDNA2015 canonicalization, implicates credentialStatus + @vocab; docs/dpg-matrix.md:176-239).
    β†’ For T1 we (a) carry the capability via a defined @context term, not @vocab (avoids worsening
    it), and (b) route JSON-LD verification through walt.id's verifier (W3C VCDM, after @context
    alignment) and/or treat Inji-verify-of-ldp as a tracked upstream bug. The evaluator's
    linkage/capability/status checks run on top of whatever signature verdict the host returns.
  2. Neither DPG enforces linkage. walt.id doesn't evaluate or inject same_subject; Inji doesn't
    either; credebl DCQL groups but doesn't evaluate onBehalfOf==subject. β†’ the evaluator is the sole
    linkage authority on every path. Drop "inject same_subject as defense-in-depth."
  3. Revocation is unchecked today. CheckedRevocation:true is hardcoded; the cache verifies the list
    JWT but never decodes the bit. D4's uniform check = ~30 new lines composing existing primitives, fail-closed.
  4. Trust is advisory-only. IsTrusted labels but never blocks (I3 unmet) β†’ the evaluator must enforce.
  5. In-app holder discards its key (inji_holder.go:237) β†’ Β§12 cnf key-binding (T3) needs an external
    wallet (Credo) or the walt.id claim path; the in-app path is bearer.

Net: new code is small + concentrated β€” one DPG-agnostic internal/delegation evaluator, per-credential
VP normalization, a ~30-line status-bit check; everything else is reuse. The evaluator is built first
(format-agnostic), then issuance per data-model tier.


1. ADR claim-verification matrix

Verdicts: βœ… CONFIRMED Β· ◐ PARTIAL Β· ❌ REFUTED Β· ? UNVERIFIABLE-IN-CODE. Evidence = code (path:line,
under verifiably-go/) and/or live endpoint.

Trust-triangle invariants

ADR Claim Verdict Evidence
I1 Issuer signs; adapter never signs βœ… Inji uses Certify key-manager ref CERTIFY_VC_SIGN_ED25519 (injicertify/db.go:152-157, storage/pg/subjects.go:154-155); claim returns Certify bytes verbatim inji_holder.go:254-297. Live: did:web:certify-nginx serves Ed25519 assertionMethod. walt.id signs (waltid/issuer.go:666-727).
I3 Verifier anchors trust in issuer ◐ advisory trust/registry.go:29 IsTrusted via verifier.go:540-557 attachTrustStatus (sites verifier.go:444,:532,public_verify.go:360) β€” only labels, never blocks; StatusListPolicy (registry.go:60-63) unread. β†’ enforce.
I4 No forge-enabling secret in adapter βœ… Only key is the ES256 trust/receipt key (trust/jwt.go:53; live JWKS ES256). Never signs credentials.

Verification asymmetry (Β§7c) & OID4VP reality

ADR Claim Verdict Evidence
§7c walt.id evaluates same_subject natively ❌ 0 occurrences; PD = fields+limit_disclosure only (waltid/verifier.go:206-270); VP policies hardcoded ["signature","presentation-definition"] (:390-392).
Β§7c Inji checks signatures, not PE constraints βœ… injiverify re-checks fields because Inji doesn't (INJIVER-1131): injiverify/adapter.go:179-184,:412-425; dpg-matrix.md:264-270.
Β§7b host-verified VP retrievable ◐ adapters receive the full VP but collapse it: VerificationResult single-valued (backend/adapter.go:377-434); walt.id vcList[0] only (verifier.go:730-735); credebl flattens (:439-451); injiverify decodes nothing (:185-194).
Β§9 DCQL is the linkage path (credebl) ◐ DCQL slice can carry the pair (credebl/verifier.go:30-44) but no credential_sets and no cross-claim eval.
β€” request a credential PAIR today ◐ single descriptor, but wire fields already arrays β†’ append one element (injiverify adapter.go:373-386, waltid verifier.go:170-171, credebl verifier.go:113-120).

Status / revocation (D4)

ADR Claim Verdict Evidence
D4 Verifiably hosts Bitstring + Token lists, allocates βœ… handlers/status_list.go:61,98; allocate issued.go:40β†’statuslist/store.go:140. Live: routes wired (404 unknown id).
D4 status entry embedded in every credential ◐ split walt.id embeds (Verifiably-hosted) waltid/issuer.go:1026-1034/:927-934; inji-ldp β†’ Certify embeds (its own list, Verifiably proxies kid inji_proxy.go:123); inji-sd-jwt β†’ none.
D4 uniform status check ❌ today CheckedRevocation:true hardcoded (waltid/verifier.go:378); public path policies ["signature","expired","not-before"] (public_verify.go:316); statuslistcache.Fetch no bit decode (fetcher.go:45). Primitives exist: bitstring.go:135 Get, token.go:39.

Formats / profile feasibility (Β§6/Β§12) / DID

ADR Claim Verdict Evidence
§6 nested credentialSubject.onBehalfOf.id + termsOfUse (ldp) ❌ as-built templates flat (injicertify/db.go:243-316); values string-only (claims->>, SubjectData map[string]string). T1 makes this in-scope work (template nesting + termsOfUse emission).
Β§12 bearer SD-JWT + cnf key-bound delegation ◐ walt.id arbitrary claims + token status (issuer.go:918-936); cnf set by the claiming wallet (haip-conformance.md:66); inji in-app holder discards key (inji_holder.go:237).
Q6 stable non-pairwise anchor needed βœ… vc_subject PK = pairwise PSU-token base64url(SHA3-256(individualId+clientId)) (api_subjects.go:64-67); raw id only at signup (onboard.go:127,153-160). β†’ explicit subjectRef claim.
Β§9 did:web issuers resolvable βœ… didresolver/web.go:45 (web only). Live: certify did.json resolves. Gap: walt.id issuer is did:jwk (waltid/issuer.go:52) β€” anchor trust on the DID string.
D6 consent/audit receipt ◐ unsigned event log (verification/events.go:18); attach public_verify.go:391-397,verifier.go:463-469; ES256 signing available (trust/jwt.go:53).

2. Per-DPG Γ— per-tier issuance support (what "across all 3 DPGs" really means)

Tier Format inji-certify walt.id credebl
T1 JSON-LD VCDM 2.0 (ldp_vc, Data Integrity) βœ… issue (verify via walt.id β€” ldp Inji-verify bug) βœ… issue+verify (W3C VCDM) ❌ SD-JWT-only (issuer is dc+sd-jwt) β†’ out of T1
T2 VCDM 1/1.1 (jwt_vc_json, ldp_vc 1.1) ◐ ldp_vc 1.1 βœ… jwt_vc_json (tested round-trip) ❌
T3 SD-JWT / SD-JWT VC (vc+sd-jwt,dc+sd-jwt) βœ… vc+sd-jwt βœ… vc+sd-jwt βœ… dc+sd-jwt (Β§12 cnf)

β†’ "across all 3 DPGs" is fully achievable only at T3. T1/T2 cover inji-certify + walt.id; credebl
joins at T3. The evaluator is identical across all tiers/DPGs (DPI reusability) β€” only issuance and
the host's signature verdict differ.


3. Open-question decisions (final)

Q1 policy-only crypto / sole linkage authority Β· Q2 termsOfUse(JSON-LD)/delegation(SD-JWT) via a
defined context term Β· Q3 single-hop, fail-closed on chains Β· Q4 internal receipt Β· Q5 reuse cache, ~60s,
fail-closed Β· Q6 explicit subjectRef (registry individualId), never the PSU-token.


4. Phases (reuse-first, minimal)

Phase 0 β€” Foundation (shared)

  • Delegated-access JSON-LD @context (delegated-access/v1) defining DelegationCapability,
    onBehalfOf, delegate, allowedAction, validUntil, allowFurtherDelegation. Host via the existing
    static FileServer (static/contexts/..., main.go:463) or a ~10-line application/ld+json handler
    mirroring ServeJWKS (main.go:528). Needed now (JSON-LD is T1).
  • Profile types in internal/delegation (the Β§6 JSON-LD shape + the normalized internal shape).

Phase 1 β€” Presentation Adapter (DPG-agnostic evaluator) β€” FIRST

Step Reuse (don't rebuild) Build (minimal) Files
Per-credential normalization parseSDJWTVC/parseVPJWT (waltid/verifier.go:658,719), credebl extractClaimsFromCompactSdJwt (:312); a JSON-LD VC parse for ldp emit []NormalizedCredential (stop collapsing) waltid/verifier.go:612, credebl/verifier.go:270, injiverify/adapter.go:185
Surface to handlers keep single-valued fields (back-compat) additive Credentials []NormalizedCredential + HolderBinding backend/adapter.go:~432 (+ backend/normalized.go)
Evaluator statuslistcache.Fetch + statuslist decoders + trust.IsTrusted internal/delegation.Evaluate (linkage+invocation+caveats+status, fail-closed; enforce trust) internal/delegation/ (new)
Wire-in (uniform) clone attachTrustStatus (verifier.go:540); INJIVER-1131 precedent (injiverify:179) attachDelegationVerdict at 3 seam sites (downgrade-only) verifier.go:444,532, public_verify.go:362
Request the pair wire fields already arrays append 2nd descriptor/credential the 3 PD/DCQL builders
  • Testable now with hand-crafted VCs (unit tests) before any issuance exists.

Phase 2 β€” T1 issuance: JSON-LD VCDM 2.0 (inji-certify + walt.id)

  • walt.id (lead β€” issue+verify in one stack): issue a VCDM2 ldp_vc with nested credentialSubject.onBehalfOf,
    termsOfUse[DelegationCapability], bitstring credentialStatus. Reuse waltid/issuer.go VCDM path
    (:1026-1034 already injects credentialStatus); the credentialData is JSON we control. Verify via walt.id.
  • inji-certify: teach buildVCTemplate (injicertify/db.go:257-312) to emit nested onBehalfOf +
    termsOfUse (the deferred template work, now T1) + extend FieldSpec with nesting; provision subjectRef
    at signup. Verify via walt.id verifier (Inji-verify-of-ldp is the known bug). Status: Certify-hosted.
  • credebl: not applicable at T1.

Phase 3 β€” T2 issuance: VCDM 1/1.1

  • walt.id jwt_vc_json (tested round-trip); inji-certify ldp_vc 1.1. Same nested shape, issuanceDate/expirationDate.

Phase 4 β€” T3 issuance: SD-JWT / SD-JWT VC (all 3 DPGs)

  • Β§12 model: bearer identity + cnf key-bound delegation (delegation JOSE claim, Token Status List).
    walt.id + credebl + inji vc+sd-jwt; cnf via external wallet (Credo). credebl joins here (DCQL).

Phase 5 β€” Cross-cutting (applied per tier)

  • Revocation/transition: reuse RevokeIssuedCredential (issued.go:225, format-agnostic) + REST (main.go:614).
  • Receipt/audit: extend verification/events.go:18 (internal-only).

5. Recommended starting slice

  1. Phase 0 (@context + types) β†’ Phase 1 (evaluator + normalization + status-bit check + enforce trust),
    unit-tested with hand-crafted VCDM2 VCs.
  2. Phase 2 walt.id first (issue+verify VCDM2 delegation in one stack β€” fastest full
    issue→verify→revoke loop), then Phase 2 inji-certify (template nesting + walt.id-verify routing).
  3. Then T2, then T3 (credebl joins at T3).

6. DPI scorecard

  • Reusability: one evaluator for all 3 DPGs Γ— all tiers via a normalized VP; reuses existing parsers,
    status-list cache, trust registry, revoke UI/REST, event log, ES256 signing.
  • Minimalism: new = internal/delegation (types + Evaluate), per-credential normalization (~3 adapter
    edits), ~30-line status-bit check, additive VerificationResult fields, handler hook, @context file.
    The only larger item is T1 buildVCTemplate nesting (deferred otherwise; required because JSON-LD is T1).
Source: docs/delegated-access/build-plan.md