Skip to content

feat(openid4vc): refactor agent to use shared public listener - #577

Draft
rmlearney-digicatapult wants to merge 5 commits into
feat/openid4vcfrom
feat/openid4vc-shared-listener
Draft

feat(openid4vc): refactor agent to use shared public listener#577
rmlearney-digicatapult wants to merge 5 commits into
feat/openid4vcfrom
feat/openid4vc-shared-listener

Conversation

@rmlearney-digicatapult

@rmlearney-digicatapult rmlearney-digicatapult commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Pull Request

Checklist

  • Have you read Digital Catapult's Code of Conduct?
  • I have performed a self-review of my own code.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have made corresponding changes to the documentation.
  • My changes generate no new warnings.
  • I have added tests that prove my fix is effective or that my feature works.
  • New and existing unit tests pass locally with my changes.

PR Type

Please delete options that are irrelevant.

  • Feature

Linked tickets

https://digicatapult.atlassian.net/browse/ENG-346

High level description

Refactor agent startup code to present public (DIDComm HTTP & WS) and private endpoints (TSOA & WS events)

Detailed description

PR for feat/openid4vc feature branch

  • Small initial set of OpenID4VC envars defaulting to false
  • Agent startup refactored to create a public/private listener split
  • DIDComm endpoints attached to /didcomm and /didcomm-ws
  • Listener middleware wired before app startup to reject calls with 503 until Cloudagent listener is ready
  • Fixtures and generators modified to use new endpoints
  • New tests for shared agent listener

Describe alternatives you've considered

Operational impact

Additional context

@rmlearney-digicatapult
rmlearney-digicatapult requested a review from a team as a code owner August 20, 2026 13:45
@rmlearney-digicatapult
rmlearney-digicatapult requested a balanced review from Copilot August 20, 2026 13:45
Comment thread README.md Dismissed
Comment thread docker-compose-testnet.yml Dismissed
Comment thread docker-compose-testnet.yml Dismissed
Comment thread docker-compose-testnet.yml Dismissed
Comment thread docker-compose.yml Dismissed
@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 88.43%
🟰 ±0%
8925 / 10092
🔵 Statements 88.43%
🟰 ±0%
8925 / 10092
🔵 Functions 81.95%
🟰 ±0%
277 / 338
🔵 Branches 68.81%
⬆️ +0.91%
651 / 946
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
src/agent.ts 93.86%
⬆️ +0.46%
90.47%
⬆️ +10.47%
100%
🟰 ±0%
93.86%
⬆️ +0.46%
185-202, 263-264
src/didweb/server.ts 56.92%
⬇️ -4.48%
100%
🟰 ±0%
50%
⬇️ -12.50%
56.92%
⬇️ -4.48%
45-46, 65-69, 77-85, 88-102, 105-129
src/utils/didcommWebSocket.ts 100% 90.9% 100% 100%
src/utils/readiness.ts 100% 100% 100% 100%
Unchanged Files
src/error.ts 93.22% 91.66% 100% 93.22% 86-90, 95-97
src/ioc.ts 100% 100% 100% 100%
src/server.ts 95.06% 66.66% 100% 95.06% 67-68, 73-74
src/anoncreds/index.ts 94.51% 100% 60% 94.51% 158-161, 164-165, 169-173, 176-177
src/controllers/HealthController.ts 100% 66.66% 100% 100%
src/controllers/examples.ts 100% 100% 100% 100%
src/controllers/v1/agent/AgentController.ts 100% 60% 100% 100%
src/controllers/v1/basic-messages/BasicMessageController.ts 96.66% 75% 100% 96.66% 57-58
src/controllers/v1/connections/ConnectionController.ts 93.63% 67.5% 100% 93.63% 83-84, 98-106, 194-195, 221-222, 264-265
src/controllers/v1/credentials/CredentialController.ts 92.03% 58.18% 100% 92.03% 107-108, 159-160, 188-191, 224-225, 273-276, 284-290, 324-325, 359-360, 388-389, 419-425
src/controllers/v1/credentials/CredentialDefinitionController.ts 86.3% 63.15% 100% 86.3% 88-92, 127-131, 133-137, 152-159
src/controllers/v1/credentials/SchemaController.ts 88.23% 66.66% 100% 88.23% 85-89, 130-136, 139-144
src/controllers/v1/did/DidController.ts 95% 72.72% 100% 95% 62-66, 115-116
src/controllers/v1/drpc/DrpcController.ts 98.29% 75% 100% 98.29% 78-79
src/controllers/v1/outofband/OutOfBandController.ts 94.44% 59.37% 100% 94.44% 54-58, 209-215, 352-353, 370-376
src/controllers/v1/proofs/ProofController.ts 93.68% 66.66% 100% 93.68% 118-119, 151-152, 182-183, 203-204, 222-224, 237-238, 273-274, 299-301, 312-316, 335-337, 387-388, 434-440, 468-470, 535-536
src/controllers/v1/verified-drpc/VerifiedDrpcController.ts 42.45% 0% 0% 42.45% 28-30, 40-87, 96-105
src/controllers/v1/wallet/WalletController.ts 73.02% 65.51% 87.5% 73.02% 30-34, 39-43, 56-73, 79-93, 142-143, 150-154, 183-186, 189-192
src/didweb/error.ts 38.57% 0% 0% 38.57% 5-6, 9-22, 27-28, 33-34, 44-47, 50-59, 62-70
src/drpc-handler/index.ts 79.31% 81.25% 66.66% 79.31% 13-14, 45-52, 70-71, 75-77, 80-82
src/events/BasicMessageEvents.ts 100% 100% 100% 100%
src/events/ConnectionEvents.ts 100% 100% 100% 100%
src/events/CredentialEvents.ts 100% 100% 100% 100%
src/events/DrpcEvents.ts 43.58% 100% 50% 43.58% 15-36
src/events/ProofEvents.ts 100% 100% 100% 100%
src/events/TrustPingEvents.ts 43.24% 100% 50% 43.24% 14-34
src/events/VerifiedDrpcEvents.ts 43.58% 100% 50% 43.58% 15-36
src/events/WebSocketEvents.ts 100% 66.66% 100% 100%
src/events/WebhookEvent.ts 73.33% 50% 100% 73.33% 11-14
src/ipfs/index.ts 96.85% 95.45% 100% 96.85% 82-85
src/ipfs/responseParser.ts 100% 100% 100% 100%
src/modules/verified-drpc/VerifiedDrpcApi.ts 44.71% 100% 40% 44.71% 61-81, 90-114, 131-144, 155-199, 208-227, 230-240
src/modules/verified-drpc/VerifiedDrpcModule.ts 100% 100% 100% 100%
src/modules/verified-drpc/VerifiedDrpcModuleConfig.ts 100% 100% 100% 100%
src/modules/verified-drpc/VerifiedDrpcRequestEvents.ts 100% 100% 100% 100%
src/modules/verified-drpc/VerifiedDrpcResponseEvents.ts 100% 100% 100% 100%
src/modules/verified-drpc/index.ts 100% 100% 100% 100%
src/modules/verified-drpc/handlers/VerifiedDrpcRequestHandler.ts 86.36% 100% 66.66% 86.36% 19-21
src/modules/verified-drpc/handlers/VerifiedDrpcResponseHandler.ts 86.36% 100% 66.66% 86.36% 19-21
src/modules/verified-drpc/handlers/index.ts 100% 100% 100% 100%
src/modules/verified-drpc/messages/VerifiedDrpcRequestMessage.ts 100% 100% 100% 100%
src/modules/verified-drpc/messages/VerifiedDrpcResponseMessage.ts 74.41% 50% 33.33% 74.41% 27-33, 39-42
src/modules/verified-drpc/messages/index.ts 100% 100% 100% 100%
src/modules/verified-drpc/models/ValidRequest.ts 93.18% 93.75% 100% 93.18% 17-19
src/modules/verified-drpc/models/ValidResponse.ts 38.8% 100% 25% 38.8% 12-25, 39-62, 65-67
src/modules/verified-drpc/models/VerifiedDrpcErrorCodes.ts 100% 100% 100% 100%
src/modules/verified-drpc/models/VerifiedDrpcRole.ts 100% 100% 100% 100%
src/modules/verified-drpc/models/VerifiedDrpcState.ts 100% 100% 100% 100%
src/modules/verified-drpc/models/index.ts 100% 100% 100% 100%
src/modules/verified-drpc/repository/VerifiedDrpcRecord.ts 72.5% 100% 50% 72.5% 54-59, 62-65, 68-79
src/modules/verified-drpc/repository/VerifiedDrpcRepository.ts 100% 100% 100% 100%
src/modules/verified-drpc/repository/index.ts 100% 100% 100% 100%
src/modules/verified-drpc/services/VerifiedDrpcService.ts 74.36% 83.87% 65% 74.36% 146-160, 169-175, 179-180, 184-201, 204-222, 233-234, 260-261, 270-273, 275-279, 305-306, 309-310, 313-315
src/modules/verified-drpc/services/index.ts 100% 100% 100% 100%
src/routes/routes.ts 94.8% 42.93% 95.08% 94.8% 1334-1335, 1347-1372, 1386-1411, 1424-1449, 1483-1484, 1520-1521, 1557-1558, 1593-1594, 1629-1630, 1665-1666, 1775-1776, 1884-1885, 1919-1920, 1955-1956, 1991-1992, 2027-2028, 2063-2064, 2135-2136, 2171-2172, 2208-2209, 2244-2245, 2282-2283, 2319-2320, 2356-2357, 2392-2393, 2428-2429, 2464-2465, 2491-2492, 2503-2504, 2527-2528, 2539-2540, 2563-2564, 2613-2614, 2649-2650, 2722-2723, 2758-2759, 2794-2795, 2830-2831, 2866-2867, 2903-2904, 2939-2940, 2975-2976, 3012-3013, 3049-3050, 3085-3086, 3122-3123, 3160-3161, 3201-3202, 3237-3238, 3274-3275, 3310-3311, 3346-3347, 3417-3418, 3454-3455, 3489-3490
src/utils/credentials.ts 77.08% 63.41% 100% 77.08% 11, 28-29, 34-35, 42-43, 46-47, 67-69, 74-76, 82-84, 94-101, 105-107, 113-114, 126-127
src/utils/didWebGenerator.ts 71% 60% 60% 71% 27-28, 38-39, 119-158, 164-168
src/utils/helpers.ts 84.21% 100% 50% 84.21% 17-19
src/utils/logger.ts 89.91% 100% 68.75% 89.91% 57-59, 68-69, 72-73, 92-93, 105-107
src/utils/proofs.ts 84.03% 73.46% 93.75% 84.03% 43-45, 48-50, 57-63, 65-66, 71-72, 76-78, 84-102, 109-110, 123-124, 149-150, 165-167, 197-202, 250-251, 254-255, 292-295, 303-306, 332-333
src/utils/version.ts 100% 100% 100% 100%
src/utils/webhook.ts 95.74% 88.88% 100% 95.74% 21-22
Generated in workflow #3343 for commit 4b9ec34 by the Vitest Coverage Report Action

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Refactors DIDComm HTTP and WebSocket traffic onto a shared public listener while retaining the private TSOA/event listener. Changes are required because startup readiness does not cover WebSocket upgrades or actual listener binding.

Changes:

  • Moves DIDComm to /didcomm and /didcomm-ws on port 5002.
  • Adds public/private listener separation and startup gating.
  • Updates fixtures, deployment configuration, documentation, and tests.

Blockers (Must-Fix)

  • src/index.ts:97: WebSocket upgrades bypass the readiness gate.
  • src/index.ts:176: Readiness is announced before listeners successfully bind.
  • src/agent.ts:266: Configured WebSocket ports are silently ignored.

Targeted Suggestions

  • Check readiness inside the HTTP upgrade handler.
  • Await listener listening events and reject startup on error.
  • Validate or remove the obsolete WebSocket port configuration.

Test Gap Analysis

  • Add a test rejecting /didcomm-ws before readiness.
  • Make the unknown-path test fail if the socket opens.
  • Cover mismatched HTTP/WS port configuration.

Integration Risks

  • Existing deployments using port 5003 will lose WebSocket connectivity.
  • README and Compose configuration still expose or document obsolete WS ports.
  • Rollback requires restoring separate HTTP and WS transport listeners.

Scores

Category Score Reason
Correctness 6/10 Readiness has HTTP/WS gaps.
Security 7/10 WS traffic bypasses startup gating.
Testing 6/10 One WS rejection test is ineffective.
Maintainability 7/10 Transport configuration is misleading.
Performance 9/10 No material regression identified.
Integration/Infra 5/10 Port documentation and exposure remain inconsistent.
Overall 6.5/10 Core approach is sound but requires readiness and configuration fixes.

Release Notes Draft

  • Added a shared public protocol listener.
  • Moved DIDComm HTTP to /didcomm.
  • Moved DIDComm WebSocket traffic to /didcomm-ws on the HTTP listener.
  • Kept administrative APIs and event WebSockets on the private listener.

Reviewed changes

Copilot reviewed 13 out of 14 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/agent.ts Configures shared DIDComm transports.
src/index.ts Creates public/private listeners and upgrade routing.
src/env.ts Updates endpoint defaults.
src/utils/readiness.ts Adds startup request gating.
tests/unit/agent.test.ts Tests listener separation and routing.
tests/unit/utils/helpers.ts Adds shared-listener test setup.
tests/integration/assets/dids/alice/did.json Updates Alice’s DIDComm endpoint.
tests/integration/assets/dids/bob/did.json Updates Bob’s DIDComm endpoint.
tests/integration/assets/dids/charlie/did.json Updates Charlie’s DIDComm endpoint.
docker-compose.yml Updates single-agent endpoints.
docker-compose-testnet.yml Updates testnet endpoints.
README.md Documents the new endpoint paths.
Suppressed comments (1)

src/index.ts:177

  • This opens the gate before listener startup has actually succeeded. privateApp.listen(...) is asynchronous, and DidWebServer.start() also returns immediately after calling listen(), so public DIDComm traffic can be accepted while the private API or enabled DID:web server is not yet bound; a subsequent bind error also occurs after readiness was announced. Await each server's listening event (and reject on error) before calling markReady().
// Public and private listeners, TSOA routes and DID:web are all up: only now accept protocol traffic.
readinessGate.markReady()

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/index.ts Outdated
Comment thread src/agent.ts
Comment thread tests/unit/agent.test.ts Outdated
Comment thread README.md
Comment thread docker-compose.yml
@rmlearney-digicatapult
rmlearney-digicatapult requested a balanced review from Copilot August 20, 2026 16:02

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 13 out of 14 changed files in this pull request and generated no new comments.

Suppressed comments (5)

src/agent.ts:263

  • A WS-only configuration now creates a noServer WebSocket server, but the upgrade handler is installed only when an HTTP transport also exists. Because the current validation only checks ports when both entries are present, setupAgent can succeed with an unreachable WS inbound transport. Reject WS without HTTP (or retain a standalone WS listener) so this valid former configuration does not fail silently.
  const httpTransportConfig = inboundTransports.find((t) => t.transport === 'http')
  const wsTransportConfig = inboundTransports.find((t) => t.transport === 'ws')
  if (wsTransportConfig && httpTransportConfig && wsTransportConfig.port !== httpTransportConfig.port) {

src/index.ts:166

  • The readiness contract is opened before all listeners are actually ready. privateApp.listen() returns before its listening callback, and DidWebServer.start() likewise returns immediately after calling listen, so public DIDComm traffic can be accepted while the private or DID:web bind is still pending (or about to fail). Await each server's listening event and reject on error before calling markReady().
// Public and private listeners, TSOA routes and DID:web are all up: only now accept protocol traffic.
readinessGate.markReady()

tests/unit/agent.test.ts:209

  • This new suite starts privateServer but never closes it, unlike the existing getTestServer users. The open HTTP handle can keep Mocha alive and leaks a listener into subsequent tests.
  after(async () => {
    await agent.shutdown()
    await deleteAgentStore(agent)
  })

tests/unit/agent.test.ts:243

  • This test closes immediately after the WebSocket handshake, so it does not prove that the newly wired handleUpgrade path delivers DIDComm frames to DidCommWsInboundTransport. Add a multi-agent integration case that sends a DIDComm message through the advertised /didcomm-ws endpoint and verifies the receiving agent's event or record state.
  test('accepts a WebSocket upgrade at /didcomm-ws', async () => {
    const { port: boundPort } = httpServer.address() as AddressInfo
    const outcome = await attemptWebSocketUpgrade(`ws://127.0.0.1:${boundPort}${DIDCOMM_WS_PATH}`)
    expect(outcome).to.equal('open')

package.json:52

  • The PR description says the OpenID4VC dependency is installed, but this dependency hunk removes @credo-ts/openid4vc (and its lockfile entries), while the implementation says OpenID4VC is deferred to a later PR. Please either retain the dependency and intended OpenID4VC scope or update the PR description so the delivered scope and release review are accurate.
    "@credo-ts/node": "^0.7.0",
    "@hyperledger/anoncreds-nodejs": "^0.4.0",

Signed-off-by: rmlearney-digicatapult <robert.learney@digicatapult.org.uk>
Signed-off-by: rmlearney-digicatapult <robert.learney@digicatapult.org.uk>
Signed-off-by: rmlearney-digicatapult <robert.learney@digicatapult.org.uk>
Signed-off-by: rmlearney-digicatapult <robert.learney@digicatapult.org.uk>
@rmlearney-digicatapult
rmlearney-digicatapult force-pushed the feat/openid4vc-shared-listener branch from 90d0fb4 to b943525 Compare August 22, 2026 18:33
Signed-off-by: rmlearney-digicatapult <robert.learney@digicatapult.org.uk>
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.

3 participants