Registration & KYC -- Conformance Spec
How to Use This
- 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.
- Each requirement (REQ-xx) is a behavioural rule + Given/When/Then accept-criteria + references + a status.
- The master decision table (section 3) is the coverage backbone — the exhaustive (design x action x amount) → outcome grid the requirements cite.
- Consumer contracts (section 2) (C-xx) are the obligations of each surface that shows or acts on the state.
- The coverage map (section 6) ties every known defect 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.
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 |
Usability is one binary axis — held vs usable (an open card is simply never held) — but "held" decomposes into verification sub-states.
Verification sub-states — "Pending" is not one state
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 | Terminal -- the card is now Active & usable |
Consumer Contracts
What each surface that shows or acts on the verification state MUST do. Behavioural — encoding is the design's choice.
C-STATE
The platform makes a card's verification state distinguishable — the held/usable fact on the card, the precise reason from the verdict authority on demand.
- 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.
- No two fields or endpoints may report conflicting answers to "does this need verification."
C-FUNNEL
The cardholder site holds back a verification-required card and offers the path to complete it.
- 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.
- When the card is verified Then it behaves like an ordinary usable card.
C-ADMIN
SetldHub shows that a card is held and why, and never presents not-yet-loaded funds as spendable.
- 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.
C-VERDICT
The compliance authority is the single source of "is verification satisfied," answered live at money-movement.
- 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.
- If the verdict is unavailable (authority down or times out), the operation fails closed — no release, no money.
Master Decision Table
The exhaustive backbone. Every requirement cites these rows.
| # | 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 |
| 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 |
| R7 | kyc | activateWithLoad | A (over band) | Pending Verification | defer A | held + verification path; 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 | deferred A | Active & usable | load A -- once | 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. SECURITY = must-fix invariant.
A -- Activation
| ID | Rule | Status |
|---|---|---|
| REQ-A1 | An open-design card is usable immediately on activation; bare activate moves no money. | SETTLED |
| REQ-A2 | A verification-required card is held after activation -- Pending Verification, no money moved -- unless the linked holder is already verified. | SETTLED |
| REQ-A3 | A pending card's held state and reason are visible to every consumer. | SETTLED |
| REQ-A4 | Re-activating a card never disturbs its state or re-triggers funding. | SETTLED |
| REQ-A5 | Bare activate on a fund-after-verification design -- behaviour pending decision. | OPEN-1 |
B -- Loading & Funding
| ID | Rule | Status |
|---|---|---|
| REQ-B1 | A load on a usable card checks funds, then funds the card now. | SETTLED |
| REQ-B2 | An activate-with-load on a verification-required card defers the load; no money moves until verification. | SETTLED |
| REQ-B3 | No path commits a load -- now or deferred -- without a balance check where the money actually moves. | SETTLED |
| REQ-B4 | An initial load funds the card exactly once. | SETTLED |
| REQ-B5 | Loading a card that is still Pending -- behaviour pending decision. | OPEN-2 |
| REQ-B6 | A later load that crosses into a higher KYC band -- re-gate vs proceed, pending decision. | OPEN-3 |
C -- Release / Verification
| ID | Rule | Status |
|---|---|---|
| REQ-C1 | A verification event releases the card: it becomes usable and any deferred funds load -- exactly once. | SETTLED |
| REQ-C2 | No entry path can strand a card -- every Pending card has a working route to usable. | SETTLED |
| REQ-C3 | Money moves on release only on a fresh, amount-aware "verified" -- never cached. | SETTLED |
| REQ-C4 | The release / verify operation is a privileged capability, authorised against the card's owning partner and the named holder. | SECURITY |
| REQ-C5 | Verification fails or never arrives -- fate pending decision. | OPEN-4 |
| REQ-C6 | A verification failure leaves the card held -- only a success releases it, so a retry recovers normally. | SETTLED |
D -- Replacement / Transfer
| ID | Rule | Status |
|---|---|---|
| REQ-D1 | A replacement carries the source's usability and funding state to the new card; the source is retired; no re-verification. | SETTLED |
| REQ-D2 | A pending source carries its Pending state and deferred funds to the replacement, releasing on verification; funded exactly once across the move. | SETTLED |
E -- Consumer Signal & Display
| ID | Rule | Status |
|---|---|---|
| REQ-E1 | A held card is exposed as needing registration and/or KYC -- never as "no verification required." | SETTLED |
| REQ-E2 | The cardholder registration path fires for every verification-required, not-yet-verified card -- reg-only included. | SETTLED |
| REQ-E3 | SetldHub labels the held reason correctly and displays a held reg-only card the same as any held card. | SETTLED |
| REQ-E4 | Every "requires verification" signal derives from one source -- multiple emitted fields are allowed, but they can never disagree. | SETTLED |
F -- Group Activation
| ID | Rule | Status |
|---|---|---|
| REQ-F1 | Group activation applies the per-card rules to every card uniformly. | SETTLED |
| REQ-F2 | A verification-required batch moves no money up front; it validates the total deferred amount, then funds each card at its own verification. | SETTLED |
G -- Safe Migration
| ID | Rule | Status |
|---|---|---|
| REQ-G1 | Changing how the verification state is encoded rolls out with no window where a card is mis-displayed. | SETTLED |
Open Decisions
Each carries both candidate outcomes so it becomes a hard test the moment it's decided.
| ID | Question | Candidates | Lean |
|---|---|---|---|
| OPEN-0 | The release operation -- split it, and who may invoke it | Split into dedicated release vs inquiry; or keep overloaded with role gating | Split. -> design + Grif |
| OPEN-1 | Bare activate on a fund-after-verification design | Require activateWithLoad; or allow bare activate; or program-default amount | Need: the real partner flow. -> Grif |
| OPEN-2 | Loading a card that is Pending Verification | Reject ("card pending verification"); or defer/extend | Lean: reject, for a clear contract |
| OPEN-3 | Does a later load re-gate an already-usable card? | Re-gate -- hold the crossing load; or proceed -- banding applies only to initial | Need: compliance intent. -> Grif |
| OPEN-4 | Verification fails or never arrives | Stay pending indefinitely; expire/cancel after a window; partner-driven action | Need: product + compliance. -> Grif |
| OPEN-5 | Retire the dead fund-timing flags? | Ignore + document now; or keep | Lean: retire (cross-schema, core-owned) |
| OPEN-6 | What a held card displays -- balance & deferred amount, per surface | Mask the balance; show zero + deferred; differ by surface | Need: UX / product. -> Grif |
| OPEN-7 | Deferred load, but funds short at release | Release usable, load fails + notify; or hold the load for retry | Need: product. -> Grif |
Coverage Map
Every known bug mapped to the requirement that catches it. If a requirement here is met, the listed defect cannot recur.
| 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 |
| 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 + REQ-C2 + REQ-E1/E2/E3 |
| 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.
- Defined, non-leaking error outcomes. Every reject has a defined outcome that leaks no internals.
- Funded-once is keyed. The fund-once guarantee rests on an idempotency key — keyed per deferred-record, honoured for the record's life, and surviving a card-id change across replacement.
- Single-currency. A deferred load, an extend, and a replacement are single-currency — a currency mismatch is rejected, not silently converted.
- Detectable. A card entering a failed / stranded sub-state, an unfunded-at-release, or a spike in denied releases raises an alert.
- Security beyond REQ-C4. Authorise the inquiry / screening branch too (PII); scope the verdict read; treat the person id in a release as authorisation-relevant; the release nudge tolerates replay 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.