Skip to content

feat(verification): Verification Status & Compliance API (#74) - #85

Merged
Kalchaqui merged 3 commits into
Thalos-Infrastructure:mainfrom
josueazc:feat/issue-74-verification-api
Jul 23, 2026
Merged

feat(verification): Verification Status & Compliance API (#74)#85
Kalchaqui merged 3 commits into
Thalos-Infrastructure:mainfrom
josueazc:feat/issue-74-verification-api

Conversation

@josueazc

@josueazc josueazc commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

What & why

Implements a centralized Verification Status & Compliance API — the single source of truth for KYC (individual users) and KYB (organizations) compliance status across Thalos. Other services (Agreements, Reputation, Enterprise, future marketplace) can consume it without caring which identity provider produced the data.

Closes #74.

Scope is intentionally the read/aggregation side described in #74. The full provider abstraction with sessions/create/cancel (#71) and the integration test suite (#75) are sibling issues and out of scope here.

Endpoints

Route Auth Description
GET /v1/verification/user/:id Bearer JWT Standardized KYC status for a user
GET /v1/verification/business/:id Bearer JWT Standardized KYB status for a business

Both always return 200 with a standardized payload — an unknown or unverified subject returns an unverified response rather than a 404, so consumers always get the same shape.

Standardized response

{
  "subjectType": "user",
  "subjectId": "…uuid…",
  "isVerified": true,
  "status": "verified",        // unverified | pending | verified | expired | rejected
  "level": "standard",         // none | basic | standard | advanced
  "provider": "persona",       // provider backing the effective verification (or null)
  "expiresAt": "2027-01-01T00:00:00.000Z",
  "lastUpdated": "2026-05-01T00:00:00.000Z"
}

Maps 1:1 to the requested fields: Is Verified? / Verification Level / Verification Expiration / Verification Provider / Last Updated.

Provider-agnostic aggregation

A subject can have several verifications rows (one per provider). The service reduces them to one effective status:

  • A verified row past its expires_at counts as expired (not verified).
  • isVerified is true iff at least one row is currently-valid verified.
  • When verified, level/provider/expiresAt come from the highest-level valid row (ties broken by most recent update).
  • When not verified, status surfaces the most advanced state present (pending > expired > rejected > unverified).
  • Fail-closed: a DB error is never reported as verified.

Changes

  • src/verification/ — module, controller, service, types, unit tests
  • scripts/002_create_verifications.sqlverifications table (provider-agnostic) with RLS + indexes. Not yet applied — run against Supabase before hitting the endpoints.
  • app.module.ts wiring + README (Modules / Main routes tables)

Design notes / decisions

  • :id is validated as a UUID (ParseUUIDPipe).
  • Endpoints require Bearer JWT (compliance data is sensitive); the service reads via the Supabase service role, so RLS only scopes direct client access.
  • Today an "organization" is an enterprise profile; subject_id is stored generically (no hard FK) so a dedicated organizations entity can slot in later without a schema break.

Testing

Unit tests cover the aggregation rules (no records, valid verified, verified-but-expired, highest-level-wins across providers, pending, DB-error fail-closed).

Note: local pnpm install/build/lint could not be completed in my environment due to a full disk; nest build type-checked the new module with zero errors (the only build error was a pre-existing missing @stellar/stellar-sdk in an unrelated file). Relying on CI (format:check + lint:check) for the final gate.

@vercel

vercel Bot commented Jul 20, 2026

Copy link
Copy Markdown

@josueazc is attempting to deploy a commit to the ManuelJG's projects Team on Vercel.

A member of the Team first needs to authorize it.

Adds a centralized, read-only Verification API that exposes KYC (user) and
KYB (business) compliance status as the single source of truth, aggregating
records across identity providers into one standardized response.

- GET /v1/verification/user/:id      — KYC status for an individual
- GET /v1/verification/business/:id  — KYB status for a business
- Standardized response: isVerified, status, level, provider, expiresAt, lastUpdated
- Provider-agnostic aggregation (highest valid level wins; expiry applied; fail-closed)
- verifications table migration (scripts/002) with RLS + indexes
- Unit tests for the aggregation rules; README + module wiring

Closes Thalos-Infrastructure#74
@josueazc
josueazc force-pushed the feat/issue-74-verification-api branch from c494e5f to 136f944 Compare July 20, 2026 20:15
@ManuelJG1999

Copy link
Copy Markdown
Collaborator

@josueazc could you please review the conflicts?

Thank you

@josueazc

Copy link
Copy Markdown
Contributor Author

Sure! I've reviewed the conflicts and they've been resolved. Thanks!

@Kalchaqui

Copy link
Copy Markdown
Collaborator

Review — almost ready (a few blockers before merge)

Thanks @josueazc — this is a strong fit for #74. The read/aggregation scope is clear, the response shape matches the issue, JWT is required, aggregation + fail-closed are well thought out, and the unit tests cover the important cases. Conflicts look resolved.

Please address the following before we merge.

Blockers

  1. Migration numbering collision with main

  2. “Single source of truth” vs existing KYB (and upcoming KYC) storage

    • main already persists KYB in public.kyb_verifications (src/kyb/).
    • #72 / #80 introduce a separate KYC table.
    • This PR introduces a third table (public.verifications) and only reads from it — so today the API will always return unverified unless something else writes here.
    • That can be the right design (SSoT projection that KYC/KYB writers upsert into), but it needs to be explicit in the PR:
      • either document that KYC/KYB flows must write/sync into verifications, or
      • aggregate from the existing provider tables until that sync exists.
    • Please add a short note in the PR / README so Design Identity Provider Abstraction Layer #71 / Integrate KYC Verification Provider #72 / Build KYC/KYB Integration Test Suite #75 don’t invent yet another status shape.
  3. Authorization is still too open for compliance data

    • JwtAuthGuard is good (better than an unauthenticated status route).
    • Any authenticated user can still GET /verification/user/:id or /business/:id for any UUID (IDOR on compliance status).
    • Please gate to: caller is the subject, an admin/internal role, and/or an internal service secret (Agreements / Reputation consumers). Same bar we want for KYC status.

What looks good

  • Endpoints match the issue (GET /verification/user/:id, GET /verification/business/:id)
  • Standardized payload: isVerified / status / level / provider / expiresAt / lastUpdated
  • Expiry handling, highest-level-wins, pending surfacing, DB-error fail-closed
  • Unit tests for the aggregation rules
  • Module export so other Nest modules can consume the service later
  • UUID validation via ParseUUIDPipe

Happy to re-review after the migration rename + authz tightening + a clear data-model note for KYC/KYB writers.

…SSoT docs)

- Rename migration 002_create_verifications.sql -> 004 to clear the number
  collision with kyb (002, main), kyc (Thalos-Infrastructure#80) and retry-queue (Thalos-Infrastructure#103) migrations.
- Tighten authorization on the compliance endpoints (IDOR fix): reads now
  require the caller to be the subject (users), an admin, or an internal
  service. Adds JwtOrInternalSecretGuard (JWT or x-thalos-internal-secret) and
  VerificationService.assertCanRead; unit tests cover self/admin/internal/denied.
- Document the single-source-of-truth model in the README: verifications is a
  read-only projection that KYC/KYB writers must upsert into, so Thalos-Infrastructure#71/Thalos-Infrastructure#72/Thalos-Infrastructure#75
  don't invent another status shape.
@Kalchaqui

Copy link
Copy Markdown
Collaborator

Re-review — LGTM

Thanks @josueazc3ddcfb3 addresses the three blockers from the prior review cleanly.

Verified

  1. Migration002_create_verifications.sql removed; now scripts/004_create_verifications.sql (avoids collision with 002_create_kyb_verifications on main; leaves room for feat(agreements): extend activity logging with previous/new state + dispute events (#61) #104’s 003 activity columns).
  2. AuthzJwtOrInternalSecretGuard + assertCanRead:
    • user: self or admin or x-thalos-internal-secret
    • business: admin or internal only (no org-membership self yet — documented, OK for now)
    • IDOR covered by unit tests (self / other user / admin / internal / business reject)
  3. SSoT contract — README documents that kyb_verifications / KYC remain systems of record and must sync terminal statuses into verifications. Clear for Design Identity Provider Abstraction Layer #71 / Integrate KYC Verification Provider #72 / Build KYC/KYB Integration Test Suite #75.

CI: Test / Lint / Build / Format green (Vercel fail expected on this Nest repo). Able to merge.

Maintainer checklist on merge

  • Apply scripts/004_create_verifications.sql on shared Supabase before hitting /v1/verification/*.
  • Until KYB/KYC writers sync into verifications, the API correctly fail-closes to unverified — expected.

Optional follow-ups (non-blocking)

  • When org membership exists, allow business “self” (owner/member) to read KYB status without admin.
  • Follow-up PRs on KYB/KYC should include the upsert into verifications on terminal status.

Approve / merge from my side.

@Kalchaqui
Kalchaqui merged commit 15692ae into Thalos-Infrastructure:main Jul 23, 2026
6 of 7 checks passed
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.

Create Verification Status & Compliance API

3 participants