Registration & KYC -- Conformance Spec
How to Use This
What a card program must do when it requires the cardholder to register and/or pass KYC before a card can be used — written as a conformance checklist. Each requirement is observable behaviour with explicit pass/fail criteria, so you can walk any proposed fix against it and know whether it's complete. Requirements are stated at the level of what the holder, the partner and the operator see — never in terms of today's field names (those sit in small as-built notes for traceability). SETTLED requirements are testable now; OPEN ones carry both candidate outcomes and convert to tests the moment a decision lands.
- To validate a fix: walk it against every settled requirement's accept-criteria. For each open requirement, confirm the fix implements the branch the decision picked. If all pass, the behaviour is complete.
- Each requirement (REQ-xx) is a behavioural rule + Given/When/Then accept-criteria + references (decision-table rows · consumer contracts) + a status.
- The master decision table (§03) is the coverage backbone — the exhaustive (design × action × amount) → outcome grid the requirements cite. Consumer contracts (§02) (C-xx) are the obligations of each surface that shows or acts on the state.
- The coverage map (§06) ties every known defect and live card to the requirement that catches it — so this list is a regression guard for the real bugs, not an aspiration.
- Altitude: requirements describe behaviour, not encoding. "Show the card as held and offer registration" is the requirement; which field carries it is a design choice (see the design).
- Beyond behaviour: §05 flags which open decisions are ⛔ design-blocking (must be answered before a design freezes); §07 lists the non-functional invariants (audit, errors, security, migration) the design must honour.
Model & Vocabulary
Four concerns, kept separate. Each can be reasoned about and reported on its own.
| concern | the question | determined by |
|---|---|---|
| Verification requirement | Must the holder register / pass KYC before this card is usable? | the design: requires registration and/or KYC, keyed per (program, design) |
| Usability state | Is this card usable yet? | the card: held (in a verification sub-state) vs usable |
| Funding timing | Does the initial load land at activation, or at verification? | follows usability — deferred while Pending, immediate when usable |
| Consumer signal | What does each surface (holder, operator) see and do? | the card's state + reason — see the consumer contracts (§02) |
Usability is one binary axis — held vs usable (an open card is simply never held) — but "held" decomposes into the verification sub-states below.
A held card sits in exactly one of these. axis enforces only the binary (held vs usable); the sub-state — the reason — is the verdict authority's (core's), surfaced to consumers. None of these are processor card-statuses.
| sub-state | means | enforced as | consumers show | exit → |
|---|---|---|---|---|
| Awaiting registration | activated & held; the holder hasn't registered the card | held | held; offer the registration path | registers → Awaiting KYC (KYC design) or Verified (reg-only); fails → Registration failed |
| Registration failed | the holder attempted registration and it was rejected | held | held; reason = registration failed | OPEN-4 |
| Awaiting KYC | registered; required KYC not yet satisfied | held | held; awaiting KYC | passes → Verified; fails → KYC failed |
| KYC failed | the holder attempted KYC and failed | held | held; reason = KYC failed | OPEN-4 |
| Verified | registration (+ any required KYC) satisfied | usable | usable; deferred funds load once (or short → OPEN-7) | terminal — the card is now Active & usable |
Reg-only designs never enter Awaiting KYC / KYC failed. Open designs enter none of this (activate → usable). A card that simply never progresses — the holder never acts — is the "never arrives" half of OPEN-4, distinct from an explicit failure. A failure leaves axis's held state unchanged — "failed" is the verdict authority's reason, read on demand (C-STATE), never pushed to axis; only a success (the release event) flips the card usable, so a retry just succeeds through the normal release (REQ-C6).
Consumer Contracts
What each surface that shows or acts on the verification state MUST do. Behavioural — encoding is the design's choice. Referenced by the requirements as C-xx.
- From the card a consumer can tell: open vs held vs usable; that it needs registration and/or KYC; and the deferred amount if any.
- The precise sub-state — awaiting-registration vs registration-failed vs awaiting-KYC vs KYC-failed — is the verdict authority's (core's), obtainable on demand. It is not all required on the card object; the card need only carry enough to be held correctly and route the holder.
- No two fields or endpoints may report conflicting answers to "does this need verification."
requiresKyc / kycLocked / deferredLoadAmount — KYC-only, so even "held + needs registration" is wrong today (DEV-890); a second, conflicting field exists (DEV-842/885); the failed sub-states aren't surfaced at all.- Given a card whose design needs registration and/or KYC, not yet verified When the holder views it Then it is shown as held and the holder is offered the registration / verification path.
- This fires for registration-only designs as well as KYC designs — both are "verification required."
- When the card is verified Then it behaves like an ordinary usable card.
- A pending card shows as held with the reason — awaiting registration, awaiting KYC, or both.
- Deferred (not-yet-loaded) funds are never shown as a spendable/available balance. Beyond that — show the real (zero) balance, mask it, how prominently to surface the deferred amount, whether SetldHub differs from the cardholder site — is an open display choice (OPEN-6).
- Funds move only on a current "verified for this holder and this amount" — never a cached verdict.
- The verdict is amount-aware: a load needing a deeper level than the holder holds does not pass.
- For a registration-only design there is no KYC level — "verified" means registration is confirmed; the amount-band check applies only where the design requires KYC.
- If the verdict is unavailable (authority down or times out), the operation fails closed — no release, no money — never fail-open.
Master Decision Table
The exhaustive backbone. Every requirement cites these rows. "—" = not applicable. Open cells defer to §05.
| # | design | action | amount | usability after | funds | consumers must show |
|---|---|---|---|---|---|---|
| R1 | open (reg f, kyc f) | activate | — | Active & usable | none | ordinary card |
| R2 | open | activateWithLoad | A | Active & usable | load A now | ordinary card |
| R3 | reg-only (reg t, kyc f) | activate | — | Pending Verification | none | held + registration path; admin "registration required: yes" |
| R4 | reg-only | activateWithLoad | A | Pending Verification | defer A | held + registration path; deferred A recorded, not shown as spendable (display → OPEN-6) |
| R5 | kyc (kyc t) | activate | — | Pending Verification | none | held + verification path |
| R6 | kyc | activateWithLoad | A within band | Pending Verification | defer A | held + verification path; deferred A recorded (display → OPEN-6) |
| R7 | kyc | activateWithLoad | A over band | Pending Verification | defer A | held + verification path (KYC); deeper level required before funds land |
| R14 | verification-required, holder already verified | activate / activateWithLoad | (A) | Active & usable | none / load A now | ordinary card (re-issue / replacement) |
| R8 | verification-required | verify / release OPEN-0 | deferred A | Active & usable | load A — once (or short → OPEN-7) | usable; balance shown |
| R9 | usable (verified / open) | load | A | Active & usable | load A now | ordinary card |
| R10 | pending | load | A | OPEN-2 | reject — or extend deferred | per decision |
| R11 | usable | load | A over band | OPEN-3 | re-gate — or proceed | per decision |
| R12 | pending source | replace / transfer | deferred | new Pending, source retired | carry deferred — once | held carries to the new card |
| R13 | usable source | replace / transfer | balance | new usable, source retired | move balance — once | ordinary card |
Requirements
Behavioural rules with pass/fail criteria. SETTLED = testable now · OPEN-n = pending a decision (§05) · SECURITY = must-fix invariant.
- Given an open design When
activateThen the card is Active & usable and no funds move.
- Given a design needing registration and/or KYC, holder not yet verified When
activateThen the card is Pending Verification (suspended at the processor — the real enforcement) and no funds move. - True for registration-only designs, not just KYC.
- Exception: if the holder is already linked and verified for this design (a re-issue / replacement), the card activates directly to usable — no hold. The hold is for unverified holders only.
- Given a Pending card When any consumer reads it Then it can tell the card is held and why (registration / KYC / both).
- Given an already-activated card (usable or held) When
activateThen its state and funds are unchanged. Whether the call errors or returns an idempotent success is an API choice, not fixed here.
IllicitModificationException — the reject-vs-idempotent shape was assumed, not specified.- With no amount, a fund-after-verification design has nothing to load when it becomes usable. See OPEN-1 for the candidate outcomes.
- Given a usable card When
load AThen a balance check runs and, if it passes, A lands on the card.
- Given a verification-required design, holder not yet verified When
activateWithLoad AThen the card is Pending, A is recorded as the deferred load, and no money moves. - Exception: an already-verified holder activates-and-loads now (no defer), like an open card — REQ-A2's carve-out on the load path.
- A card holds one deferred-load record (amount and currency). If OPEN-2 resolves to "extend", extension updates that one record — so "funded exactly once" (REQ-B4) is once in total, not once per parked entry.
- An immediate load is checked at request time; a deferred load is checked at verification, before the funds move.
- A deferring request MAY also validate fundability up front as an early signal, but the binding check is at fund-movement.
- If the at-verification check fails (funds short by release time) Then the shortfall is a reported outcome — never a silent drop and never a strand; the card is still verified. Exact handling (release-unfunded-and-notify vs hold-the-load) is OPEN-7.
- Given a card with a deferred load When a verification/release signal arrives more than once (retry, duplicate, concurrency) Then the card is funded once and only once.
- Concurrent operations resolve to one outcome: a release racing a
loadfunds once (not both); a release racing a replacement funds the destination once. (The locking / atomic-claim mechanism is §07.)
- A load must never succeed without the funds reaching the card. Reject vs extend-the-deferred is OPEN-2.
- If a load pushes a usable card into a higher amount band, must it re-verify first? OPEN-3.
- Given a Pending card When registration (and any required KYC) completes Then the card becomes usable and the deferred load lands, once.
- Usability flips on the same trust as the money: an authorised release (REQ-C4 / OPEN-0) confirmed against the live verdict — not on any inbound signal. A stale or spoofed nudge makes the card neither usable nor funded.
- Given a card made Pending by any path (plain
activate,activateWithLoad, group, transfer) When verification completes Then it releases cleanly — never a permanent suspend.
- Given a release/verify signal When funds would move Then the compliance authority is asked, live, "verified for this holder and amount?" — a stale/replayed/wrong signal funds nothing.
- Given a release call (the legitimate caller is a cross-partner orchestrator) When it names a card + a holder Then the card is resolved and authorised against the card's owning partner / the holder's entitlement — quoting an arbitrary card id or person id must not release another holder's card.
- It is a privileged role (the release orchestrator only), not the broad partner role — and "caller's own scope" is the wrong test, since the legitimate caller owns no partner scope.
- A card sits Pending; the holder fails or never completes. Stay pending / expire / partner-driven — and the held-card expiry-anchor interaction — is OPEN-4.
- A failure does not change axis's state — the card stays held. "Failed" is the verdict authority's reason, read on demand (C-STATE); axis is never pushed a failure verdict and caches none.
- Given a held card When the holder retries and verification succeeds Then the normal success release (REQ-C1) flips it usable — failure is not terminal (its terminal fate is OPEN-4).
- Given a usable source with a balance When replaced Then the balance moves to the new card (usable); the holder is already verified, so no re-verification.
- Given a Pending source (deferred, never funded) When replaced Then the Pending state + deferred load carry to the new card; a retried/concurrent move must not fund the destination twice.
- Given a registration-only held card When a consumer reads it Then it sees the card needs registration — not "no verification required." (The finer awaiting-vs-failed sub-state comes from the verdict authority — C-STATE.)
- Given a held reg-only card When the holder visits the cardholder site Then they are offered the registration path.
- Given a held reg-only card When SetldHub shows it Then it reads "registration required" and is displayed like a held KYC card — deferred funds not shown as spendable.
- Given any card When two fields or endpoints report whether it needs verification Then they agree, because both derive from a single source of truth. (Derived fields may persist for compatibility — this does not mandate collapsing to one field; they must just never diverge.)
- Given a group activation — a batch is a single design (enforced precondition) — When activated Then every card follows the same per-card rules above (all-open or all-verification-required).
- Given a verification-required batch When activated Then no funds move up front; the total deferred amount is validated up front (advisory — the binding check is per-card at each card's release, REQ-B3); per-card funding happens at each card's verification.
- Partial failure: if the up-front total check fails, the whole batch is rejected (no card activates). If a card fails mid-batch, its outcome is independent and reported — a partial failure neither strands the rest nor silently drops the failures.
- Given a change to the signal encoding When it ships Then consumers tolerate old and new and are deployed first (expand/contract) — at no point does a card show the wrong held/usable state.
- Assumes every branching consumer is internal — pending the external-consumer investigation (the open Investigate card).
Open Decisions
Each carries both candidate outcomes so it becomes a hard test the moment it's decided. ◆ = needs product / Grif · ⛔ = design-blocking (a design can't freeze until it's answered); unmarked ones are safely parameterised. The design §08 tracks the same set — numbering isn't 1:1 (it carries OPEN-0; this adds OPEN-6/7).
The release operation — split it, and who may invoke it
"Release" is today an overloaded endpoint (a single verifyIndividual branched on a person id: with it → release = activate + load; without → a KYC inquiry/screening that moves no money). It is reachable by two legitimate callers — core-service and card-balance-api (forwarding the cardholder) — neither of which is the owning partner. The whole release contract (REQ-C1–C4) rests on resolving this.
Bare activate on a fund-after-verification design
No amount, so a fund-after-verification design has nothing to load when it becomes usable.
activateWithLoad — amount captured at activation, deferred.Loading a card that is Pending Verification
What does load do when the target isn't usable yet? (It must never succeed without funds reaching the card.)
Does a later load re-gate an already-usable card?
A load that pushes a verified card into a higher amount band — re-verify first, or not?
Verification fails or never arrives
A card sits in Registration failed, KYC failed, or simply never progresses (the holder never acts). What is its fate — and what do we tell the partner? The explicit-failure and never-arrives cases may warrant different answers.
Retire the dead fund-timing flags?
Funding follows usability; the per-design fund-timing flags have no readers in any service.
What a held card displays — balance & deferred amount, per surface
A Pending card has no spendable funds yet (real available ≈ 0) and a deferred amount to come. The settled rule is only: never present the deferred amount as spendable, and show reg-only the same as KYC. Beyond that, what should each surface actually show? (Wrongly asserted as settled in an earlier draft — it isn't.)
***), surface the deferred amount.Deferred load, but funds short at release
Verification completes (the card is now usable), but the funding source can't cover the deferred load (parked for days/weeks). Settled invariant: the card becomes usable and the shortfall is a reported outcome — never a strand or a silent drop (REQ-B3). What then?
Coverage Map
If a requirement here is met, the listed defect/card cannot recur. This is what makes the list a regression guard.
| defect / card | what it is | caught by |
|---|---|---|
| D1 | activate strands a verification-required card (no release route) | REQ-C2 |
| D2 | pure load on a locked card defers into a dead end | REQ-C2, REQ-B2 |
| D3 | locked designs skip the up-front balance check | REQ-B3 |
| D4 | the consumer signal reports KYC-only, hiding registration-only locks | REQ-E1, REQ-E2, REQ-E3 |
| D5 | dead fund-timing flags | OPEN-5 |
| D6 | design-config lookup ignores soft-delete / keys on design alone | Rule 0 (keyed per (program, design)) |
| DEV-890 | reg-only cards lock with no path out (funnel never fires) | REQ-C2, REQ-E1, REQ-E2, REQ-E3 |
| DEV-823 | lock applied to reg-only — but only lock-ON, never lock-OFF | REQ-A2 (on) + REQ-C2 (off) + REQ-E1/E2/E3 (reg-only surfaced) |
| DEV-842/885 | kyc-level API returns conflicting requiresKyc/kycRequired | REQ-E4 |
| stranded cards | prod cards held with no release row | REQ-C2, REQ-B4 |
| release IDOR | cross-tenant release/fund by card id | REQ-C4 |
Non-Functional Invariants
Stated as invariants (what must hold), not mechanism (how). The design picks the how.
- Auditable. Every state transition and every release attempt — allowed and denied — is recorded with the caller's identity. Given the IDOR history, denied releases especially.
- Defined, non-leaking error outcomes. Every reject has a defined outcome that leaks no internals: load-on-pending, release-not-authorised, release-of-unknown-card, verdict-not-satisfied, funds-short-at-release. (The repo's
RestServletcurrently leaks raw exception detail — the unhappy path is security-relevant.) - Funded-once is keyed. The fund-once guarantee (REQ-B4 / D2) rests on an idempotency key — keyed per deferred-record, honoured for the record's life, and surviving a card-id change across replacement. The key's existence + scope are required here; its encoding is design.
- Single-currency. A deferred load, an extend (OPEN-2), and a replacement are single-currency — a currency mismatch is rejected, not silently converted (FX is out of scope here).
- Detectable. A card entering a failed / stranded sub-state, an unfunded-at-release, or a spike in denied releases raises an alert — so the next strand is caught, not found months later.
- Security beyond REQ-C4. Authorise the inquiry / screening branch too (PII); scope the verdict read (axis→core person lookup); treat the person id in a release as authorisation-relevant, not a free parameter; the release nudge tolerates replay (a stale nudge moves nothing — REQ-C3) and is rate-limited.
- Existing state & data are migrated. The fix must remediate the already-stranded prod cards and migrate the existing parked-load rows into the new record shape — not only prevent recurrence (REQ-C2) and roll the signal out safely (REQ-G1). State + data migration, not just code.
Genuinely design-phase (named, not specified here): the idempotency-key mechanism; transaction isolation / locking for the concurrency invariants (REQ-B4 + release-vs-load and release-vs-transfer races); the Tribe ≤6-concurrent cap on batch funding; and the latency a live verdict call adds to every fund-movement.