Milestone: M5 — Disclosure and Verification | SOW Reference: FR2, FR3, INT2, INT4, INT7, NFR5 | Requirement Clarity: 🟢 2026-08-26: Velocity's own "CV Capture" spec gives a concrete implementation pattern (real API endpoints, the "depot" isolation model, 6 verification checks). Reclaim Employment's build cost still depends on the unanswered M7 cross-reference. | Dev Status: ❌ Zero code exists — this is the starting point
M5-00 Disclosure & Verification — Implementation Guide¶
Why this exists¶
M5 is the one milestone where "what do we build" and "nothing has been built yet" overlap completely — 17 task files (M5-01–M5-17) already exist, each written as a design-ahead spec, but none of them explain the underlying Velocity protocol or give a single ordered starting point. This doc is that starting point: it explains what disclosure and verification actually are on the Velocity Network, what today's client feedback means for scope, what's already modeled in this codebase (more than it looks — three inert entity files), and what order to build things in. Read this before any individual M5-0x file.
See also: Flow & UI Reference — an interactive page in this same folder with the CV Capture sequence diagram, the org-admin decision flow, a live UI mockup (request creation, verification results, and the package/email settings screen), and a running list of what's changed across both clarification calls.
Today's client feedback (2026-08-20) — superseded, see 2026-08-25 update below¶
"Disclosure & Verification: MVP will use one generic disclosure request. Verification should support multiple credentials with clear status/results. Existing Verify My Credentials UI will be used as a reference."
This single sentence resolves two of the three items this milestone's README already flagged as blocking, and directly confirms what the original requirements PDF's Appendix 1 (the image-only user-story tables, not the body text) already said: the MVP disclosure request is one fixed, generic request — not a configurable policy/template system. See "What's in vs. out of MVP" below for the concrete effect on each task file.
This "one generic request" framing was corrected five days later — see the 2026-08-25 update immediately below before reading the rest of this document.
Update 2026-08-25 — request "packages," not one generic request¶
A follow-up clarification call replaced the framing above. Source: 2026-08-25 Disclosure and Verification clarification.pdf, shared during the call.
Not one generic request — org-configured packages. An Org Admin sets up named "packages" in org settings, each a combination of: 1. Disclose Credentials 2. Claim Right to Work (RTW, via DataChecker — Home Office-approved) + Disclose Credentials 3. Reclaim Employment (HMRC) + Disclose Credentials 4. Claim RTW + Reclaim Employment (HMRC) + Disclose Credentials
MVP ships combinations 1 and 3. Combinations involving RTW (2 and 4) are "for info only at this stage" — the option can exist in the UI/data model, but the actual DataChecker integration is not being built now; treat it the same as any other clearly-deferred, not-yet-scoped integration. The client's own stated aim is for "1 and 3" to become the default package for new orgs once — their words — "the Reclaim protocol is working," implying Reclaim Employment (HMRC) may not be fully live on Velocity's side yet. Confirm this readiness before committing a delivery date for combination 3.
The Org User picks one pre-configured package per request, not a fixed request shape. The resulting email is "a single mail with one or more sections of pre-configured text" — one section per atomic option in the package (Disclose / RTW / Reclaim).
Reclaim Employment (HMRC) — flag before assuming this is simple. The client's framing this session is that our side only needs to add a link into the disclosure email, and the rest is handled entirely inside the Velocity Career Wallet app (candidate authenticates to HMRC from inside their wallet, the wallet produces the proof/credential, which then comes back to us through the normal Velocity disclosure channel). This needs to be reconciled with M7 (Reclaim Protocol milestone) before being taken as confirmed scope: M7-01 has been asking Curo since 2026-04-16 whether "Reclaim Protocol" means a separate third-party integration our own backend would need to build against directly (HMRC OAuth, Reclaim Protocol's own API, zkTLS-style proof verification) — a materially larger build than "add a link." It is not yet confirmed whether today's "Reclaim Employment (HMRC)" package option is: - (a) the Velocity Career Wallet having its own native HMRC-pull feature, meaning our backend never talks to Reclaim Protocol or HMRC at all — just requests a credential type and receives it via normal disclosure, or - (b) the same integration M7-01 scoped, now packaged differently at the UI level but still requiring the backend work M7 describes.
Getting this wrong in either direction is expensive — treating (b) as (a) means discovering a real, unscoped HMRC/Reclaim Protocol integration mid-build; treating (a) as (b) means over-building something Velocity's wallet already does. This is the single most important thing to get an explicit answer on before estimating combination 3. See M7-01, still marked blocking.
Review dashboard (Stage 2 of the PDF). An Org User searches for a person who has submitted credentials — UI to copy from VerifyMyCredentials (Andres to supply, resolving the open question below about a concrete reference). Sent-but-unanswered requests and completed disclosures live in the same list, with "new" returns highlighted. Table: Name / Status / Status date / Date sent (tracked, not shown as a column) / Actions (Resend — only while unanswered; View). Statuses observed in the mockup: Requested → Received → Verified (pass/fail).
Verification (Stage 3) UI rules, more precise than previously documented:
- Verification is optional and separate from receiving a disclosure — a Received item can be left unverified indefinitely.
- Overall result is a single green/red indicator per disclosure — red if any sub-part of the verification fails, not a per-check pass/fail table at the list level (the per-check detail lives one level in, on the detail page).
- Store who verified and when, for audit — check this against the existing VerificationReport entity's columns (see "What's already in the codebase" below) before adding new ones.
- Re-verify is a detail-page-only action. It must not appear on the main table row — "it's an unusual action to take" (client's own words).
- Post-MVP, explicitly deferred: an automation/notification layer for upcoming credential expiry.
The 4-vs-5-checks question is still open, and now reaffirmed. The PDF again says "5 factors" per credential, without specifying the 5th. This document already flagged (see Part 2, item 6 below) that Velocity's real credentialChecks schema only exposes 4 fields. The client has now restated "5" twice without resolving the gap — this needs a direct question back to them, not another attempt to infer it from Velocity's docs. Substantially clarified one day later — see the 2026-08-26 update below.
Update 2026-08-26 — Velocity's "CV Capture" spec: the real implementation pattern¶
Velocity's own team shared a detailed technical wiki page today: velocitycareerlabs/credential-platform wiki — CV Capture. This is materially more precise than anything used to write Parts 1–5 below, which were pieced together from marketing-site pages and a public JSON schema. Treat this wiki page as the primary source going forward — Parts 2, 4, and the open questions below are corrected here rather than rewritten in place, so the history of what changed stays visible.
What "CV Capture" is, in this platform's terms: it's Velocity's named pattern for exactly M5 — a staffing company privately requesting verified credentials from a candidate for one specific job application, isolated from any other application, with the recruiter never seeing raw JWTs or being able to identify the candidate outside the application context they already have.
The API surface is different from — and more specific than — what Part 2 assumed:
- POST /operator/relying-party-services/create — org-level setup (once per org, like the "disclosure request definition" Part 2 describes), but with real fields: tenantId, and a service object carrying mode: "single", velocityNetworkServiceId (the org's inspector service DID reference), termsUrl, description, a disclosureRequest block (types, purpose, retentionPeriod as an ISO8601 duration like P30D), and presentationRequestsExpireIn (seconds until a share link expires, e.g. 900).
- POST /operator/depots/create — new concept, not in our current data model. A "depot" is an isolated storage container created per application/share-attempt, not per org and not per candidate. Its userReference must be an opaque HMAC-derived string (hmacSha256(applicationReferenceKey, "${employerId}:${applicationId}:${shareAttemptId}")) — never the candidate's name, email, or job title. Our own database must separately store employerId + applicationId + shareAttemptId → tenantId + serviceId + depotId + userReference, since Velocity's side never sees anything that could identify the candidate directly.
- POST /operator/presentation-links/refresh — takes tenantId + serviceId + depotId, returns the candidate-facing deep link/QR/OpenID4VP link. This replaces "start-an-exchange" from Part 2 — the exchange is scoped to a depot, not created standalone.
- GET /operator/presentations/get — fetches the submitted presentation by tenantId + depotId. Must verify the returned presentation's depotId matches the application's current depot before showing anything to a recruiter — this is the actual mechanism that prevents a stale/forwarded link from leaking a different application's data.
- POST /operator/presentations/verify — takes tenantId + presentationId, returns per-credential, per-check results.
This resolves the 4-vs-5-checks question — not as "4" or "5," but as 6 real checks plus a timestamp, and explains why the count kept disagreeing: the public credentialChecks schema (4 fields) only covers credential-level authenticity. CV Capture's recruiter-facing spec requires two more, which only make sense in a disclosure/presentation context, not a standalone credential:
1. Holder signature — did the wallet submitting this actually sign with a valid holder key
2. Credential tamper check — same as the schema's UNTAMPERED
3. Trusted issuer check — same as TRUSTED_ISSUER
4. Trusted holder check — is the submitting entity authorized to hold/submit this credential
5. Revocation check — same as UNREVOKED
6. Expiry check — same as UNEXPIRED
7. (displayed alongside, not a pass/fail check itself) Verification timestamp — when the operator ran the check
If any single one of the 6 fails, the whole credential must show as unverified — not partially verified. This is an explicit rule in the spec, and matches the aggregate-status model already in this document, just applied one level down (per-credential, not just per-request).
Authorization rules — new, and worth building in from day one, not retrofitting:
- The backend must independently check (never trust the browser for any of this): the recruiter's session is valid, the session's employer/org matches the application's org, the recruiter has permission for this specific application, the application's configured tenant matches the org's assigned tenant, and the retrieved presentation's depotId matches the application's current depot.
- The browser must never supply tenantId, depotId, presentationId, or employerId as authorization inputs — all of it is derived server-side from the recruiter's session and the application record, never from request parameters a client could tamper with.
- The operator bearer token must never reach the browser — not in page source, not in localStorage, not in any network response visible client-side, not in analytics/telemetry.
- One depot per share attempt, always — re-requesting a CV for the same application creates a new depot, so an old forwarded link can never surface a newer submission or vice versa.
A real protocol gotcha, worth flagging to whoever implements the wallet-facing side (or verifying our understanding of what the wallet already does, since we don't control it): the outer deep link's query parameters other than request_uri (e.g. vendorOriginContext) must be copied onto the fetched request_uri before resolving it, and vendorOriginContext specifically must be echoed back inside the submitted presentation. The spec is explicit that skipping this causes Velocity to silently create an uncorrelated depot — i.e. the disclosure would come back with no way to match it to the right application. This is exactly the kind of silent-correlation bug that would be very hard to diagnose after the fact.
A real constraint on GDPR erasure (relevant to M9): the spec explicitly states the current operator API does not expose a presentation-deletion endpoint. Do not promise candidates automated deletion of disclosed data from Velocity's side until a real mechanism is confirmed — this directly affects what M9-01 can commit to.
Concrete PDF report rules (relevant to M5-10, which currently has almost nothing specified): one credential and its verification checks per page; printing disabled; if generation fails, show a clear error — never render a download link with an empty href.
A ready-made acceptance test plan (relevant to M5-13) — four categories, each with several concrete cases: Happy Path (full flow, results match exactly what was disclosed), Access Isolation (cross-org access blocked, browser-supplied IDs never trusted for auth, forwarded links can't leak old submissions, operator token never client-visible), Protocol Correctness (invalid signatures rejected, deep-link parameters forwarded and echoed correctly, replay of a completed exchange rejected), and Verification/Failure States (exhausted vouchers show a clear message not a false green check, revoked/expired/untrusted/tampered credentials each show distinct failure states, PDF failures are visible not silent).
One workaround worth knowing about if we ever build the test-issuance path: the spec notes that including Email-type credentials in a test-issuance depot caused link refresh to fail, because email configuration wasn't present in the operator's supported types at the time it was tested — Employment and Education credential types issued successfully once Email was removed. Worth re-testing before relying on this, since it may since have been fixed.
Who raises it, where it lands, who tracks it¶
Confirmed 2026-08-20, kept strictly to MVP: Org Admin owns this, same as every other org-level function. M2-15 confirms Org Admin as the sole functional role across every org-level function in this app for MVP — user management, branding, KYB, issuing, templates, employees, and now Disclosure & Verification too. This isn't a temporary stand-in while something else gets built; it's the confirmed scope.
- Raises the request: Org Admin.
- Where it lands: wherever M5's screens get added to the existing Org Admin navigation — no separate role-specific section needed for MVP.
- Who tracks it: the whole org's Org Admin staff — a shared view, not a personal inbox, matching how
disclosure-management-dashboard.html's wireframe is already drawn (bulk follow-up actions across all pending requests, not filtered to one sender). - Gate every new M5 controller with
@Roles(ORG_ADMIN)— no other role needed for MVP.
Worth knowing for later, not acting on now: the requirements PDF describes the Staffing Company's overall role on the Velocity network as "Relying Party" for this functionality, and this codebase already has an unused RELYING_PARTY role with a reserved (but unbuilt) nav section — /verifier/dashboard, /verifier/verify, /verifier/history in navConfig.tsx:114-126. If Curo ever wants to split verification duties into their own role, that scaffolding is half there. But per the confirmed MVP scope, there's no reason to wire it in now — see M2-15 anomaly 3.
Part 1 — What "disclosure" and "verification" actually are¶
These are two distinct, sequential steps on the Velocity Network, confirmed against VNF's own developer documentation:
- Disclosure — the process by which a credential holder (the candidate) shares one or more of their Verifiable Credentials with a requesting organisation. This is the request + the holder's response.
- Verification — the separate step where the receiving organisation's Credential Agent checks each shared credential for authenticity: is it from a trusted issuer, has it been revoked, has it expired, has it been tampered with.
Four roles are involved: Holder (the candidate, via their wallet app), Inspector (Curo-REC, in the role of Relying Party, requesting the disclosure), Issuer (whoever originally issued the credential — possibly this same platform, possibly not), Registrar (Velocity's network-wide directory of organisations and their DIDs). An organisation's Credential Agent can hold the Issuer and Inspector roles at once — this platform already acts as an Issuer (M3); M5 adds the Inspector role on the same Credential Agent.
Part 2 — The protocol flow, end to end¶
Superseded by the 2026-08-26 CV Capture update above — this section's "create a definition once / start an exchange per candidate" model is close but not exact; the real flow is scoped through a per-application "depot," not a bare exchange. Left as-is below for the general shape (roles, wallet behaviour, webhook+poll pattern all still hold) — read the 2026-08-26 section above for the corrected API calls and the depot/authorization model before building against this.
sequenceDiagram
participant Admin as Curo-REC Admin
participant BE as Curo-REC Backend
participant CIH as Velocity Credential Agent
participant Wallet as Candidate's Wallet
Admin->>BE: Request disclosure for candidate
BE->>CIH: Start an Exchange (against one shared<br/>generic Disclosure Request Definition)
CIH-->>BE: exchangeId + deep link / QR
BE->>Wallet: Email with claim-style link (reuses M3-05 pattern)
Wallet->>CIH: Resolve request_uri → signed presentation request JWT
Wallet->>Wallet: Verify inspector DID matches deep link
Wallet->>Wallet: Candidate picks which credentials to share
Wallet->>CIH: Submit presentation (chosen credentials)
CIH->>CIH: Run per-credential checks<br/>(trusted issuer / unrevoked / unexpired / untampered)
CIH-->>BE: Webhook callback (+ polling fallback)
BE->>BE: Store one VerificationReport row per disclosed credential
Admin->>BE: View results
BE-->>Admin: Per-credential status grid + PDF/CSV export
- Create the Disclosure Request Definition — a call to Velocity's Credential Agent Operator API (
create-a-disclosure-request-definition). Because MVP uses one generic request, this happens once (at platform or org setup), not per-candidate. Structurally it's a DIF Presentation-Exchange-style object: aninput_descriptorsarray (one entry per credential type you're willing to accept) plus asubmission_requirementsrule — the real VNF sample fixture for exactly this "request everything, pick at least one" case lists ~30 credential types under one group with{"rule": "pick", "from": "A", "min": 1}. That fixture is the shape of a generic request — confirms this is a real, supported, documented pattern, not something we'd be inventing. - Start an Exchange per candidate (
start-an-exchange), referencing the one definition from step 1. Returns anexchangeIdand a deep link (velocity-network-devnet://inspect?request_uri=...&inspectorDid=...) — the same deep-link mechanism this codebase already uses for credential issuance (M3-06), just a different verb (inspectvsissue). - Deliver the link by email — reuse the M3-05 claim-email/landing-page pattern directly; this is the same "generate a link, brand an email, track open/click" infrastructure already built for issuance.
- The candidate's wallet handles the rest — resolves the request, verifies it's genuinely from this org (DID match), lets the candidate choose which of their credentials to share, and submits. Nothing on this point happens inside Curo-REC's UI — confirmed by an exhaustive search of both the wireframes and the live app for any candidate-facing "share credentials" screen: there isn't one, by design. The interaction lives entirely in the candidate's wallet app.
- Results come back two ways: a webhook callback to our backend (Velocity calls this a "Vendor Webhook" — RPC-style, not REST), and a polling endpoint as a fallback. Build both — the exact webhook payload isn't in the publicly reachable docs, so a resilient implementation shouldn't depend on the webhook alone (mirrors the "self-heal stale sessions" approach already used elsewhere in this codebase for a similar reason).
- Verification runs per disclosed credential. Velocity's own schema for this (
credentialChecks) is four fields —TRUSTED_ISSUER,UNREVOKED,UNEXPIRED,UNTAMPERED— eachPASS/NOT_CHECKED/NOT_APPLICABLE(and presumablyFAIL, not seen in any sample fixture, all of which were happy-path). Open item to verify against the live API before finalizing the UI: every occurrence ofcredentialChecksfound in this codebase's reference material was attached to an organisation profile check, not a disclosed candidate credential — the shape is very likely identical for both (same backend, same vocabulary) but this should be confirmed against Velocity's real Operator API response for a disclosure exchange, not assumed. - Credits are spent per verification, per the original requirements doc's Appendix 1: either the Relying Party's own credit balance or the platform's pooled credits (the pooled option is explicitly time-boxed to 12 months in that doc) — the org's backend calls the Registrar to convert credits to a voucher, then uses that voucher to call the Credential Agent for verification. This is M5-11; see "corrections" below — the mechanism is documented, it isn't a genuine "spec doesn't exist" blocker.
Part 3 — Multi-credential support & status display (today's second feedback point)¶
"Verification should support multiple credentials with clear status/results."
Good news: the existing (currently unused) database schema already anticipated this. PresentationRequest already declares a OneToMany relation to VerificationReport — meaning the data model was designed for one report per disclosed credential, not one flat result per request. It just needs one column added to make that real: VerificationReport currently has no credentialType (or Velocity credential id) column to say which disclosed credential a given report row is for. Add that, and the "multiple credentials, clear status per credential" requirement is a schema tweak plus wiring — not new architecture.
Recommended shape:
- One VerificationReport row per disclosed credential, each holding that credential's own credentialChecks (4 booleans/enums) plus its own overall pass/fail.
- PresentationRequest.verificationStatus (already exists: pending / success / partial / failed) becomes the aggregate across all of a candidate's disclosed credentials — partial is exactly the right existing value for "3 of 4 credentials verified clean."
- On the frontend, adapt the existing verification-dashboard.html wireframe's per-credential PASS/FAIL grid — today it's drawn for a single hardcoded 5-row example; make it loop over however many credentials a given candidate actually disclosed.
Part 4 — What's already in the codebase (more than it looks)¶
app/backend/src/verification/ contains three TypeORM entity files and nothing else — no controller, no service, and the module doesn't even register the entities (TypeOrmModule.forFeature([]) is empty). But the entities themselves are a genuinely useful starting skeleton:
| Entity | What it already models |
|---|---|
PresentationRequest |
org, candidate, requesting user, a policy jsonb field (currently unused — a natural place to store the requested credential-type list once that's not hardcoded), a velocityRequestId, status lifecycle (created → sent → responded → verified → expired), and the verification-status aggregate described above. Missing, per the 2026-08-26 CV Capture spec: a depotId column (and ideally the userReference used to create it) — every request needs its own isolated depot, and velocityRequestId alone doesn't capture that. Add before building M5-01. |
VerificationReport |
linked to a PresentationRequest, a verifier string, a result jsonb blob, and a dead pdfS3Key column (no PDF library exists anywhere in this codebase yet — confirmed via package.json) |
Attachment |
generic S3-key attachment record, already shared with KYB records via an ownerType enum — reusable as-is for storing verification PDFs once M5-10 is built |
There is no existing client code for the Operator/Inspector side of the Velocity API anywhere in this codebase — the CIH client this platform already built (shared/services/cih/cih-api.service.ts) only covers the issuer side (tenants, issuer services, credentials, claim links). M5 needs a new, parallel client for disclosure request definitions, exchanges, and verification results — same pattern as the existing CIH client, different endpoints.
Part 5 — What's in vs. out of MVP, given today's feedback¶
The wireframes in docs/design/ui-designs/credential-disclosure/ and credential-verification/ (9 screens total) were designed before today's simplification and go considerably further than "one generic disclosure request." Mapped against today's feedback and the original requirements doc's own Appendix 1 (which independently says the same thing — policy configurability is explicitly "post-MVP" there):
In MVP:
- Send a disclosure request from a candidate's record — no configuration form, because there's only one generic request (M5-01, drastically simplified from disclosure-request-form.html's full field set)
- Disclosure status tracking per candidate — sent / responded / expired (M5-05, disclosure-management-dashboard.html's status table, without its template/policy columns)
- Manual "send a reminder" action — the original requirements doc's Appendix 1 explicitly says MVP reminders are manual, with automated multi-stage scheduling deferred to a later drop (M5-04 — flagging a real conflict here, see corrections below)
- Per-credential verification results — adapt verification-dashboard.html's status grid to loop over N disclosed credentials (M5-08, M5-09)
- CSV export of disclosures + verification status, PDF report per verification (M5-06, M5-10 — basic versions)
Out of MVP (wireframe exists, but neither today's feedback nor the requirements doc's Appendix 1 calls for it at this stage):
- verification-configuration.html — per-credential-type configurable verification areas, saved templates, audit log (M5-15)
- batch-verification.html — verifying many candidates' credentials in one bulk action (M5-16)
- notification-preferences.html — the full personal + team notification-preference system (M5-17)
- The disclosure request form's identity-assurance-level dropdown, verification-policy dropdown, and "save as template" flow (part of M5-02)
Corrections to the existing M5 task files¶
Cross-checking this milestone's docs against the actual requirements PDF (including its image-only Appendix 1 pages, which don't appear to have been read as closely before) turned up several things worth fixing:
- M5-02 (policy configuration) — no longer blocking. Today's client feedback ("one generic disclosure request") directly answers this task's open question, and independently matches the requirements doc's own Appendix 1: "For the MVP, proof of email address will be assumed sufficient... For verification, the MVP will include a default identity type required, set for all staffing companies." Configurable policy is confirmed post-MVP. Recommend re-marking M5-02 ✅ resolved, scoped to "hardcoded default, no admin UI."
- M5-11 (credits/vouchers) — the spec isn't actually missing. The task file states the original v3 requirements document describing this mechanism isn't present in this repo. It is —
REC Verifiable Credentialing Platform High-level Requirements v3.0.pdf, Appendix 1 page 14 — and it describes the exact mechanism (Relying Party or platform-pooled credits → Registrar → voucher → Credential Agent, pooled option time-boxed to 12 months). This should move from "spec doesn't exist" to "spec exists, needs Curo's confirmation this still reflects their intent" — a much smaller ask. - M5-14 (general payments) — already answered. Appendix 1 page 15: "assumed for the MVP initial stages that... payments will be handled by offline manual invoicing... Stripe... implemented in later iterations." No MVP code needed here beyond, at most, a manual "mark as invoiced" flag.
- M5-04 (reminders) vs. the wireframe — a real conflict, not yet flagged anywhere.
email-template-editor.htmlshows a fully automated multi-stage follow-up scheduler (first/second follow-up after N days, max follow-up count). The requirements doc's Appendix 1 explicitly says MVP reminders are sent manually, with automation deferred. Build the manual version for MVP; keep the wireframe as the post-MVP target. - M5-06 (CSV export) undersells its own wireframe. The task file assumes "Excel" is just CSV with a different file extension.
data-export-interface.htmlactually presents CSV and Excel as two separate export-format cards with distinct descriptions ("Native Excel format with formatting preserved") — worth deciding explicitly whether MVP needs a real.xlsxwriter or CSV-only is acceptable, rather than assuming. - "Existing Verify My Credentials UI" — needs a concrete pointer from Curo. Public research surfaced two plausible candidates —
VerifyMyCreds(a Velocity Career Labs product, but described as a holder/candidate wallet app, not an inspector-facing verification UI) and a "Universal Verification Demo" page on Velocity's own site (page exists but returned no usable content) — neither is a confirmed match. Since the client explicitly wants this as a design reference, worth asking for the specific URL or screenshot before the verification-results UI is finalized, rather than guessing which one they mean. - Unrelated but worth flagging once, here: every M5 task file (and M1-01) cites a formally-numbered SOW — FR1–FR8, NFR1–NFR6, Appendices 1–6, a named Acceptance Lead — that doesn't appear to exist anywhere in this repository. Only the informal
REC Verifiable Credentialing Platform High-level Requirements v3.0.pdfis present, and its only appendix is the unnumbered, image-only "Appendix 1 – User Stories" table used throughout this doc. Every "per SOW Appendix 3, FR2 acceptance criteria"-style citation across the milestone docs is currently unverifiable against any file in the repo — worth confirming with Curo/NeuralRays whether a separate formal SOW document exists and should be added, since it would upgrade every one of those citations from "as described to the doc author" to "verified."
Suggested build order¶
- Register the three existing entities with TypeORM; add
credentialType/vnfCredentialIdtoVerificationReport. - Build the Operator-API client (new — mirrors
cih-api.service.ts's pattern): create-disclosure-request-definition (once), start-exchange, get-exchange-results. - Webhook receiver + polling fallback for exchange completion.
- Map
credentialChecksper disclosed credential intoVerificationReportrows; compute the aggregateverificationStatusonPresentationRequest. - Frontend: "Request disclosure" action on the candidate record (M5-01/M5-03, reusing M3-05's email infrastructure) + disclosure status view (M5-05).
- Frontend: per-credential verification results grid (M5-08/M5-09), adapted from
verification-dashboard.html. - CSV export + PDF report, basic versions (M5-06/M5-10).
- Manual follow-up reminder action (M5-04, manual only for MVP).
- Wire the credits/voucher call into the verification step (M5-11) — confirm the exact starting credit source with Curo first.
- Explicitly defer M5-15, M5-16, M5-17, and the elaborate parts of M5-02 to post-MVP.
Open questions for Curo¶
- Point us to the specific "Verify My Credentials" reference (URL or screenshot) — couldn't be confirmed independently.
- ~~Confirm the 4 Velocity
credentialChecks...~~ Substantially resolved 2026-08-26 — Velocity's CV Capture spec lists 6 real checks (holder signature, tamper, trusted issuer, trusted holder, revocation, expiry) plus a timestamp, not 4 or 5. Remaining ask: confirm with the client that these 6 map onto whatever they meant by "5 factors" — likely they were undercounting the presentation-level checks (holder signature, trusted holder), which don't show up if you only look at a standalone credential's schema. - Confirm which credit source (Relying Party's own vs. platform pooled) MVP should start from for verification.
- Confirm manual-only reminders are correct for MVP (matches the requirements doc; today's feedback didn't mention reminders directly).
- Confirm whether a formal, numbered SOW document exists separately from the requirements PDF already in this repo.
- Confirm the CV Capture spec's data model (relying-party services → depots → presentation links, one depot per application) is meant to replace M5-01's simpler "one request per candidate" framing, not sit alongside it.
Related¶
- M5-01 · M5-02 · M5-03 · M5-04 · M5-05 · M5-06 · M5-07 · M5-08 · M5-09 · M5-10 · M5-11 · M5-14 · M5-15 · M5-16 · M5-17
- M3-05 / M3-06 — the issuance-side email/deep-link infrastructure this milestone reuses
app/backend/src/shared/services/cih/cih-api.service.ts— the issuer-side API client to mirror for the new Operator/Inspector client- CV Capture — velocitycareerlabs/credential-platform wiki — primary source for the 2026-08-26 update; the actual implementation spec for this milestone, shared directly by Velocity
- M9-01 GDPR right to erasure — constrained by the "no presentation-deletion endpoint" note above