Skip to content

Feat/sd jwt revocation flow#1594

Open
sagarkhole4 wants to merge 20 commits intomainfrom
feat/sd-jwt-revocation-flow
Open

Feat/sd jwt revocation flow#1594
sagarkhole4 wants to merge 20 commits intomainfrom
feat/sd-jwt-revocation-flow

Conversation

@sagarkhole4
Copy link
Copy Markdown
Contributor

@sagarkhole4 sagarkhole4 commented Mar 30, 2026

Feat/sd jwt revocation flow

Summary by CodeRabbit

  • New Features

    • Credential revocation endpoint to revoke issued credentials.
    • Revocable offers: optional isRevocable flag when creating credential offers.
    • Status-list management: allocate and track revocation slots for SD-JWT credentials.
  • Chores

    • New STATUS_LIST_HOST environment variable; startup now validates it.
    • Persistence and DB support added for status-list allocations and issued-credential records.

Signed-off-by: Sagar Khole <sagar.khole@ayanworks.com>
Signed-off-by: Sagar Khole <sagar.khole@ayanworks.com>
Signed-off-by: Sagar Khole <sagar.khole@ayanworks.com>
Signed-off-by: Sagar Khole <sagar.khole@ayanworks.com>
Signed-off-by: Sagar Khole <sagar.khole@ayanworks.com>
Signed-off-by: Sagar Khole <sagar.khole@ayanworks.com>
Signed-off-by: Sagar Khole <sagar.khole@ayanworks.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 30, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds revocable SD‑JWT support: new STATUS_LIST_HOST env var and startup check, DB models/migration for status-list allocations and issued credentials, bitmap-based allocator service, allocation/persistence/release integrated into issuance flows, NATS + HTTP revoke handlers across API Gateway, OID4VC service, and agent-service.

Changes

Cohort / File(s) Summary
Environment
/.env.demo, /.env.sample
Added STATUS_LIST_HOST= entry and ensured trailing newline at EOF.
Database schema & migration
libs/prisma-service/prisma/schema.prisma, libs/prisma-service/prisma/migrations/.../migration.sql
Added status_list_allocation and issued_oid4vc_credentials models/tables; added indexes/constraints; added webhookSecret column to org_agents; adjusted oid4vc_credentials.orgId to be nullable.
Constants
libs/common/src/common.constant.ts
Added DEFAULT_STATUS_LIST_SIZE = 131072.
Status-list allocator
apps/oid4vc-issuance/src/status-list-allocator.service.ts
New RandomBitmapIndexAllocator and StatusListAllocatorService implementing allocate/save/get/release semantics with Prisma transactions and bitmap management.
OID4VC service (logic)
apps/oid4vc-issuance/src/oid4vc-issuance.service.ts
Integrated status-list allocation for revocable SD‑JWTs, persisted allocations, rollback on failure, added revokeCredential, adjusted error handling and webhook orgId assignment.
OID4VC controllers & module
apps/oid4vc-issuance/src/oid4vc-issuance.controller.ts, apps/oid4vc-issuance/src/oid4vc-issuance.module.ts, apps/oid4vc-issuance/src/main.ts
Added oid4vc-revoke-credential NATS handler; tightened typings; registered StatusListAllocatorService; main.ts enforces STATUS_LIST_HOST presence at startup.
OID4VC types & builders
apps/oid4vc-issuance/interfaces/oid4vc-issuer-sessions.interfaces.ts, apps/oid4vc-issuance/libs/helpers/credential-sessions.builder.ts
Added isRevocable to DTO/interface; added statusListDetails to built credential types; updated disclosure-frame builder to support payload-driven child arrays and array replication of child frames.
API Gateway (OID4VC)
apps/api-gateway/src/oid4vc-issuance/dtos/issuer-sessions.dto.ts, apps/api-gateway/src/oid4vc-issuance/oid4vc-issuance.controller.ts, apps/api-gateway/src/oid4vc-issuance/oid4vc-issuance.service.ts
Added optional isRevocable DTO field; added authenticated POST .../revoke endpoint and service method that sends NATS revoke message.
Agent service
apps/agent-service/src/agent-service.controller.ts, apps/agent-service/src/agent-service.service.ts
Added oidcRevokeCredential NATS handler and service method that POSTs to provided URL using org agent API key; logs and rethrows HTTP errors.
DTO tweaks
apps/api-gateway/src/oid4vc-issuance/dtos/issuer-sessions.dto.ts
Added isRevocable optional property; adjusted credential exp example computation to a fixed millisecond delta.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant GW as API_Gateway
    participant OID as OID4VC_Service
    participant Alloc as StatusListAllocator
    participant DB as Database
    participant Agent as Agent_Service

    Client->>GW: POST /create (isRevocable, SD‑JWT)
    GW->>OID: createOidcCredentialOffer(...)
    OID->>Alloc: allocate(orgId, issuerDid)
    Alloc->>DB: find/create active allocation, update bitmap
    Alloc-->>OID: {listId, index}
    OID->>Agent: create credential offer (agent)
    Agent-->>OID: issuanceSessionId
    OID->>Alloc: saveCredentialAllocation(issuanceSessionId, listId, index)
    Alloc->>DB: insert issued_oid4vc_credentials
    OID-->>GW: credential offer + statusListDetails
    GW-->>Client: 200 OK
Loading
sequenceDiagram
    participant Client as Client
    participant GW as API_Gateway
    participant OID as OID4VC_Service
    participant Alloc as StatusListAllocator
    participant DB as Database
    participant Agent as Agent_Service

    Client->>GW: POST /revoke (issuanceSessionId)
    GW->>OID: revokeCredential(issuanceSessionId)
    OID->>Alloc: getCredentialAllocations(orgId, issuanceSessionId)
    Alloc->>DB: fetch issued_oid4vc_credentials
    Alloc-->>OID: [{listId,index,statusListUri}]
    loop per allocation
      OID->>Agent: oidcRevokeCredential({ url: statusListUri, orgId })
      Agent->>Agent: resolve org agent API key
      Agent->>Agent: POST {} to url with authorization header
      Agent-->>OID: revoke response
      OID->>Alloc: release(listId,index)
      Alloc->>DB: update bitmap, decrement allocatedCount
    end
    OID-->>GW: revoked result
    GW-->>Client: 200 OK
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 I hopped through bitmaps late at night,

I chased free indexes till the moon was bright,
I stamped each listId with a curious paw,
I nudged revokes along a quiet straw,
Hooray — status lists take flight!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feat/sd jwt revocation flow' directly and clearly describes the main change: adding SD-JWT credential revocation functionality across the codebase. It aligns well with the substantial additions to revocation handling, status list allocation, and related infrastructure.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/sd-jwt-revocation-flow

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: 6

🧹 Nitpick comments (4)
apps/oid4vc-issuance/src/status-list-allocator.service.ts (4)

130-133: Potential allocatedCount drift between DB and bitmap.

The DB update uses activeList.allocatedCount + 1 (the stale value read at transaction start), but the actual allocated count may differ if the bitmap already had bits set from prior runs. Consider using the allocator's internally computed count for consistency.

Suggested fix

Expose the internal count from the allocator:

// In RandomBitmapIndexAllocator
public getAllocatedCount(): number {
  return this.allocatedCount;
}

Then use it during the update:

         await tx.status_list_allocation.update({
           where: { id: activeList.id },
           data: {
             bitmap: Buffer.from(allocator.export()),
-            allocatedCount: activeList.allocatedCount + 1
+            allocatedCount: allocator.getAllocatedCount()
           }
         });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/oid4vc-issuance/src/status-list-allocator.service.ts` around lines 130 -
133, The DB update uses activeList.allocatedCount + 1 which can drift from the
allocator's real state; add an accessor on RandomBitmapIndexAllocator (e.g.,
getAllocatedCount() or expose allocatedCount) and use
allocator.getAllocatedCount() when writing the updated data (bitmap and
allocatedCount) instead of activeList.allocatedCount + 1 so the saved
allocatedCount matches the bitmap exported by allocator.

6-6: Prefer node:crypto module specifier.

Using the node: prefix makes it explicit this is a Node.js built-in module and avoids potential conflicts with npm packages of the same name.

Suggested fix
-import { randomInt, randomUUID } from 'crypto';
+import { randomInt, randomUUID } from 'node:crypto';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/oid4vc-issuance/src/status-list-allocator.service.ts` at line 6, The
import currently pulls randomInt and randomUUID from 'crypto'; update the module
specifier to use the Node built-in form 'node:crypto' (i.e., change the import
that references randomInt and randomUUID to import from 'node:crypto') so it
explicitly uses the Node.js core module and avoids name conflicts with similarly
named npm packages.

137-147: Fragile error detection via string comparison.

Checking error.message === 'No indexes left' is brittle—message changes or localization would break this logic. Consider using a custom error class for reliable identification.

Suggested approach

Define a custom error at the top of the file:

export class NoIndexesLeftError extends Error {
  constructor() {
    super('No indexes left');
    this.name = 'NoIndexesLeftError';
  }
}

Then throw and catch it explicitly:

-      throw new Error('No indexes left');
+      throw new NoIndexesLeftError();
-        if ('No indexes left' === error.message) {
+        if (error instanceof NoIndexesLeftError) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/oid4vc-issuance/src/status-list-allocator.service.ts` around lines 137 -
147, The catch block is using a brittle string comparison on error.message to
detect the "No indexes left" case; define and export a custom error class (e.g.,
NoIndexesLeftError) and throw that from the allocation logic instead of a plain
Error, then update the catch to use "if (error instanceof NoIndexesLeftError)"
to reliably identify the condition; keep the retry behavior (calling
tx.status_list_allocation.update with activeList.id to set isActive: false and
rethrow a new Error('Retry allocation, list full') or propagate as appropriate).

9-11: Mark immutable members as readonly.

These members are initialized in the constructor and never reassigned. Marking them readonly improves type safety and communicates intent.

Suggested fix
-  private bitmap: Uint8Array;
-  private capacity: number;
+  private readonly bitmap: Uint8Array;
+  private readonly capacity: number;
   private allocatedCount: number;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/oid4vc-issuance/src/status-list-allocator.service.ts` around lines 9 -
11, The fields bitmap, capacity, and allocatedCount in the StatusListAllocator
class are set in the constructor and never reassigned; mark them readonly to
express immutability and improve type safety by changing their declarations to
readonly bitmap: Uint8Array, readonly capacity: number, and readonly
allocatedCount: number (ensure there are no later assignments to these
properties elsewhere such as in methods of StatusListAllocator before making
them readonly).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/api-gateway/src/oid4vc-issuance/dtos/issuer-sessions.dto.ts`:
- Around line 191-193: The isRevocable property in both
CreateOidcCredentialOfferDto and CreateCredentialOfferD2ADto lacks a boolean
validator, allowing string "true"/"false" to pass and cause incorrect truthy
checks; add `@IsBoolean`() immediately after `@IsOptional`() on the isRevocable
field in both DTO classes and update the imports to include IsBoolean from
class-validator so the validation enforces a real boolean type before
service-layer checks.

In `@apps/oid4vc-issuance/src/oid4vc-issuance.service.ts`:
- Around line 746-767: The code currently swallows allocation errors for
revocable SD-JWT credentials (inside the oidcCredentialD2APayload.isRevocable
branch and loop over oidcCredentialD2APayload.credentials) and simply logs via
this.logger.warn, which can produce non-revocable offers; change the logic so
that when a credential with format CredentialFormat.SdJwtVc requires
statusListDetails and statusListAllocatorService.allocate(orgId,
agentDetailsForAlloc.orgDid) fails you either retry the allocation a limited
number of times or propagate the failure by throwing (e.g., throw new
BadRequestException or an appropriate error) so the whole request fails; update
the loop around the allocation attempt (where allocError is caught) to implement
the retry policy or rethrow the error instead of only logging so no revocable
credential is sent without statusListDetails.

In `@apps/oid4vc-issuance/src/status-list-allocator.service.ts`:
- Around line 44-58: The allocate() method uses an unbounded random retry loop
which degrades when the bitmap is nearly full; modify allocate() to try a
bounded number of random attempts (e.g., MAX_RANDOM_ATTEMPTS) using
randomInt(this.capacity) and isSet(idx), and if none succeed fall back to a
deterministic linear scan across the bitmap to find the first unset index, call
set(idx) and increment this.allocatedCount before returning; keep the existing
capacity check and throw when full. Use clear symbols from the file such as
allocate(), isSet(), set(), this.capacity and this.allocatedCount, and introduce
a local MAX_RANDOM_ATTEMPTS constant to control the fallback threshold.

In `@libs/prisma-service/prisma/schema.prisma`:
- Around line 653-662: Add an orgId FK to the issued_oid4vc_credentials model
and persist it when allocations are created: update the Prisma model
issued_oid4vc_credentials to include orgId (UUID) with a foreign key relation to
your org/issuer table and add a composite index on (orgId, issuanceSessionId);
update saveCredentialAllocation() to set orgId when creating rows and update
revokeCredential() to load allocations by the composite key [orgId,
issuanceSessionId] instead of issuanceSessionId alone; also mirror the schema
change by adding the orgId column, FK constraint and the composite index in
libs/prisma-service/prisma/migrations/20260323183000_index_allocation/migration.sql
so queries can be scoped and indexed by org.
- Line 235: The org_agents model currently declares the field webhookSecret
twice; remove the duplicate declaration so only a single webhookSecret String?
`@db.VarChar` field remains in the org_agents model, ensuring the remaining
declaration has the correct name, type and attributes; then run Prisma
format/generate to verify the schema compiles and update any code that
referenced the removed duplicate if necessary.

---

Nitpick comments:
In `@apps/oid4vc-issuance/src/status-list-allocator.service.ts`:
- Around line 130-133: The DB update uses activeList.allocatedCount + 1 which
can drift from the allocator's real state; add an accessor on
RandomBitmapIndexAllocator (e.g., getAllocatedCount() or expose allocatedCount)
and use allocator.getAllocatedCount() when writing the updated data (bitmap and
allocatedCount) instead of activeList.allocatedCount + 1 so the saved
allocatedCount matches the bitmap exported by allocator.
- Line 6: The import currently pulls randomInt and randomUUID from 'crypto';
update the module specifier to use the Node built-in form 'node:crypto' (i.e.,
change the import that references randomInt and randomUUID to import from
'node:crypto') so it explicitly uses the Node.js core module and avoids name
conflicts with similarly named npm packages.
- Around line 137-147: The catch block is using a brittle string comparison on
error.message to detect the "No indexes left" case; define and export a custom
error class (e.g., NoIndexesLeftError) and throw that from the allocation logic
instead of a plain Error, then update the catch to use "if (error instanceof
NoIndexesLeftError)" to reliably identify the condition; keep the retry behavior
(calling tx.status_list_allocation.update with activeList.id to set isActive:
false and rethrow a new Error('Retry allocation, list full') or propagate as
appropriate).
- Around line 9-11: The fields bitmap, capacity, and allocatedCount in the
StatusListAllocator class are set in the constructor and never reassigned; mark
them readonly to express immutability and improve type safety by changing their
declarations to readonly bitmap: Uint8Array, readonly capacity: number, and
readonly allocatedCount: number (ensure there are no later assignments to these
properties elsewhere such as in methods of StatusListAllocator before making
them readonly).
🪄 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: 82f7c59f-cc77-4ee0-ae03-25214d229064

📥 Commits

Reviewing files that changed from the base of the PR and between 6f7da22 and 380e0cc.

📒 Files selected for processing (17)
  • .env.demo
  • .env.sample
  • apps/agent-service/src/agent-service.controller.ts
  • apps/agent-service/src/agent-service.service.ts
  • apps/api-gateway/src/oid4vc-issuance/dtos/issuer-sessions.dto.ts
  • apps/api-gateway/src/oid4vc-issuance/oid4vc-issuance.controller.ts
  • apps/api-gateway/src/oid4vc-issuance/oid4vc-issuance.service.ts
  • apps/oid4vc-issuance/interfaces/oid4vc-issuer-sessions.interfaces.ts
  • apps/oid4vc-issuance/libs/helpers/credential-sessions.builder.ts
  • apps/oid4vc-issuance/src/main.ts
  • apps/oid4vc-issuance/src/oid4vc-issuance.controller.ts
  • apps/oid4vc-issuance/src/oid4vc-issuance.module.ts
  • apps/oid4vc-issuance/src/oid4vc-issuance.service.ts
  • apps/oid4vc-issuance/src/status-list-allocator.service.ts
  • libs/common/src/common.constant.ts
  • libs/prisma-service/prisma/migrations/20260323183000_index_allocation/migration.sql
  • libs/prisma-service/prisma/schema.prisma

Comment thread apps/api-gateway/src/oid4vc-issuance/dtos/issuer-sessions.dto.ts Outdated
Comment thread apps/oid4vc-issuance/src/oid4vc-issuance.service.ts Outdated
Comment thread apps/oid4vc-issuance/src/oid4vc-issuance.service.ts
Comment thread apps/oid4vc-issuance/src/status-list-allocator.service.ts
Comment thread libs/prisma-service/prisma/schema.prisma
Comment thread libs/prisma-service/prisma/schema.prisma
Signed-off-by: Sagar Khole <sagar.khole@ayanworks.com>
Signed-off-by: Sagar Khole <sagar.khole@ayanworks.com>
Signed-off-by: Sagar Khole <sagar.khole@ayanworks.com>
Signed-off-by: Sagar Khole <sagar.khole@ayanworks.com>
Signed-off-by: Sagar Khole <sagar.khole@ayanworks.com>
Signed-off-by: Sagar Khole <sagar.khole@ayanworks.com>
@tipusinghaw tipusinghaw force-pushed the feat/sd-jwt-revocation-flow branch from 380e0cc to fe14467 Compare April 1, 2026 08:41
Signed-off-by: Tipu_Singh <tipu.singh@ayanworks.com>
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: 3

♻️ Duplicate comments (4)
apps/oid4vc-issuance/src/status-list-allocator.service.ts (1)

44-58: ⚠️ Potential issue | 🟠 Major

Performance degradation risk: unbounded loop when bitmap is nearly full.

The random allocation strategy in the while (true) loop becomes increasingly inefficient as the bitmap fills up. At 90% capacity, ~10 attempts per allocation are expected; at 99%, ~100 attempts. This can cause severe latency spikes.

🔧 Proposed fix: bounded random with linear fallback
   public allocate(): number {
     if (this.allocatedCount === this.capacity) {
       throw new Error('No indexes left');
     }

-    while (true) {
-      const idx = randomInt(this.capacity);
-
-      if (!this.isSet(idx)) {
-        this.set(idx);
-        this.allocatedCount++;
-        return idx;
+    const maxRandomAttempts = 10;
+    for (let attempt = 0; attempt < maxRandomAttempts; attempt++) {
+      const idx = randomInt(this.capacity);
+      if (!this.isSet(idx)) {
+        this.set(idx);
+        this.allocatedCount++;
+        return idx;
       }
     }
+
+    // Fallback: linear scan from random starting point
+    const start = randomInt(this.capacity);
+    for (let offset = 0; offset < this.capacity; offset++) {
+      const idx = (start + offset) % this.capacity;
+      if (!this.isSet(idx)) {
+        this.set(idx);
+        this.allocatedCount++;
+        return idx;
+      }
+    }
+
+    throw new Error('No indexes left');
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/oid4vc-issuance/src/status-list-allocator.service.ts` around lines 44 -
58, The allocate() method uses an unbounded random retry loop which degrades as
the bitmap fills; change allocate() (and use of isSet, set, allocatedCount,
capacity) to first try a bounded number of random attempts (e.g., maxRetries =
max(10, Math.ceil(capacity * 0.01))) and if none succeed fall back to a
deterministic linear scan over the bitmap to find the first clear index, set it,
increment allocatedCount and return it; preserve the existing full-capacity
check and throw the same error if no index is free after the fallback.
libs/prisma-service/prisma/schema.prisma (2)

235-235: ⚠️ Potential issue | 🔴 Critical

Remove duplicate webhookSecret field declaration.

The org_agents model declares webhookSecret twice (lines 235 and 243), which violates Prisma schema syntax and will block client generation and migrations.

🔧 Remove duplicate

Remove line 243:

   organisation        organisation?       `@relation`(fields: [orgId], references: [id])
   webhookUrl          String?             `@db.VarChar`
-  webhookSecret       String?             `@db.VarChar`
   org_dids            org_dids[]

Also applies to: 243-243

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/prisma-service/prisma/schema.prisma` at line 235, The org_agents model
contains a duplicate field declaration for webhookSecret which breaks Prisma
schema parsing; open the prisma schema where the model org_agents is defined,
locate both webhookSecret entries and remove the redundant one (leave a single
webhookSecret String? `@db.VarChar` definition), then run Prisma validate/generate
to ensure the schema compiles; reference the org_agents model and the
webhookSecret field when making the change.

653-662: ⚠️ Potential issue | 🟠 Major

Add orgId to enable org-scoped revocation queries.

The revokeCredential() method queries allocations only by issuanceSessionId, but the /orgs/:orgId/.../revoke endpoint receives an orgId parameter that isn't validated against the allocation ownership. Without orgId:

  1. Any org could potentially revoke another org's credentials if they guess the session ID
  2. Queries lack an efficient index path
🔧 Proposed addition
 model issued_oid4vc_credentials {
   id                String   `@id` `@default`(uuid()) `@db.Uuid`
   credentialId      String   `@unique`
   listId            String   `@db.Uuid`
   index             Int
   issuanceSessionId String   `@db.VarChar`
+  orgId             String   `@db.Uuid`
   createDateTime    DateTime `@default`(now()) `@db.Timestamptz`(6)
   updatedAt         DateTime `@updatedAt`
   statusListUri     String
+
+  organisation      organisation `@relation`(fields: [orgId], references: [id])
+
+  @@index([orgId, issuanceSessionId])
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/prisma-service/prisma/schema.prisma` around lines 653 - 662, Add an
org-scoped identifier to the issued_oid4vc_credentials model and make
queries/indexing org-aware: add a new field orgId (String, `@db.Uuid`) to the
issued_oid4vc_credentials model and create a composite index on (orgId,
issuanceSessionId) so revocation queries can filter by org efficiently; update
the revokeCredential implementation (the revokeCredential function and the
/orgs/:orgId/.../revoke endpoint handler) to require and validate the orgId
parameter and include orgId in the database WHERE clause when looking up
allocations/credentials.
apps/oid4vc-issuance/src/oid4vc-issuance.service.ts (1)

774-790: ⚠️ Potential issue | 🟠 Major

Don't silently downgrade revocable D2A offers.

This branch catches allocation errors and only logs a warning, allowing the offer to proceed without statusListDetails. A caller requesting isRevocable: true will receive SD-JWT credentials that cannot be revoked, and subsequent /revoke calls will fail.

Either retry allocation or fail the entire request when any revocable credential can't reserve an index.

🔧 Proposed fix
             if ((cred as any).format === CredentialFormat.SdJwtVc && !cred.statusListDetails) {
-              try {
-                const allocation = await this.statusListAllocatorService.allocate(orgId, agentDetailsForAlloc.orgDid);
-                cred.statusListDetails = {
-                  listId: allocation.listId,
-                  index: allocation.index,
-                  listSize: Number(CommonConstants.DEFAULT_STATUS_LIST_SIZE)
-                };
-              } catch (allocError) {
-                this.logger.warn(`Could not allocate status list index: ${allocError.message}`);
-              }
+              const allocation = await this.statusListAllocatorService.allocate(orgId, agentDetailsForAlloc.orgDid);
+              cred.statusListDetails = {
+                listId: allocation.listId,
+                index: allocation.index,
+                listSize: Number(CommonConstants.DEFAULT_STATUS_LIST_SIZE)
+              };
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/oid4vc-issuance/src/oid4vc-issuance.service.ts` around lines 774 - 790,
The code currently catches allocation errors for SD-JWT credentials and only
logs a warning, allowing revocable offers to proceed without statusListDetails;
change this so allocation failures cause the request to fail (or be retried)
instead of silently downgrading revocability: in the loop over
oidcCredentialD2APayload.credentials, where you check (cred as any).format ===
CredentialFormat.SdJwtVc and call statusListAllocatorService.allocate(orgId,
agentDetailsForAlloc.orgDid), remove the silent catch or implement a short retry
and then throw an error (or return a failed response) if allocation still fails;
ensure cred.statusListDetails is only left unset when revocability was not
requested (check the isRevocable flag in the surrounding context), and replace
this.logger.warn(...) with throwing an Error (or rejecting the request) so
callers who requested isRevocable: true do not receive non-revocable
credentials.
🧹 Nitpick comments (4)
libs/prisma-service/prisma/schema.prisma (1)

653-662: Consider adding relation from listId to status_list_allocation.

The listId field references status_list_allocation.listId but lacks a Prisma relation. This prevents referential integrity checks and cascade behavior.

🔧 Proposed addition
 model issued_oid4vc_credentials {
   id                String   `@id` `@default`(uuid()) `@db.Uuid`
   credentialId      String   `@unique`
   listId            String   `@db.Uuid`
   index             Int
   issuanceSessionId String   `@db.VarChar`
   createDateTime    DateTime `@default`(now()) `@db.Timestamptz`(6)
   updatedAt         DateTime `@updatedAt`
   statusListUri     String
+
+  statusListAllocation status_list_allocation `@relation`(fields: [listId], references: [listId])
 }

And add the back-relation to status_list_allocation:

 model status_list_allocation {
   ...
   organisation    organisation `@relation`(fields: [orgId], references: [id])
+  issuedCredentials issued_oid4vc_credentials[]

   @@index([orgId, isActive])
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/prisma-service/prisma/schema.prisma` around lines 653 - 662, The
issued_oid4vc_credentials model has a listId String field that intends to
reference status_list_allocation.listId but lacks a Prisma relation; add a
proper relation by changing listId to a foreign key field with
`@relation`(references: [listId], fields: [listId]) (or similar) on the
issued_oid4vc_credentials model and add the complementary back-relation field
(e.g., issuedCredentials or issued_oid4vc_credentials) on the
status_list_allocation model so Prisma enforces referential integrity and
enables cascade behavior; locate the models by the names
issued_oid4vc_credentials and status_list_allocation to implement the relation
attributes and any desired onDelete/onUpdate behavior.
libs/prisma-service/prisma/migrations/20260323183000_index_allocation/migration.sql (1)

17-41: Consider adding foreign key from issued_oid4vc_credentials.listId to status_list_allocation.listId.

The migration creates a foreign key from status_list_allocation.orgId to organisation.id, but there's no FK constraint linking issued_oid4vc_credentials.listId to status_list_allocation.listId. Without this, deleting a status_list_allocation row will orphan issued_oid4vc_credentials records.

🔧 Proposed addition

Add after line 41:

-- AddForeignKey
ALTER TABLE "issued_oid4vc_credentials" ADD CONSTRAINT "issued_oid4vc_credentials_listId_fkey" FOREIGN KEY ("listId") REFERENCES "status_list_allocation"("listId") ON DELETE RESTRICT ON UPDATE CASCADE;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@libs/prisma-service/prisma/migrations/20260323183000_index_allocation/migration.sql`
around lines 17 - 41, The migration is missing a foreign key linking
issued_oid4vc_credentials.listId to status_list_allocation.listId; add an ALTER
TABLE statement to create constraint issued_oid4vc_credentials_listId_fkey that
references status_list_allocation("listId") with ON DELETE RESTRICT ON UPDATE
CASCADE so issued_oid4vc_credentials rows cannot be orphaned when a
status_list_allocation is deleted; place this ADD CONSTRAINT after the other
ALTER TABLE statements in the migration.
apps/oid4vc-issuance/libs/helpers/credential-sessions.builder.ts (1)

485-489: Consider conditionally including isRevocable in the envelope.

isRevocable is always assigned to baseEnvelope, even when undefined. While this works, it adds an unnecessary property to non-revocable offers.

♻️ Optional cleanup
   const baseEnvelope: BuiltCredentialOfferBase = {
     credentials: builtCredentials,
-    ...(finalPublicIssuerId ? { publicIssuerId: finalPublicIssuerId } : {}),
-    isRevocable: dto.isRevocable
+    ...(finalPublicIssuerId ? { publicIssuerId: finalPublicIssuerId } : {}),
+    ...(dto.isRevocable !== undefined ? { isRevocable: dto.isRevocable } : {})
   };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/oid4vc-issuance/libs/helpers/credential-sessions.builder.ts` around
lines 485 - 489, The baseEnvelope currently always includes isRevocable (even
when undefined); change the object construction in
credential-sessions.builder.ts so that isRevocable is only spread into
baseEnvelope when dto.isRevocable is not undefined (e.g., use a conditional
spread instead of always setting isRevocable). Locate the baseEnvelope
construction (BuiltCredentialOfferBase, baseEnvelope, builtCredentials,
finalPublicIssuerId, dto.isRevocable) and replace the unconditional isRevocable
assignment with a conditional spread like ...(dto.isRevocable !== undefined ? {
isRevocable: dto.isRevocable } : {}).
apps/oid4vc-issuance/src/status-list-allocator.service.ts (1)

8-11: Consider marking class members as readonly and using node: prefix.

Per static analysis:

  • bitmap and capacity are never reassigned; mark them readonly
  • Prefer node:crypto over crypto for Node.js built-in modules
♻️ Suggested changes
-import { randomInt, randomUUID } from 'crypto';
+import { randomInt, randomUUID } from 'node:crypto';

 export class RandomBitmapIndexAllocator {
-  private bitmap: Uint8Array;
-  private capacity: number;
+  private readonly bitmap: Uint8Array;
+  private readonly capacity: number;
   private allocatedCount: number;

Also applies to: 6-6

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/oid4vc-issuance/src/status-list-allocator.service.ts` around lines 8 -
11, Mark the class fields bitmap and capacity in RandomBitmapIndexAllocator as
readonly (since they are assigned only once) by adding the readonly modifier to
their declarations, and update any related type annotations if needed; also
change the built-in crypto import to use the node: prefix (replace import from
"crypto" with "node:crypto") so the module uses Node's recommended specifier.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/oid4vc-issuance/src/oid4vc-issuance.service.ts`:
- Around line 927-965: The revokeCredential method currently overwrites
lastResponse for each allocation and only returns the final result (symbols:
revokeCredential, allocations, lastResponse, natsCall, pattern, payload), which
swallows intermediate failures; change the logic to accumulate each natsCall
result into an array (e.g., responses) and return that array, and ensure
per-allocation errors are not silently swallowed by either (a) letting any
natsCall error throw immediately (wrap the call so it bubbles as an RpcException
with context including allocation.listId/index) or (b) capturing success/error
per allocation in the returned array (e.g., { allocation, result } or {
allocation, error }) so callers can see which allocations failed; keep the
existing outer try/catch to log and rethrow RpcException as before.

In
`@libs/prisma-service/prisma/migrations/20260323183000_index_allocation/migration.sql`:
- Around line 17-29: The migration creates the "issued_oid4vc_credentials" table
but lacks an index on issuanceSessionId which makes
getCredentialAllocations(issuanceSessionId) perform full table scans; add a
CREATE INDEX for issuanceSessionId (e.g.,
issued_oid4vc_credentials_issuanceSessionId_idx) immediately after the table
creation in the migration to improve query performance and ensure the index name
matches your naming convention and the column "issuanceSessionId" in the
"issued_oid4vc_credentials" table.
- Line 23: The migration defines the column "issuanceSessionId" as VARCHAR with
no size; update the SQL to declare a bounded length (e.g., change
"issuanceSessionId" VARCHAR NOT NULL to "issuanceSessionId" VARCHAR(255) NOT
NULL or another appropriate max length for your data) so the column has an
explicit size constraint; ensure the chosen length matches any corresponding
model/schema constraints used elsewhere.

---

Duplicate comments:
In `@apps/oid4vc-issuance/src/oid4vc-issuance.service.ts`:
- Around line 774-790: The code currently catches allocation errors for SD-JWT
credentials and only logs a warning, allowing revocable offers to proceed
without statusListDetails; change this so allocation failures cause the request
to fail (or be retried) instead of silently downgrading revocability: in the
loop over oidcCredentialD2APayload.credentials, where you check (cred as
any).format === CredentialFormat.SdJwtVc and call
statusListAllocatorService.allocate(orgId, agentDetailsForAlloc.orgDid), remove
the silent catch or implement a short retry and then throw an error (or return a
failed response) if allocation still fails; ensure cred.statusListDetails is
only left unset when revocability was not requested (check the isRevocable flag
in the surrounding context), and replace this.logger.warn(...) with throwing an
Error (or rejecting the request) so callers who requested isRevocable: true do
not receive non-revocable credentials.

In `@apps/oid4vc-issuance/src/status-list-allocator.service.ts`:
- Around line 44-58: The allocate() method uses an unbounded random retry loop
which degrades as the bitmap fills; change allocate() (and use of isSet, set,
allocatedCount, capacity) to first try a bounded number of random attempts
(e.g., maxRetries = max(10, Math.ceil(capacity * 0.01))) and if none succeed
fall back to a deterministic linear scan over the bitmap to find the first clear
index, set it, increment allocatedCount and return it; preserve the existing
full-capacity check and throw the same error if no index is free after the
fallback.

In `@libs/prisma-service/prisma/schema.prisma`:
- Line 235: The org_agents model contains a duplicate field declaration for
webhookSecret which breaks Prisma schema parsing; open the prisma schema where
the model org_agents is defined, locate both webhookSecret entries and remove
the redundant one (leave a single webhookSecret String? `@db.VarChar` definition),
then run Prisma validate/generate to ensure the schema compiles; reference the
org_agents model and the webhookSecret field when making the change.
- Around line 653-662: Add an org-scoped identifier to the
issued_oid4vc_credentials model and make queries/indexing org-aware: add a new
field orgId (String, `@db.Uuid`) to the issued_oid4vc_credentials model and create
a composite index on (orgId, issuanceSessionId) so revocation queries can filter
by org efficiently; update the revokeCredential implementation (the
revokeCredential function and the /orgs/:orgId/.../revoke endpoint handler) to
require and validate the orgId parameter and include orgId in the database WHERE
clause when looking up allocations/credentials.

---

Nitpick comments:
In `@apps/oid4vc-issuance/libs/helpers/credential-sessions.builder.ts`:
- Around line 485-489: The baseEnvelope currently always includes isRevocable
(even when undefined); change the object construction in
credential-sessions.builder.ts so that isRevocable is only spread into
baseEnvelope when dto.isRevocable is not undefined (e.g., use a conditional
spread instead of always setting isRevocable). Locate the baseEnvelope
construction (BuiltCredentialOfferBase, baseEnvelope, builtCredentials,
finalPublicIssuerId, dto.isRevocable) and replace the unconditional isRevocable
assignment with a conditional spread like ...(dto.isRevocable !== undefined ? {
isRevocable: dto.isRevocable } : {}).

In `@apps/oid4vc-issuance/src/status-list-allocator.service.ts`:
- Around line 8-11: Mark the class fields bitmap and capacity in
RandomBitmapIndexAllocator as readonly (since they are assigned only once) by
adding the readonly modifier to their declarations, and update any related type
annotations if needed; also change the built-in crypto import to use the node:
prefix (replace import from "crypto" with "node:crypto") so the module uses
Node's recommended specifier.

In
`@libs/prisma-service/prisma/migrations/20260323183000_index_allocation/migration.sql`:
- Around line 17-41: The migration is missing a foreign key linking
issued_oid4vc_credentials.listId to status_list_allocation.listId; add an ALTER
TABLE statement to create constraint issued_oid4vc_credentials_listId_fkey that
references status_list_allocation("listId") with ON DELETE RESTRICT ON UPDATE
CASCADE so issued_oid4vc_credentials rows cannot be orphaned when a
status_list_allocation is deleted; place this ADD CONSTRAINT after the other
ALTER TABLE statements in the migration.

In `@libs/prisma-service/prisma/schema.prisma`:
- Around line 653-662: The issued_oid4vc_credentials model has a listId String
field that intends to reference status_list_allocation.listId but lacks a Prisma
relation; add a proper relation by changing listId to a foreign key field with
`@relation`(references: [listId], fields: [listId]) (or similar) on the
issued_oid4vc_credentials model and add the complementary back-relation field
(e.g., issuedCredentials or issued_oid4vc_credentials) on the
status_list_allocation model so Prisma enforces referential integrity and
enables cascade behavior; locate the models by the names
issued_oid4vc_credentials and status_list_allocation to implement the relation
attributes and any desired onDelete/onUpdate behavior.
🪄 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: 9c58ec6d-7475-4ceb-b132-cb8b4c0fc488

📥 Commits

Reviewing files that changed from the base of the PR and between 380e0cc and fe14467.

📒 Files selected for processing (17)
  • .env.demo
  • .env.sample
  • apps/agent-service/src/agent-service.controller.ts
  • apps/agent-service/src/agent-service.service.ts
  • apps/api-gateway/src/oid4vc-issuance/dtos/issuer-sessions.dto.ts
  • apps/api-gateway/src/oid4vc-issuance/oid4vc-issuance.controller.ts
  • apps/api-gateway/src/oid4vc-issuance/oid4vc-issuance.service.ts
  • apps/oid4vc-issuance/interfaces/oid4vc-issuer-sessions.interfaces.ts
  • apps/oid4vc-issuance/libs/helpers/credential-sessions.builder.ts
  • apps/oid4vc-issuance/src/main.ts
  • apps/oid4vc-issuance/src/oid4vc-issuance.controller.ts
  • apps/oid4vc-issuance/src/oid4vc-issuance.module.ts
  • apps/oid4vc-issuance/src/oid4vc-issuance.service.ts
  • apps/oid4vc-issuance/src/status-list-allocator.service.ts
  • libs/common/src/common.constant.ts
  • libs/prisma-service/prisma/migrations/20260323183000_index_allocation/migration.sql
  • libs/prisma-service/prisma/schema.prisma
✅ Files skipped from review due to trivial changes (5)
  • libs/common/src/common.constant.ts
  • .env.demo
  • apps/oid4vc-issuance/interfaces/oid4vc-issuer-sessions.interfaces.ts
  • apps/oid4vc-issuance/src/oid4vc-issuance.module.ts
  • .env.sample
🚧 Files skipped from review as they are similar to previous changes (6)
  • apps/api-gateway/src/oid4vc-issuance/oid4vc-issuance.service.ts
  • apps/agent-service/src/agent-service.controller.ts
  • apps/agent-service/src/agent-service.service.ts
  • apps/oid4vc-issuance/src/oid4vc-issuance.controller.ts
  • apps/api-gateway/src/oid4vc-issuance/dtos/issuer-sessions.dto.ts
  • apps/api-gateway/src/oid4vc-issuance/oid4vc-issuance.controller.ts

Comment thread apps/oid4vc-issuance/src/oid4vc-issuance.service.ts
Signed-off-by: Tipu_Singh <tipu.singh@ayanworks.com>
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: 2

🧹 Nitpick comments (2)
apps/oid4vc-issuance/libs/helpers/credential-sessions.builder.ts (2)

250-254: Remove unnecessary non-null assertions.

SonarCloud correctly flags attr.children! as redundant—the guard at line 241 already confirms attr.children exists and has length > 0.

♻️ Proposed fix
       if (Array.isArray(payloadValue)) {
-        const childFrame = buildDisclosureFrameFromTemplate(attr.children!);
+        const childFrame = buildDisclosureFrameFromTemplate(attr.children);
         frame[attr.key] = payloadValue.map(() => ({ ...childFrame }));
       } else {
-        const childFrame = buildDisclosureFrameFromTemplate(attr.children!, payloadValue as Record<string, any>);
+        const childFrame = buildDisclosureFrameFromTemplate(attr.children, payloadValue as Record<string, any>);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/oid4vc-issuance/libs/helpers/credential-sessions.builder.ts` around
lines 250 - 254, The non-null assertions on attr.children are redundant because
the surrounding guard already ensures attr.children exists and is non-empty;
remove the unnecessary "!" usages and pass attr.children directly to
buildDisclosureFrameFromTemplate in both branches (the array-mapped branch and
the plain-object branch) so calls become
buildDisclosureFrameFromTemplate(attr.children) and
buildDisclosureFrameFromTemplate(attr.children, payloadValue as Record<string,
any>), keeping behavior unchanged.

272-272: Remove commented-out debug code.

♻️ Proposed fix
-  // console.log('Built disclosure frame:', JSON.stringify(frame, null, 2));
   return frame;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/oid4vc-issuance/libs/helpers/credential-sessions.builder.ts` at line
272, Remove the commented-out debug statement "// console.log('Built disclosure
frame:', JSON.stringify(frame, null, 2));"—delete the line entirely; if you need
retained debug output, replace it with a proper logger call (e.g., logger.debug)
in the function that builds the disclosure frame and reference the variable
"frame" there instead.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/oid4vc-issuance/libs/helpers/credential-sessions.builder.ts`:
- Around line 245-247: The TODO indicates missing type validation for payload
values: locate the block referencing payloadValue and the template definition
(search for symbols payloadValue and templateDefinition or the function that
builds credential sessions, e.g.,
CredentialSessionsBuilder/buildCredentialSession) and add runtime checks that
enforce payloadValue is either an object or an array of objects, then validate
structure according to the template (if templateDefinition.type === 'array'
require an array of objects; if 'object' require a single object). On validation
failure throw/return a descriptive error (or ValidationError) and add unit tests
covering object vs array cases and template mismatches; keep error messages tied
to the same function names (payloadValue/templateDefinition) so logs/tests can
find the source.
- Around line 248-251: The array branch in buildDisclosureFrameFromTemplate
fails to pass each element's payload into the recursive call and uses a shallow
spread for copies; update the Array.isArray(payloadValue) case so for each
element you call buildDisclosureFrameFromTemplate(attr.children!,
payloadElement) (or equivalent overload) to propagate the element's payload, and
create deep independent copies of the returned child frame (e.g., by
constructing each entry via a fresh recursive call or using a deterministic
deep-clone of the child frame) before assigning frame[attr.key] =
payloadValue.map(...), ensuring nested _sd arrays and objects are not shared
across array items.

---

Nitpick comments:
In `@apps/oid4vc-issuance/libs/helpers/credential-sessions.builder.ts`:
- Around line 250-254: The non-null assertions on attr.children are redundant
because the surrounding guard already ensures attr.children exists and is
non-empty; remove the unnecessary "!" usages and pass attr.children directly to
buildDisclosureFrameFromTemplate in both branches (the array-mapped branch and
the plain-object branch) so calls become
buildDisclosureFrameFromTemplate(attr.children) and
buildDisclosureFrameFromTemplate(attr.children, payloadValue as Record<string,
any>), keeping behavior unchanged.
- Line 272: Remove the commented-out debug statement "// console.log('Built
disclosure frame:', JSON.stringify(frame, null, 2));"—delete the line entirely;
if you need retained debug output, replace it with a proper logger call (e.g.,
logger.debug) in the function that builds the disclosure frame and reference the
variable "frame" there instead.
🪄 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: 0e8b9e68-e6ce-4d59-a512-f82155158cc4

📥 Commits

Reviewing files that changed from the base of the PR and between 379fc76 and 7ba2bf7.

📒 Files selected for processing (1)
  • apps/oid4vc-issuance/libs/helpers/credential-sessions.builder.ts

Comment on lines +245 to +247
//todo:
//1) Need to handle the type validation here to ensure payloadValue is in expected format (object or array of objects)
//2) Need to add add validation based on template definition (e.g. if template defines an array, payload must be an array, etc.)
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

Address the TODO comment before merging.

The comment acknowledges missing type validation for payload values. Consider creating an issue to track this work if it cannot be completed in this PR.

Do you want me to open a new issue to track this validation task?

🧰 Tools
🪛 GitHub Check: SonarCloud Code Analysis

[warning] 245-245: Complete the task associated to this "TODO" comment.

See more on https://sonarcloud.io/project/issues?id=credebl_platform&issues=AZ1hMR7tlZMhgCvZPboE&open=AZ1hMR7tlZMhgCvZPboE&pullRequest=1594

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/oid4vc-issuance/libs/helpers/credential-sessions.builder.ts` around
lines 245 - 247, The TODO indicates missing type validation for payload values:
locate the block referencing payloadValue and the template definition (search
for symbols payloadValue and templateDefinition or the function that builds
credential sessions, e.g., CredentialSessionsBuilder/buildCredentialSession) and
add runtime checks that enforce payloadValue is either an object or an array of
objects, then validate structure according to the template (if
templateDefinition.type === 'array' require an array of objects; if 'object'
require a single object). On validation failure throw/return a descriptive error
(or ValidationError) and add unit tests covering object vs array cases and
template mismatches; keep error messages tied to the same function names
(payloadValue/templateDefinition) so logs/tests can find the source.

Comment on lines +248 to +251
if (Array.isArray(payloadValue)) {
// Array of objects → [{ _sd: [...] }, ...]
const childFrame = buildDisclosureFrameFromTemplate(attr.children!);
frame[attr.key] = payloadValue.map(() => ({ ...childFrame }));
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

Array handling has two issues: inconsistent payload propagation and shallow copy.

  1. Missing payload propagation: Unlike the object branch (line 254), the array branch doesn't pass individual payload elements to the recursive call. This means nested children within array elements won't get payload-aware processing (e.g., detecting nested arrays).

  2. Shallow copy hazard: { ...childFrame } creates shallow copies that share references to nested objects like _sd arrays. If any frame element is mutated, all copies are affected.

🔧 Proposed fix
       if (Array.isArray(payloadValue)) {
-        // Array of objects → [{ _sd: [...] }, ...]
-        const childFrame = buildDisclosureFrameFromTemplate(attr.children!);
-        frame[attr.key] = payloadValue.map(() => ({ ...childFrame }));
+        // Array of objects → [{ _sd: [...] }, ...] — build per-element frame
+        frame[attr.key] = payloadValue.map((element) =>
+          buildDisclosureFrameFromTemplate(attr.children, element as Record<string, any>)
+        );
       } else {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (Array.isArray(payloadValue)) {
// Array of objects → [{ _sd: [...] }, ...]
const childFrame = buildDisclosureFrameFromTemplate(attr.children!);
frame[attr.key] = payloadValue.map(() => ({ ...childFrame }));
if (Array.isArray(payloadValue)) {
// Array of objects → [{ _sd: [...] }, ...] — build per-element frame
frame[attr.key] = payloadValue.map((element) =>
buildDisclosureFrameFromTemplate(attr.children, element as Record<string, any>)
);
🧰 Tools
🪛 GitHub Check: SonarCloud Code Analysis

[warning] 250-250: This assertion is unnecessary since it does not change the type of the expression.

See more on https://sonarcloud.io/project/issues?id=credebl_platform&issues=AZ1hMR7tlZMhgCvZPboF&open=AZ1hMR7tlZMhgCvZPboF&pullRequest=1594

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/oid4vc-issuance/libs/helpers/credential-sessions.builder.ts` around
lines 248 - 251, The array branch in buildDisclosureFrameFromTemplate fails to
pass each element's payload into the recursive call and uses a shallow spread
for copies; update the Array.isArray(payloadValue) case so for each element you
call buildDisclosureFrameFromTemplate(attr.children!, payloadElement) (or
equivalent overload) to propagate the element's payload, and create deep
independent copies of the returned child frame (e.g., by constructing each entry
via a fresh recursive call or using a deterministic deep-clone of the child
frame) before assigning frame[attr.key] = payloadValue.map(...), ensuring nested
_sd arrays and objects are not shared across array items.

Signed-off-by: Sagar Khole <sagar.khole@ayanworks.com>
…ebl/platform into feat/sd-jwt-revocation-flow

Signed-off-by: Sagar Khole <sagar.khole@ayanworks.com>
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: 6

♻️ Duplicate comments (1)
apps/oid4vc-issuance/src/oid4vc-issuance.service.ts (1)

673-727: ⚠️ Potential issue | 🔴 Critical

Remove the duplicate persistence block before this can compile.

Lines 674/701 redeclare parsedResponse, Lines 681/708 redeclare issuanceSessionId, and the first saveCredentialAllocation() call omits the new orgId argument. Keep one block with orgId, and fail revocable offers when the agent response has no session id so allocated indexes are not left without lookup rows.

🐛 Proposed fix
-      // Revocation logic to save the credential offer id and status list details in DB for later use during revocation
-      let parsedResponse;
-      if ('string' === typeof createCredentialOfferOnAgent.response) {
-        parsedResponse = JSON.parse(createCredentialOfferOnAgent.response);
-      } else {
-        parsedResponse = createCredentialOfferOnAgent.response;
-      }
-
-      const issuanceSessionId =
-        parsedResponse.issuanceSessionId ||
-        parsedResponse.credentialOfferId ||
-        parsedResponse.id ||
-        parsedResponse.issuanceSession?.id;
-      if (issuanceSessionId && createOidcCredentialOffer.isRevocable) {
-        for (const cred of buildOidcCredentialOffer.credentials) {
-          if (cred.statusListDetails) {
-            const statusListUri = `${process.env.STATUS_LIST_HOST}/status-lists/${cred.statusListDetails.listId}`;
-            await this.statusListAllocatorService.saveCredentialAllocation(
-              `${issuanceSessionId}-${cred.statusListDetails.index}`,
-              cred.statusListDetails.listId,
-              cred.statusListDetails.index,
-              issuanceSessionId,
-              statusListUri
-            );
-          }
-        }
-      }
-
       let parsedResponse;
       if ('string' === typeof createCredentialOfferOnAgent.response) {
         parsedResponse = JSON.parse(createCredentialOfferOnAgent.response);
@@
-      if (issuanceSessionId && createOidcCredentialOffer.isRevocable) {
+      if (createOidcCredentialOffer.isRevocable) {
+        if (!issuanceSessionId) {
+          throw new InternalServerErrorException('Issuance session ID missing from agent response');
+        }
+
         for (const cred of buildOidcCredentialOffer.credentials) {
           if (cred.statusListDetails) {
             const statusListUri = `${process.env.STATUS_LIST_HOST}/status-lists/${cred.statusListDetails.listId}`;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/oid4vc-issuance/src/oid4vc-issuance.service.ts` around lines 673 - 727,
Duplicate persistence logic for parsedResponse/issuanceSessionId must be
removed; keep a single block that parses createCredentialOfferOnAgent.response
into parsedResponse, computes issuanceSessionId (from
parsedResponse.issuanceSessionId || parsedResponse.credentialOfferId ||
parsedResponse.id || parsedResponse.issuanceSession?.id), and only proceeds if
issuanceSessionId and createOidcCredentialOffer.isRevocable; call
statusListAllocatorService.saveCredentialAllocation with the orgId argument (use
signature saveCredentialAllocation(orgId, allocationId, listId, index,
issuanceSessionId, statusListUri)); if the offer is revocable but
issuanceSessionId is missing, throw an error (or otherwise fail) so you do not
allocate status list indexes without a lookup row; reference
buildOidcCredentialOffer.credentials and cred.statusListDetails when building
statusListUri and allocationId.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.env.demo:
- Around line 248-257: The .env.demo contains a duplicated environment variable
STATUS_LIST_HOST which can silently override the intended value; remove the
duplicate declaration so only one STATUS_LIST_HOST exists (keep the intended one
and delete the other) and ensure the remaining STATUS_LIST_HOST is set or
documented appropriately so startup has a single canonical value.

In @.env.sample:
- Around line 276-285: Duplicate declaration of STATUS_LIST_HOST exists; remove
the redundant blank entry so only a single STATUS_LIST_HOST key remains. Locate
the two occurrences of STATUS_LIST_HOST in the snippet, delete the second/extra
line (the one near the end), and ensure the remaining single STATUS_LIST_HOST is
the one used for local configuration so apps/oid4vc-issuance/src/main.ts can
correctly detect its presence.

In `@apps/agent-service/src/agent-service.service.ts`:
- Around line 1495-1501: The current catch in _oidcRevokeCredential logs
JSON.stringify(error) which can leak headers including the decrypted agent API
key; change the logging to a sanitized message that does not serialize the full
error or include request config/headers. Specifically, in
agent-service.service.ts inside _oidcRevokeCredential replace
JSON.stringify(error) with a concise log that includes error.message (and
optionally error.response?.status or a short, redacted error.response?.data) but
never error.config or headers; ensure you do not log getApiKey or any
Authorization header and throw the original error after logging.

In `@apps/oid4vc-issuance/src/oid4vc-issuance.service.ts`:
- Around line 795-864: The catch block currently releases any
cred.statusListDetails regardless of who allocated it; change the allocation
loop (inside the isRevocable && hasSdJwt branch where
statusListAllocatorService.allocate is called and cred.statusListDetails is set)
to record allocated entries into a local array (e.g., allocatedStatusEntries as
objects containing listId and index and possibly cred reference or a symbol);
then in the catch block, iterate only over that allocatedStatusEntries array and
call statusListAllocatorService.release for those entries, leaving pre-existing
cred.statusListDetails untouched; update references to
statusListAllocatorService.allocate, cred.statusListDetails, and the catch
release loop to use the new allocatedStatusEntries collection.

In `@apps/oid4vc-issuance/src/status-list-allocator.service.ts`:
- Around line 111-151: The current allocation can race when two transactions
read the same active bitmap and both allocate the same index; fix by serializing
updates with a DB-level uniqueness guard plus a retry loop using optimistic
conditional update or row-level locking: add a migration creating a partial
unique index on status_list_allocation(orgId, issuerDid) WHERE isActive = true
to enforce one active list; inside the transaction, when selecting the active
allocation use a SELECT FOR UPDATE (via tx.$queryRaw) or re-read the row before
updating, allocate with RandomBitmapIndexAllocator on the in-transaction bitmap,
then attempt an UPDATE that includes a conditional WHERE matching the previous
bitmap/allocatedCount (or use the FOR UPDATE-locked row) and retry the
allocate/update sequence on conflict up to N times; update references to
status_list_allocation.update, RandomBitmapIndexAllocator.allocate,
allocator.export, and the bitmap/allocatedCount fields accordingly.
- Around line 204-212: The code always decrements allocatedCount after calling
RandomBitmapIndexAllocator.release(), but release() is a no-op for an
already-free index so allocatedCount can go out of sync; change the logic to
only decrement allocatedCount when a bit was actually cleared — either by using
a boolean return value from RandomBitmapIndexAllocator.release(index) if
available, or by checking the bit state before releasing (e.g., call
allocator.isSet(index) or allocator.get(index) prior to
allocator.release(index)), and only update status_list_allocation.allocatedCount
= Math.max(0, allocation.allocatedCount - 1) when that check indicates the bit
was previously set; still persist bitmap with Buffer.from(allocator.export()) as
before.

---

Duplicate comments:
In `@apps/oid4vc-issuance/src/oid4vc-issuance.service.ts`:
- Around line 673-727: Duplicate persistence logic for
parsedResponse/issuanceSessionId must be removed; keep a single block that
parses createCredentialOfferOnAgent.response into parsedResponse, computes
issuanceSessionId (from parsedResponse.issuanceSessionId ||
parsedResponse.credentialOfferId || parsedResponse.id ||
parsedResponse.issuanceSession?.id), and only proceeds if issuanceSessionId and
createOidcCredentialOffer.isRevocable; call
statusListAllocatorService.saveCredentialAllocation with the orgId argument (use
signature saveCredentialAllocation(orgId, allocationId, listId, index,
issuanceSessionId, statusListUri)); if the offer is revocable but
issuanceSessionId is missing, throw an error (or otherwise fail) so you do not
allocate status list indexes without a lookup row; reference
buildOidcCredentialOffer.credentials and cred.statusListDetails when building
statusListUri and allocationId.
🪄 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: 139e7d88-65f5-4a90-8760-ced955f8b2c7

📥 Commits

Reviewing files that changed from the base of the PR and between 7ba2bf7 and c6258ac.

📒 Files selected for processing (9)
  • .env.demo
  • .env.sample
  • apps/agent-service/src/agent-service.controller.ts
  • apps/agent-service/src/agent-service.service.ts
  • apps/api-gateway/src/oid4vc-issuance/dtos/issuer-sessions.dto.ts
  • apps/oid4vc-issuance/src/oid4vc-issuance.service.ts
  • apps/oid4vc-issuance/src/status-list-allocator.service.ts
  • libs/prisma-service/prisma/migrations/20260323183000_index_allocation/migration.sql
  • libs/prisma-service/prisma/schema.prisma
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/api-gateway/src/oid4vc-issuance/dtos/issuer-sessions.dto.ts

Comment thread .env.demo Outdated
Comment thread .env.sample Outdated
Comment thread apps/agent-service/src/agent-service.service.ts
Comment thread apps/oid4vc-issuance/src/oid4vc-issuance.service.ts
Comment thread apps/oid4vc-issuance/src/status-list-allocator.service.ts
Comment thread apps/oid4vc-issuance/src/status-list-allocator.service.ts
Signed-off-by: Sagar Khole <sagar.khole@ayanworks.com>
… counters

Signed-off-by: Sagar Khole <sagar.khole@ayanworks.com>
…ebl/platform into feat/sd-jwt-revocation-flow

Signed-off-by: Sagar Khole <sagar.khole@ayanworks.com>
@sonarqubecloud
Copy link
Copy Markdown

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