Skip to content

feat: support W3C JSON-LD credential issuance and presentation#389

Open
sagarkhole4 wants to merge 2 commits into
mainfrom
feat/JSONLD_JWTVC
Open

feat: support W3C JSON-LD credential issuance and presentation#389
sagarkhole4 wants to merge 2 commits into
mainfrom
feat/JSONLD_JWTVC

Conversation

@sagarkhole4
Copy link
Copy Markdown
Contributor

@sagarkhole4 sagarkhole4 commented May 20, 2026

Summary by CodeRabbit

  • New Features
    • Added support for storing, retrieving, and managing W3C credentials via new endpoint and service methods
    • Introduced credential versioning for issuance (v1.1, v2.0) and verification requests (v1, v1.draft21, v1.draft24)
    • Extended credential binding resolver to support additional credential formats
    • Added verifier branding customization with background and text color options

Review Change Stack

Signed-off-by: Sagar Khole <sagar.khole@ayanworks.com>
@sagarkhole4 sagarkhole4 self-assigned this May 20, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 20, 2026

📝 Walkthrough

Walkthrough

This PR implements comprehensive W3C Verifiable Credentials support in OpenID4VC flows. New holder endpoints retrieve and manage W3C credentials; issuer offer generation gains version-aware payload transformation for v1.1 and v2.0 contexts; JWT VC JSON formats are mapped into W3C credentials; verifier proof requests now support Presentation Exchange. Types, routes, and API docs are updated throughout.

Changes

W3C VC Support Implementation

Layer / File(s) Summary
Type Contracts & Enums
src/controllers/openid4vc/types/holder.types.ts, src/controllers/openid4vc/types/issuer.types.ts, src/controllers/openid4vc/types/verifier.types.ts
Adds W3C_VC credential type enum, new IssuerObject and CredentialSubject interfaces for flexible issuer/subject representation, version fields to offer and authorization request types, and optional background_color/text_color metadata fields.
Holder W3C Credential Retrieval
src/controllers/openid4vc/holder/holder.Controller.ts, src/controllers/openid4vc/holder/holder.service.ts
New GET /openid4vc/holder/w3c-vcs endpoint and getW3cCredentials service method retrieve all W3C credential records from the holder agent.
Holder Credential Storage, Deletion & Binding
src/controllers/openid4vc/holder/credentialBindingResolver.ts, src/controllers/openid4vc/holder/holder.service.ts
Extends credential workflow to store W3C credentials via w3cCredentials.store(), delete them via W3cCredentialService, enable JWT VC formats (JwtVcJsonLd, JwtVcJson) in binding resolution, and improve error messaging with runtime type information.
Holder Presentation Request Routing
src/controllers/openid4vc/holder/holder.service.ts
Refactors acceptPresentationRequest to build acceptOptions and route credential selection through either DCQL or Presentation Exchange (previously rejected).
Issuer Offer Generation with Version-Aware Transformation
src/controllers/openid4vc/issuance-sessions/issuance-sessions.service.ts
Computes effective version per credential based on format and options; validates payloads with version awareness (e.g., requiring credentialSubject and validating issuer for v2.0); transforms payloads for v2.0 (date field mapping, context reordering, issuer normalization).
JWT VC JSON to W3C Credential Transformation
src/utils/oid4vc-agent.ts
Adds mapping path for JwtVcJson and JwtVcJsonLd formats: derives context (v1 vs v2), normalizes subject ID, resolves issuer from payload or configured DID, builds JWT VC JSON with V2/V1 date fields, transforms into W3cV2Credential/W3cCredential, with error logging to mapper-error.log.
Verifier Version & Presentation Exchange Support
src/controllers/openid4vc/verifier-sessions/verification-sessions.service.ts
Propagates version field into authorization request options; switches Presentation Exchange from error to supported mode with proper credential routing; improves X.509 certificate handling with keyId assignment.
Routes & API Documentation
src/routes/routes.ts, src/routes/swagger.json
Auto-generated TSOA routes and Swagger OpenAPI schemas reflect all type, endpoint, and flow changes with expanded offer payloads (validFrom, validUntil, credentialSubject, flexible issuer), new W3C component schemas, and updated CredentialType enum.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related issues

  • feat: Support for W3c JSONLD credential using oid4vc platform#1639: Adds explicit code-level support for W3C JSON-LD and JWT-VC credential formats within the OID4VC issuance and holder flows, implementing the feature with new W3C credential type handling, holder endpoints, version-aware offer generation, and JWT VC JSON transformation.

Possibly related PRs

  • credebl/agent-controller#357: Both PRs modify createCredentialOffer in issuance-sessions service to inject and shape credential status/payload blocks; overlaps in v1/v2 payload transformation and status-list integration.
  • credebl/agent-controller#340: Both PRs modify getCredentialBindingResolver for JWT VC format eligibility in credential binding selection; direct overlap in how JwtVcJson/JwtVcJsonLd determine binding method.
  • credebl/agent-controller#368: Main PR builds on holder/controller/service scaffolding introduced in PR #368; overlaps at same OpenID4VC code paths for holder credential management.

Suggested labels

enhancement

Suggested reviewers

  • RinkalBhojani
  • tipusinghaw
  • shitrerohit

Poem

🐰 W3C credentials now flow through the gates,
Holder to issuer, no more we wait,
JWT VC JSON transforms so bright,
Version-aware offers burning through the night,
Presentation Exchange finally takes flight! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main objective of the pull request, which adds comprehensive support for W3C JSON-LD credential issuance and presentation across multiple service layers and controller endpoints.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/JSONLD_JWTVC

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 12

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/controllers/openid4vc/holder/credentialBindingResolver.ts (1)

100-107: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update fallback messaging to match newly supported JWT VC formats.

The branch now supports JwtVcJsonLd and JwtVcJson, but the comment/error text still implies only sd-jwt/mdoc support.

✏️ Suggested text alignment
-    // Otherwise we also support plain jwk for sd-jwt only
+    // Otherwise we also support plain jwk for sd-jwt, jwt-vc-json(-ld), and mdoc
@@
-      `No supported binding method could be found. Supported methods are did:key and did:jwk, or plain jwk for sd-jwt/mdoc. Issuer supports ${
+      `No supported binding method could be found. Supported methods are did:key and did:jwk, or plain jwk for sd-jwt/jwt-vc-json/jwt-vc-json-ld/mdoc. Issuer supports ${
         supportsJwk ? 'jwk, ' : ''
       }${supportedDidMethods?.join(', ') ?? 'Unknown'}`,

Also applies to: 115-118

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/controllers/openid4vc/holder/credentialBindingResolver.ts` around lines
100 - 107, Update the comment/error text in credentialBindingResolver.ts to
reflect the expanded fallback support: where the code checks supportsJwk and
credentialFormat against OpenId4VciCredentialFormatProfile (including SdJwtVc,
SdJwtDc, JwtVcJsonLd, JwtVcJson, MsoMdoc), change any messaging that currently
mentions only "sd-jwt/mdoc" to list or generically state "sd-jwt, JWT VC
(JSON/JSON-LD) and mdoc" so logs and comments align with the actual supported
formats checked by the supportsJwk/credentialFormat branch.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/controllers/openid4vc/holder/holder.service.ts`:
- Line 157: The if-condition formatting around credentialRecord and
W3cCredentialRecord is violating Prettier; update the three occurrences (the
conditional at the current if and the ones around lines referenced) to a
Prettier-compliant style by reformatting the conditional expression that checks
instance and type (use a consistent cast/parentheses and spacing for
"(credentialRecord as any).type" or prefer a safe type guard) so the file
holder.service.ts compiles with linting; locate references to credentialRecord
and W3cCredentialRecord in the methods around the mentioned checks and apply the
consistent formatting change.

In `@src/controllers/openid4vc/issuance-sessions/issuance-sessions.service.ts`:
- Around line 36-43: transformPayloadForVersion currently returns the original
payload so effectiveVersion is ignored and downstream mapping still treats
offers as v1; update transformPayloadForVersion (and its calls in
issuance-sessions.service.ts where effectiveVersion is computed) to actually
rewrite the credential JSON-LD for 'v2.0' (e.g., set `@context`, validFrom,
structure and vct fields to v2 names) or, alternatively, persist the requested
version on the session/offer object (e.g., add a requestedVersion property) so
oid4vc-agent's mapper (which decides between W3cCredential and W3cV2Credential)
can read it; locate transformPayloadForVersion and any code referencing
effectiveVersion/statusBlock/cred.payload and implement the real transformation
or persist the explicit version flag accordingly.
- Around line 71-72: The code always forces version to 'v1' when building the
offer (see the assignment to version in the issuance session creation), so
options.version is ignored; change the logic in the offer creation (the
variable/assignment that currently reads version: options.version ? 'v1' : 'v1')
to actually use options.version when provided (e.g., set version =
options.version ?? 'v1' or pass options.version directly) so the emitted offer
reflects the requested version; update any callers or validation in the issuance
session creation method (e.g., methods in IssuanceSessionsService that construct
the offer) to accept and propagate the provided options.version.
- Around line 88-90: The current validation always requires
cred.payload.credentialSubject and thereby rejects non-W3C credential formats;
update the validator in createCredentialOffer to only enforce credentialSubject
for W3C VC formats — i.e., first detect W3C VCs by checking the credential's
format metadata (e.g., cred.format or cred.format.type / formatId used in your
code) and only then throw BadRequestError using the existing message referencing
cred.credentialSupportedId when cred.payload?.credentialSubject is missing;
leave SdJwtDc and MsoMdoc flows (flat claims or namespaces) untouched so their
mapper logic can run.

In `@src/controllers/openid4vc/types/issuer.types.ts`:
- Line 7: Replace the value import of SignerMethod with a type-only import since
SignerMethod is used only in type positions; update the import statement that
currently reads "import { SignerMethod } ..." to use a type import (e.g.,
"import type { SignerMethod } ...") so it satisfies
`@typescript-eslint/consistent-type-imports` and unblocks linting for the types
referenced in issuer.types.ts.

In
`@src/controllers/openid4vc/verifier-sessions/verification-sessions.service.ts`:
- Line 66: Remove the trailing comma after "version: dto.version" in the object
literal and reformat the indented block around lines 180-184 to match project
Prettier settings; specifically, in the VerificationSessionsService (where the
verification session object is constructed) remove the extra comma from the
"version: dto.version" property and align the nested block (the subsequent
object/array entries) to the same indentation level as other object properties
so Prettier linting passes.
- Around line 74-82: The code must enforce mutual exclusivity between
dto.presentationExchange and dto.dcql before building options for
verifier.createAuthorizationRequest: validate that not both are provided and
throw/return a clear error when they are; if only presentationExchange is
present set options.presentationExchange = dto.presentationExchange and do not
set options.dcql, otherwise set options.dcql = dto.dcql; keep the existing
parsedCertificate/requestSigner handling (parsedCertificate.publicJwk.keyId and
options.requestSigner.x5c) unchanged but ensure options.dcql is not forwarded
when presentationExchange is used and vice versa.

In `@src/routes/routes.ts`:
- Around line 1444-1473: The schemas W3cIssuer, W3cCredentialStatus, and
W3cCredentialSubject are too strict (additionalProperties: false) and reject
valid VCs; fix by updating the original TypeScript model definitions that
produce these refs so tsoa will allow extra top-level fields — add an index
signature (e.g. [key: string]: any) or otherwise enable additionalProperties on
the interfaces/types for W3cIssuer, W3cCredentialStatus, and
W3cCredentialSubject (and ensure W3cCredentialSubject still includes the
existing id and claims properties but permits other top-level terms); then
re-run tsoa to regenerate routes.ts so the generated refs no longer set
additionalProperties: false.

In `@src/routes/swagger.json`:
- Around line 1783-1804: The schema for validFrom and validUntil currently uses
anyOf with {type: "string"} plus {type: "string", format: "date-time"}, which
effectively allows any string and bypasses date-time validation; replace the
anyOf with a single schema that enforces date-time (e.g., { "type": "string",
"format": "date-time" }) for the properties named validFrom and validUntil in
the swagger.json so the format constraint is enforced (also make the identical
change for the other occurrence around the 2057-2078 region).

In `@src/utils/oid4vc-agent.ts`:
- Around line 206-247: The code flattens array credentialSubject values by doing
"{ ...(payload.credentialSubject || {}) }" and later assumes a single object
when assigning ids; change the logic to detect whether payload.credentialSubject
is an Array or an Object (preserve arrays as-is), build credentialSubjectRaw
accordingly, and when applying subject ids set the id on each object element
(e.g., loop through credentialSubjectRaw array and assign subjectId per element)
before creating credInstance via JsonTransformer.fromJSON (used with
W3cV2Credential / W3cCredential), and likewise, when mutating
credInstance.credentialSubject assign id per-element if it's an array or assign
directly if it's an object.
- Around line 260-262: The catch block that currently does
fs.appendFileSync('mapper-error.log', e.stack + '\n') (the catch (e: any) {...}
that ends with throw e;) must not perform a synchronous file write that can
throw and block; either log the stack via the existing logger (e.g.,
processLogger or logger) instead of appendFileSync, or if you still want a file
fallback use fs.promises.appendFile(...) and wrap that call in its own try/catch
so any error from the write is swallowed or logged but does not override the
original exception; in all cases ensure you always rethrow the original error
variable e with throw e.
- Around line 193-253: The mapping for JwtVcJson/JwtVcJsonLd currently always
uses issuerDidVerificationMethod and thus drops SignerMethod.X5c signer data;
fix by detecting when credential.signerOptions.method === SignerMethod.X5c (or
binding.method indicates x5c) and either (A) reject/unable-to-map with a clear
error/throw when X5c is configured for JwtVcJson/JwtVcJsonLd, or (B) implement
the x5c path: populate verificationMethod with an object containing the x5c
certificate chain (e.g., include x5c array and appropriate type/id) instead of
issuerDidVerificationMethod, and ensure the returned credential entry (inside
the credentials.map) carries that verificationMethod. Reference symbols:
JwtVcJson/JwtVcJsonLd branch, credential.signerOptions.method, SignerMethod.X5c,
issuerDidVerificationMethod, and the credentials.map return object.

---

Outside diff comments:
In `@src/controllers/openid4vc/holder/credentialBindingResolver.ts`:
- Around line 100-107: Update the comment/error text in
credentialBindingResolver.ts to reflect the expanded fallback support: where the
code checks supportsJwk and credentialFormat against
OpenId4VciCredentialFormatProfile (including SdJwtVc, SdJwtDc, JwtVcJsonLd,
JwtVcJson, MsoMdoc), change any messaging that currently mentions only
"sd-jwt/mdoc" to list or generically state "sd-jwt, JWT VC (JSON/JSON-LD) and
mdoc" so logs and comments align with the actual supported formats checked by
the supportsJwk/credentialFormat branch.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 75954c14-55f0-4b8c-bd82-a66658c621a6

📥 Commits

Reviewing files that changed from the base of the PR and between 3116c1a and cdfee53.

📒 Files selected for processing (12)
  • src/controllers/openid4vc/holder/credentialBindingResolver.ts
  • src/controllers/openid4vc/holder/holder.Controller.ts
  • src/controllers/openid4vc/holder/holder.service.ts
  • src/controllers/openid4vc/issuance-sessions/issuance-sessions.service.ts
  • src/controllers/openid4vc/types/holder.types.ts
  • src/controllers/openid4vc/types/issuer.types.ts
  • src/controllers/openid4vc/types/verifier.types.ts
  • src/controllers/openid4vc/verifier-sessions/verification-sessions.service.ts
  • src/enums/enum.ts
  • src/routes/routes.ts
  • src/routes/swagger.json
  • src/utils/oid4vc-agent.ts

Comment thread src/controllers/openid4vc/holder/holder.service.ts Outdated
Comment thread src/controllers/openid4vc/issuance-sessions/issuance-sessions.service.ts Outdated
Comment thread src/controllers/openid4vc/issuance-sessions/issuance-sessions.service.ts Outdated
Comment thread src/controllers/openid4vc/types/issuer.types.ts Outdated
Comment thread src/routes/routes.ts
Comment on lines +1444 to +1473
"W3cIssuer": {
"dataType": "refObject",
"properties": {
"id": {"dataType":"string","required":true},
},
"additionalProperties": false,
},
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
"W3cCredentialSchema": {
"dataType": "refObject",
"properties": {
"id": {"dataType":"string","required":true},
"type": {"dataType":"string","required":true},
},
"additionalProperties": false,
},
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
"SingleOrArray_W3cCredentialSchema_": {
"dataType": "refAlias",
"type": {"dataType":"union","subSchemas":[{"ref":"W3cCredentialSchema"},{"dataType":"array","array":{"dataType":"refObject","ref":"W3cCredentialSchema"}}],"validators":{}},
},
// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa
"W3cCredentialStatus": {
"dataType": "refObject",
"properties": {
"id": {"dataType":"string","required":true},
"type": {"dataType":"string","required":true},
},
"additionalProperties": false,
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

W3C VC verification schema is overly strict for standard extension fields.

W3cIssuer, W3cCredentialStatus, and W3cCredentialSubject are generated with additionalProperties: false, and W3cCredentialSubject only allows id + claims. This can reject valid W3C credentials where issuer/status/subject contain additional top-level terms (common in real VC payloads), causing false validation failures on verify flows.

Also applies to: 1592-1616

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/routes/routes.ts` around lines 1444 - 1473, The schemas W3cIssuer,
W3cCredentialStatus, and W3cCredentialSubject are too strict
(additionalProperties: false) and reject valid VCs; fix by updating the original
TypeScript model definitions that produce these refs so tsoa will allow extra
top-level fields — add an index signature (e.g. [key: string]: any) or otherwise
enable additionalProperties on the interfaces/types for W3cIssuer,
W3cCredentialStatus, and W3cCredentialSubject (and ensure W3cCredentialSubject
still includes the existing id and claims properties but permits other top-level
terms); then re-run tsoa to regenerate routes.ts so the generated refs no longer
set additionalProperties: false.

Comment thread src/routes/swagger.json
Comment on lines +1783 to +1804
"validUntil": {
"anyOf": [
{
"type": "string"
},
{
"type": "string",
"format": "date-time"
}
]
},
"validFrom": {
"anyOf": [
{
"type": "string"
},
{
"type": "string",
"format": "date-time"
}
]
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Finds "anyOf: [string, string+date-time]" patterns for temporal fields
rg -n -C3 '"valid(From|Until)"\s*:\s*\{|"format"\s*:\s*"date-time"|\"anyOf\"\s*:\s*\[' src/routes/swagger.json

Repository: credebl/agent-controller

Length of output: 265


🏁 Script executed:

#!/bin/bash
# Read the file to check line counts and structure
wc -l src/routes/swagger.json

Repository: credebl/agent-controller

Length of output: 98


🏁 Script executed:

#!/bin/bash
# Check the first occurrence around lines 1783-1804
sed -n '1780,1810p' src/routes/swagger.json | cat -n

Repository: credebl/agent-controller

Length of output: 862


🏁 Script executed:

#!/bin/bash
# Check the second occurrence around lines 2057-2078
sed -n '2054,2084p' src/routes/swagger.json | cat -n

Repository: credebl/agent-controller

Length of output: 802


🏁 Script executed:

#!/bin/bash
# Use a simpler pattern to find validFrom and validUntil
rg -n 'validFrom|validUntil' src/routes/swagger.json | head -20

Repository: credebl/agent-controller

Length of output: 229


🏁 Script executed:

#!/bin/bash
# Get context around each occurrence to understand which schemas they belong to
rg -B10 '"validUntil"|"validFrom"' src/routes/swagger.json | grep -E '(^[0-9]+-|"(validUntil|validFrom)"|"title"|schema)'

Repository: credebl/agent-controller

Length of output: 199


🏁 Script executed:

#!/bin/bash
# Check context for the first occurrence to understand parent schema
sed -n '1770,1810p' src/routes/swagger.json | cat -n

Repository: credebl/agent-controller

Length of output: 1088


🏁 Script executed:

#!/bin/bash
# Look for all temporal field definitions in the file
rg -B5 -A8 '"validUntil"' src/routes/swagger.json | head -80

Repository: credebl/agent-controller

Length of output: 832


🏁 Script executed:

#!/bin/bash
# Get all anyOf occurrences with their exact line numbers and context
rg -n 'anyOf' src/routes/swagger.json | grep -B2 -A2 'validFrom\|validUntil'

Repository: credebl/agent-controller

Length of output: 50


🏁 Script executed:

#!/bin/bash
# Find all places where validUntil or validFrom have anyOf structure
rg -B1 -A10 '"(validUntil|validFrom)"\s*:\s*\{\s*"anyOf"' src/routes/swagger.json | grep -E '^[0-9]+' | head -30

Repository: credebl/agent-controller

Length of output: 50


🏁 Script executed:

#!/bin/bash
# Count how many validFrom/validUntil fields use anyOf pattern
rg '"(validUntil|validFrom)"\s*:\s*\{\s*"anyOf"' src/routes/swagger.json | wc -l

Repository: credebl/agent-controller

Length of output: 70


🏁 Script executed:

#!/bin/bash
# Count total validFrom and validUntil definitions
rg '"(validUntil|validFrom)"\s*:' src/routes/swagger.json | wc -l

Repository: credebl/agent-controller

Length of output: 70


🏁 Script executed:

#!/bin/bash
# Extract the exact structure for each validFrom/validUntil
for line in 1783 1794 1854 1858 2057 2068; do
  echo "=== Line $line ==="
  sed -n "${line},$((line+12))p" src/routes/swagger.json | head -15
done

Repository: credebl/agent-controller

Length of output: 1672


validFrom / validUntil currently bypass date-time validation due to anyOf shape.

These fields are declared as anyOf of { type: "string" } and { type: "string", format: "date-time" }, which effectively accepts any string and makes the date-time constraint non-operative. This can let invalid temporal values through contract validation.

🔧 Suggested schema fix
- "validUntil": {
-   "anyOf": [
-     { "type": "string" },
-     { "type": "string", "format": "date-time" }
-   ]
- },
+ "validUntil": {
+   "type": "string",
+   "format": "date-time"
+ },

- "validFrom": {
-   "anyOf": [
-     { "type": "string" },
-     { "type": "string", "format": "date-time" }
-   ]
- },
+ "validFrom": {
+   "type": "string",
+   "format": "date-time"
+ },

Also applies to: 2057-2078

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/routes/swagger.json` around lines 1783 - 1804, The schema for validFrom
and validUntil currently uses anyOf with {type: "string"} plus {type: "string",
format: "date-time"}, which effectively allows any string and bypasses date-time
validation; replace the anyOf with a single schema that enforces date-time
(e.g., { "type": "string", "format": "date-time" }) for the properties named
validFrom and validUntil in the swagger.json so the format constraint is
enforced (also make the identical change for the other occurrence around the
2057-2078 region).

Comment thread src/utils/oid4vc-agent.ts
Comment thread src/utils/oid4vc-agent.ts
Comment thread src/utils/oid4vc-agent.ts
@sagarkhole4 sagarkhole4 requested a review from sairanjit May 21, 2026 03:37
Comment thread src/controllers/openid4vc/holder/holder.service.ts Outdated
Comment thread src/controllers/openid4vc/holder/holder.service.ts Outdated
Comment thread src/enums/enum.ts Outdated
Comment thread src/utils/oid4vc-agent.ts
Signed-off-by: Sagar Khole <sagar.khole@ayanworks.com>
@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/controllers/openid4vc/issuance-sessions/issuance-sessions.service.ts (1)

73-73: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pass options.version to the offer instead of hardcoding 'v1'.

The code computes effectiveVersion (line 31) and transforms the payload accordingly, but line 73 always passes version: 'v1' to the offer creation. This means wallets negotiating v2.0 credentials will receive v1 offer metadata despite having v2.0-transformed payloads, making the versioning system ineffective.

🔧 Proposed fix
-      version: 'v1',
+      version: options.version ?? 'v1',
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/controllers/openid4vc/issuance-sessions/issuance-sessions.service.ts` at
line 73, The offer is being built with a hardcoded version: 'v1' which ignores
the computed effectiveVersion and breaks v2 negotiations; update the object
passed to the offer creation in issuance-sessions.service.ts to use the computed
effectiveVersion (or options.version if that's what you prefer) instead of the
literal 'v1' so the version field matches the transformed payload (refer to
effectiveVersion and the offer creation call near the existing version: 'v1'
line).
src/controllers/openid4vc/holder/holder.service.ts (1)

47-58: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Major: complete the W3C VC v2.0 holder lifecycle (store/list/delete)

  • getW3cCredentials only returns agentReq.agent.w3cCredentials.getAll(); w3cV2Credentials.getAll() support is commented out.
  • requestAndStoreCredentials only stores W3cCredentialRecord; the W3cV2CredentialRecord store branch is commented out, so v2 records hit Unsupported credential record type.
  • deleteCredential only uses W3cCredentialService; the W3cV2CredentialService.removeCredentialRecord fallback is commented out and v2 deletion never happens.

This breaks the advertised v2.0 flow (version?: 'v1.1' | 'v2.0', plus v2 mapping in oid4vc-agent.ts -> W3cV2Credential).

Suggested direction
 import {
   Mdoc,
   SdJwtVcRecord,
   MdocRecord,
   W3cCredentialRecord,
   W3cCredentialService,
-  // W3cV2CredentialRecord,
-  // W3cV2CredentialService,
+  W3cV2CredentialRecord,
+  W3cV2CredentialService,
 } from '`@credo-ts/core`'
@@
   public async getW3cCredentials(agentReq: Req) {
-    return await agentReq.agent.w3cCredentials.getAll()
+    const [v1Records, v2Records] = await Promise.all([
+      agentReq.agent.w3cCredentials.getAll(),
+      agentReq.agent.w3cV2Credentials.getAll(),
+    ])
+
+    return [...v1Records, ...v2Records]
   }
@@
         if (
           credentialRecord instanceof W3cCredentialRecord ||
           (credentialRecord as { type?: string }).type === 'W3cCredentialRecord'
         ) {
           return await agentReq.agent.w3cCredentials.store({
             record: credentialRecord as W3cCredentialRecord,
           })
         }
+
+        if (
+          credentialRecord instanceof W3cV2CredentialRecord ||
+          (credentialRecord as { type?: string }).type === 'W3cV2CredentialRecord'
+        ) {
+          return await agentReq.agent.w3cV2Credentials.store({
+            record: credentialRecord as W3cV2CredentialRecord,
+          })
+        }
@@
     if (credentialType === CredentialType.W3C_VC) {
       const w3cCredentialService = await agentReq.agent.dependencyManager.resolve(W3cCredentialService)
+      const w3cV2CredentialService = await agentReq.agent.dependencyManager.resolve(W3cV2CredentialService)
@@
       try {
         return await w3cCredentialService.removeCredentialRecord(agentReq.agent.context, credentialId)
       } catch (error) {
-        throw error
+        return await w3cV2CredentialService.removeCredentialRecord(agentReq.agent.context, credentialId)
       }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/controllers/openid4vc/holder/holder.service.ts` around lines 47 - 58,
getW3cCredentials currently returns only agentReq.agent.w3cCredentials.getAll()
and the w3cV2 path is commented out—re-enable and combine both by fetching
agentReq.agent.w3cCredentials.getAll() and
agentReq.agent.w3cV2Credentials.getAll() (e.g., Promise.all) and returning the
concatenated results; in requestAndStoreCredentials un-comment and implement the
W3cV2 branch so that when the credential is a W3cV2CredentialRecord you store it
using the W3cV2CredentialRecord handling code (mirror the W3cCredentialRecord
branch but use the v2 record/service); in deleteCredential try removing via
W3cCredentialService first and if not found/fails, call
W3cV2CredentialService.removeCredentialRecord as a fallback so v2 records are
deletable (ensure you reference the existing functions/classes:
getW3cCredentials, requestAndStoreCredentials, deleteCredential,
W3cCredentialRecord, W3cV2CredentialRecord,
agentReq.agent.w3cV2Credentials.getAll(), and
W3cV2CredentialService.removeCredentialRecord).
🧹 Nitpick comments (1)
src/controllers/openid4vc/issuance-sessions/issuance-sessions.service.ts (1)

174-197: ⚡ Quick win

Remove unreachable else block.

The early return at line 129 ensures that lines 133-199 only execute when version === 'v2.0'. This makes the conditional at line 174 always true and the else block (lines 186-197) unreachable dead code.

♻️ Proposed cleanup
-    if (version === 'v2.0') {
-      const currentCtx = Array.isArray(transformed['`@context`'])
-        ? transformed['`@context`']
-        : typeof transformed['`@context`'] === 'string'
-          ? [transformed['`@context`']]
-          : []
-
-      const ctxSet = new Set(currentCtx)
-      ctxSet.delete(v1Context)
-      ctxSet.delete(v2Context)
-      // W3C V2.0 requires the V2 context to be the very first element.
-      transformed['`@context`'] = [v2Context, v1Context, ...Array.from(ctxSet)]
-    } else {
-      // W3C V1.1 / Default behavior
-      if (!transformed['`@context`']) {
-        transformed['`@context`'] = [v1Context]
-      } else if (Array.isArray(transformed['`@context`'])) {
-        const ctxSet = new Set(transformed['`@context`'])
-        ctxSet.delete(v1Context)
-        transformed['`@context`'] = [v1Context, ...Array.from(ctxSet)]
-      } else if (typeof transformed['`@context`'] === 'string') {
-        transformed['`@context`'] = [v1Context, transformed['`@context`']]
-      }
-    }
+    const currentCtx = Array.isArray(transformed['`@context`'])
+      ? transformed['`@context`']
+      : typeof transformed['`@context`'] === 'string'
+        ? [transformed['`@context`']]
+        : []
+
+    const ctxSet = new Set(currentCtx)
+    ctxSet.delete(v1Context)
+    ctxSet.delete(v2Context)
+    // W3C V2.0 requires the V2 context to be the very first element.
+    transformed['`@context`'] = [v2Context, v1Context, ...Array.from(ctxSet)]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/controllers/openid4vc/issuance-sessions/issuance-sessions.service.ts`
around lines 174 - 197, The else branch that handles non-v2 contexts is dead
code because the surrounding function returns early unless version === 'v2.0';
remove the unreachable else block and simplify the code to only perform the v2.0
handling for transformed['`@context`'] (using v2Context, v1Context and ctxSet),
keeping the existing logic that ensures v2Context is first and v1Context second;
also remove any related array/string checks inside the removed branch and ensure
no unused variables remain (version, v1Context, v2Context,
transformed['`@context`']).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/controllers/openid4vc/issuance-sessions/issuance-sessions.service.ts`:
- Around line 5-7: Reorder and group the related `@credo-ts` imports to satisfy
import-order rules: move the CREDENTIALS_CONTEXT_V1_URL and
CREDENTIALS_CONTEXT_V2_URL import (symbols CREDENTIALS_CONTEXT_V1_URL,
CREDENTIALS_CONTEXT_V2_URL) so it appears before the
OpenId4VcIssuanceSessionRepository import (symbol
OpenId4VcIssuanceSessionRepository) and remove the blank line between them so
both `@credo-ts` imports are adjacent and correctly ordered.

---

Duplicate comments:
In `@src/controllers/openid4vc/holder/holder.service.ts`:
- Around line 47-58: getW3cCredentials currently returns only
agentReq.agent.w3cCredentials.getAll() and the w3cV2 path is commented
out—re-enable and combine both by fetching
agentReq.agent.w3cCredentials.getAll() and
agentReq.agent.w3cV2Credentials.getAll() (e.g., Promise.all) and returning the
concatenated results; in requestAndStoreCredentials un-comment and implement the
W3cV2 branch so that when the credential is a W3cV2CredentialRecord you store it
using the W3cV2CredentialRecord handling code (mirror the W3cCredentialRecord
branch but use the v2 record/service); in deleteCredential try removing via
W3cCredentialService first and if not found/fails, call
W3cV2CredentialService.removeCredentialRecord as a fallback so v2 records are
deletable (ensure you reference the existing functions/classes:
getW3cCredentials, requestAndStoreCredentials, deleteCredential,
W3cCredentialRecord, W3cV2CredentialRecord,
agentReq.agent.w3cV2Credentials.getAll(), and
W3cV2CredentialService.removeCredentialRecord).

In `@src/controllers/openid4vc/issuance-sessions/issuance-sessions.service.ts`:
- Line 73: The offer is being built with a hardcoded version: 'v1' which ignores
the computed effectiveVersion and breaks v2 negotiations; update the object
passed to the offer creation in issuance-sessions.service.ts to use the computed
effectiveVersion (or options.version if that's what you prefer) instead of the
literal 'v1' so the version field matches the transformed payload (refer to
effectiveVersion and the offer creation call near the existing version: 'v1'
line).

---

Nitpick comments:
In `@src/controllers/openid4vc/issuance-sessions/issuance-sessions.service.ts`:
- Around line 174-197: The else branch that handles non-v2 contexts is dead code
because the surrounding function returns early unless version === 'v2.0'; remove
the unreachable else block and simplify the code to only perform the v2.0
handling for transformed['`@context`'] (using v2Context, v1Context and ctxSet),
keeping the existing logic that ensures v2Context is first and v1Context second;
also remove any related array/string checks inside the removed branch and ensure
no unused variables remain (version, v1Context, v2Context,
transformed['`@context`']).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2f2c9616-ddad-472a-a729-4f264f42ce21

📥 Commits

Reviewing files that changed from the base of the PR and between cdfee53 and 2a653b1.

📒 Files selected for processing (4)
  • src/controllers/openid4vc/holder/holder.service.ts
  • src/controllers/openid4vc/issuance-sessions/issuance-sessions.service.ts
  • src/controllers/openid4vc/types/issuer.types.ts
  • src/controllers/openid4vc/verifier-sessions/verification-sessions.service.ts

Comment on lines 5 to +7
import { OpenId4VcIssuanceSessionRepository } from '@credo-ts/openid4vc'

import { CREDENTIALS_CONTEXT_V1_URL, CREDENTIALS_CONTEXT_V2_URL } from '@credo-ts/core'
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix import order violations.

The pipeline and ESLint are flagging import order issues: the @credo-ts/core import should appear before @credo-ts/openid4vc, and the empty line between related @credo-ts imports should be removed.

📦 Proposed fix
 import type { OpenId4VcIssuanceSessionState } from '`@credo-ts/openid4vc`'
+import { CREDENTIALS_CONTEXT_V1_URL, CREDENTIALS_CONTEXT_V2_URL } from '`@credo-ts/core`'
 import { OpenId4VcIssuanceSessionRepository } from '`@credo-ts/openid4vc`'
-
-import { CREDENTIALS_CONTEXT_V1_URL, CREDENTIALS_CONTEXT_V2_URL } from '`@credo-ts/core`'
-
 import { CredentialFormat, SignerMethod } from '../../../enums/enum'
🧰 Tools
🪛 ESLint

[error] 5-5: There should be no empty line within import group

(import/order)


[error] 7-7: @credo-ts/core import should occur before import of @credo-ts/openid4vc

(import/order)

🪛 GitHub Actions: Continuous Integration / Validate

[error] 5-5: ESLint import/order: There should be no empty line within import group

🪛 GitHub Check: Validate

[failure] 7-7:
@credo-ts/core import should occur before import of @credo-ts/openid4vc


[failure] 5-5:
There should be no empty line within import group

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/controllers/openid4vc/issuance-sessions/issuance-sessions.service.ts`
around lines 5 - 7, Reorder and group the related `@credo-ts` imports to satisfy
import-order rules: move the CREDENTIALS_CONTEXT_V1_URL and
CREDENTIALS_CONTEXT_V2_URL import (symbols CREDENTIALS_CONTEXT_V1_URL,
CREDENTIALS_CONTEXT_V2_URL) so it appears before the
OpenId4VcIssuanceSessionRepository import (symbol
OpenId4VcIssuanceSessionRepository) and remove the blank line between them so
both `@credo-ts` imports are adjacent and correctly ordered.

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.

2 participants