Career OS

Case Study — The Whole Assure PAT System

This is the capstone. Six pages of theory, now spent on one real system you have access to every day: Assure PAT. We draw the entire platform using the C4 zoom-levels method — Context → Container → Component — then trace the real business flow end to end. By the end you should be able to sit at a whiteboard and redraw all of it from memory. That single skill is what separates “I work on the codebase” from “I understand the system.”

⏱ ~25 min · 🔴 Advanced · Prerequisites: pages 1–6 (especially the C4 model on the Start Here page)

🎯 What you’ll be able to do

  • Draw Assure PAT at three zoom levels and pick the right one for any conversation
  • Narrate the full request → approve → tap → review flow without notes
  • Explain why the boundaries are where they are (multi-tenancy, three token types, serverless)
  • Use this page as a map back into the real code when you get lost

1. What is Assure PAT, and what problem does it solve?

Payment cards that use Discover D-PAS contactless (tap-to-pay) have to behave exactly right when they touch a real terminal — the cryptogram, the counters, the EMV tags all have to be correct, or the card is rejected in the field. You cannot find that out after you ship a million cards. Assure PAT is a multi-tenant platform that lets card issuers test that behaviour before production. Ayris Global onboards each customer (an organization), who sets up their issuers, BINs and card profiles; a card request flows through approval; a tester then loads the profile onto an Android phone that emulates the card over NFC (Host Card Emulation), taps a real terminal, and the phone captures the full EMV/APDU transaction. Everything captured comes back to the dashboard where it is reviewed and any failures are tracked as issues. One platform, many organizations, all isolated from each other — that “many tenants, one system” property drives almost every design decision below.


2. Level 1 — Context (🛰 satellite view)

The Context diagram answers one question: who and what touches the system, from the outside? No internals — just Assure PAT as a single black box, the people who use it, and the external things it depends on.

flowchart TB
    subgraph ext["External systems"]
        Terminal["Real payment terminal<br/>(POS / contactless reader)"]
        AWS["AWS Cloud<br/>(Lambda · API GW · RDS · S3 · KMS)"]
    end

    subgraph people["People"]
        SysAdmin(["Ayris Global<br/>System Admin"])
        OrgUser(["Org users<br/>SME/Admin · Approver · Manager · Profile Editor"])
        Tester(["Mobile tester<br/>(field)"])
    end

    PAT["Assure PAT<br/>Payment-card testing platform"]

    SysAdmin -->|"onboards orgs, manages keys & cards"| PAT
    OrgUser -->|"sets up issuers, profiles, requests; reviews results"| PAT
    Tester -->|"loads a card profile, taps terminals"| PAT
    Tester -->|"NFC tap (HCE emulates the card)"| Terminal
    PAT -->|"runs on"| AWS

How to read it. The three pill shapes are actors (people), the box is the system, the right-hand cluster is external systems. Narrate it aloud: “Ayris admins onboard organizations and manage crypto keys; org users set up the test data and review results; a mobile tester loads a profile and taps a real terminal, and the whole platform runs on AWS.” Notice that the terminal is not part of Assure PAT — it’s a real-world device the mobile app interacts with. That boundary is the entire point of the product: it tests how the emulated card behaves against real hardware.

Why so many kinds of user?

Assure PAT has two completely separate worlds of people: Ayris Global staff (system admins) who run the platform, and the customers’ own staff (org users), who are further split into six roles. That split is enforced all the way down with three different JWT token types — we’ll see it again at the component level.


3. Level 2 — Container (✈️ airplane view)

The Container diagram answers: what are the separately deployable/running pieces, and how do they talk? “Container” here means a runnable thing (an app, an API, a database) — not a Docker container specifically. Now we open the black box from level 1.

flowchart TB
    Tester(["Mobile tester"])
    OrgUser(["Org user"])
    SysAdmin(["Ayris admin"])

    subgraph clients["Clients"]
        Web["Web Dashboard<br/>React 18 + Vite"]
        Admin["System Admin Panel<br/>(same React app, /system/*)"]
        Mobile["Mobile App<br/>Android / Kotlin · NFC HCE"]
    end

    subgraph aws["AWS Cloud"]
        APIGW["API Gateway"]
        API["Express 5 API<br/>(Lambda via serverless-http)"]
        DB[("MySQL 8 'assurepat'<br/>on RDS")]
        S3[("S3<br/>files / artifacts")]
        KMS[("KMS<br/>crypto keys")]
    end

    Terminal["Real payment terminal"]

    OrgUser --> Web
    SysAdmin --> Admin
    Tester --> Mobile

    Web -->|"HTTPS / REST"| APIGW
    Admin -->|"HTTPS / REST"| APIGW
    Mobile -->|"HTTPS / REST (uploads taps)"| APIGW
    Mobile -.->|"NFC tap"| Terminal

    APIGW --> API
    API -->|"Knex / SQL"| DB
    API -->|"presigned URLs"| S3
    API -->|"per-user keys"| KMS

How to read it. Three clients on top, the cloud in the middle, the terminal off to the side. Every client speaks HTTPS/REST through API Gateway — there is one front door. Behind it, the Express API does all the thinking and is the only thing that touches the data stores. That “API is the single gatekeeper of data” rule is what keeps a multi-tenant system safe.

ContainerTechResponsibility
Web DashboardReact 18 + Vite + BootstrapOrg users (roles 1–6) manage issuers, profiles, requests, cards, issues; review captured transactions.
System Admin PanelSame React app, /system/* routesAyris staff onboard orgs, manage partners, generate cards, manage crypto keys.
Mobile AppAndroid / Kotlin, NFC HCEEmulates a Discover contactless card from a profile, taps terminals, captures EMV/APDU, uploads results.
API GatewayAWS API GatewaySingle HTTPS front door; routes requests to the Lambda.
Express APIExpress 5 + Knex, on Lambda via serverless-httpAll business logic, auth, tenant isolation, validation; the only thing that reads/writes data.
MySQL 8RDS, schema assurepatSystem of record. Every table carries org_id.
S3AWS S3Files and artifacts — issue attachments, receipts, APDU log files.
KMSAWS KMSManages the per-user / per-issuer encryption keys for card data.

The serverless detail that trips people up

The Express app doesn’t run on a server that’s “always on.” serverless-http wraps the whole Express app so it runs inside a Lambda function, invoked per-request by API Gateway. To you as a developer it’s still just Express with routes and controllers — but there’s no long-lived process, which matters for things like in-memory caches and DB connection pooling.


4. Level 3 — Component (🚗 street view)

The Component diagram answers: inside one container, what are the modules and how does a request move through them? We zoom into the Express API — the most interesting container. This is the layered architecture from page 2, made concrete.

flowchart TB
    APIGW["API Gateway"] --> Routes["Routes<br/>(/api/v2/...)"]

    subgraph mw["Middleware chain (runs in order)"]
        direction TB
        VT["verifyToken<br/>(JWT: org / mobile / system)"]
        ET["extractTenant<br/>(org_id + org must be active)"]
        VR["validateRequest<br/>(schema check)"]
        RA["checkRoleAccess<br/>(186 role mappings)"]
        VT --> ET --> VR --> RA
    end

    Routes --> mw
    RA --> Ctrl["Controllers"]
    Ctrl --> Svc["Services<br/>(business logic)"]
    Svc --> Models["Models"]
    Models --> Knex["Knex query builder<br/>(scopedQuery adds WHERE org_id = ?)"]
    Knex --> DB[("MySQL 8")]
    Svc -.->|"files"| S3[("S3")]
    Svc -.->|"keys"| KMS[("KMS")]

How to read it. A request enters at a route, then runs the middleware chain in order: is the token valid (verifyToken)? which tenant is this and is it active (extractTenant)? does the body match the schema (validateRequest)? is this role allowed on this route (checkRoleAccess)? Only if all four pass does it reach a controller (HTTP shape), which calls a service (the actual logic), which calls a model, which builds SQL with Knex — and scopedQuery quietly bolts WHERE org_id = ? onto every query so one tenant can never see another’s data.

The major functional modules inside this same skeleton:

Module groupWhat it owns
AuthLogin, OTP, password reset, the three token types, T&C acceptance.
UsersOrg user CRUD, roles via user_org_roles, login history, mobile debug logs.
OrganizationsTenant records, status, allowed email domains, config.
Cards / Issuers / BINsIssuers (35+ encrypted crypto columns), BINs, encrypted cards, assignment.
Profiles & TemplatesD-PAS EMV profiles (the core), system + org templates, versioning, clone.
Card RequestsThe approval workflow — the wizard and its status lifecycle.
Test CasesWhat to test (terminal type, scope, PIN capability), V2 validation rules.
IssuesBug tracking with comments, attachments (S3), tags, history, timeline.
NotificationsWeb / mobile / inbox announcements with a draft→approve flow.
Audit TrailsEvery INSERT/UPDATE/DELETE across all tables, with before/after diffs.

The three tokens, one chain

verifyToken is really three checks: an org token (bhtoken, web users), a mobile token (the app), and a system token (systemToken, Ayris admins). Org and system tokens are mutually exclusive — logging in as one clears the other. Same middleware chain, three identities.

Before you move on, prove you can actually read this architecture — not just nod at it. Each question below points at one layer, boundary, or design choice you just saw.


5. The business flow, end to end

Now stitch the levels together with the real workflow. Watch the card_requests status lifecycle drive it: draft → submitted → approved → assign_card → shipped → completed.

flowchart TB
    A["Ayris onboards an org<br/>(issuers seeded, cards generated)"] --> B["Org sets up<br/>issuers · BINs · profiles"]
    B --> C{"Card Request wizard"}

    subgraph wiz["Wizard steps"]
        direction TB
        C1["1 · Requester details"]
        C2["2 · Terminal info<br/>(terminal_type + brands)"]
        C3["3 · Profile selection"]
        C4["4 · Test case selection"]
        C5["5 · Card assignment"]
        C6["6 · Submit"]
        C1 --> C2 --> C3 --> C4 --> C5 --> C6
    end

    C --> wiz
    C6 -->|"status: submitted"| D["SME / Approver reviews"]
    D -->|"approve → status: approved"| E["Cards assigned<br/>(status: assign_card)"]
    D -->|"reject (with comments)"| B
    E -->|"status: shipped"| F["Mobile tester loads profile<br/>taps real terminal (NFC HCE)"]
    F --> G["Phone captures EMV / APDU<br/>+ debug logs"]
    G --> H["Uploaded via API Gateway"]
    H --> I["Reviewed in dashboard<br/>(Transactions module)"]
    I -->|"failures"| J["Issues tracked<br/>→ resolved"]
    I -->|"all good → status: completed"| K(["Done"])

Read it as a story. Ayris onboards an organization and seeds it with issuers and generated cards. The org’s profile editors build the D-PAS profiles. An approver opens the Card Request wizard — requester details, then terminal info (terminal_type plus the card brands), then which profile to test, which test cases to run, which card to assign, then submit. An SME/Approver reviews; on approve, cards are assigned and the request moves toward shipped. A mobile tester loads the assigned card’s profile, the phone emulates it over NFC and taps a real terminal, capturing the EMV/APDU data and debug logs. That upload flows back through API Gateway, lands in the Transactions view for review, and anything that failed becomes a tracked issue. Clean run → completed.

Where a profile gates the request

A request can’t even be created for a feature unless an active profile exists for that feature/product/issuer (isActiveProfileAvailable()). The profile must be ready before the request — the workflow enforces it up front, not at assignment time.


6. Data flow — the “tap capture” path

The single most distinctive path in the whole system: a physical NFC tap turning into a reviewable row. This is a focused data-flow view of step F→I above.

sequenceDiagram
    participant App as Mobile App (HCE)
    participant Term as Real terminal
    participant GW as API Gateway
    participant API as Express API (Lambda)
    participant DB as MySQL 8
    participant S3 as S3

    App->>App: load card profile (JSON) → emulate card
    App->>Term: NFC tap (APDU exchange)
    Term-->>App: terminal APDUs (amount, TTQ, ...)
    App->>App: capture EMV (AC, CID, ATC, IAD, Track2 ...) + debug logs
    App->>GW: HTTPS POST transaction JSON
    GW->>API: invoke Lambda
    API->>API: verifyMobileUserToken + extractTenant
    API->>DB: INSERT user_card_usage (+ link user_card, card)
    API->>S3: store artifacts (receipt / APDU log file)
    API-->>GW: 200 OK
    GW-->>App: 200 OK
    Note over DB,S3: Dashboard Transactions module reads<br/>these rows + artifacts for review

How to read it. Time runs downward; solid arrows are calls, dashed are responses. The interesting part is the APDU round-trip with the terminal — the phone, acting as the card, answers the terminal’s commands and records everything. The structured transaction (cryptograms, counters, Track 2) goes to MySQL; the bulky artifacts (receipt images, raw APDU log files) go to S3. The dashboard later reads both to let an SME review the tap.


7. The dashboard modules at a glance

The org dashboard is ~14 modules sharing the same table/sort/search/role skeleton. One line each:

#ModulePurpose
1Request HistoryThe card-request lifecycle list (draft → completed) with status tabs.
2Test Card IssuerManage issuers, their BINs and EMV crypto keys.
3Test Card FulfilmentManager’s view of requests needing approval / assignment / shipping.
4Testing PartnerGlobal & internal partners and the testers that belong to them.
5Manage CardsSearch/view/update encrypted test cards; assign, block, delete.
6Maintain Card InventoryRead-only stock counts by issuer + BIN (available / assigned / total).
7TransactionsCaptured taps — amount, status, APDU logs, receipts, map.
8Manage UsersUser CRUD, login history, mobile debug logs, passcode reset.
9Manage AnnouncementWeb/mobile announcements with a draft → submit → approve flow.
10Audit TrailEvery change across all tables with before/after diffs.
11Card ProfileD-PAS EMV profiles — the core; create/edit/clone/archive, assign card.
12Test CasesDefine what to test (terminal type, scope, PIN capability); V2 rules.
13Manage IssuesFull bug lifecycle: new → assigned → in progress → resolved → closed.
14ReportAnalytics — request status, terminal mix, success rate, trends.

The two cross-cutting modules

Audit Trail (10) and Report (14) don’t own a workflow — they sit across every other module. Audit Trail records every write everywhere; Report aggregates reads from everywhere. When you see a module that touches “ALL modules,” that’s your hint it’s cross-cutting, not a peer.


8. This is a living document

Everything above is a snapshot. The real system changes every week — new migrations, new test-case columns, new modules. The always-current source of truth lives in the repo: Assure PAT/system-understanding.md (the HLD + LLD), with Assure PAT/module-map.md for the module detail. When something here disagrees with those files, those files win — go read them, then update your mental model.

Do this, don’t just read it

Close this page and, on paper, redraw the three C4 levels from memory — Context, then Container, then Component. Then redraw the tap-capture sequence. Where you stall is exactly where your understanding is thin; open system-understanding.md or the real code at that spot and patch the gap. Do that twice and you’ll own this system, not just work in it.

Check Yourself

No peeking at the diagrams. The goal is to retrieve and redraw — where you stall is where your understanding is thin.

Name the three C4 zoom levels used on this page, in order, and what question each one answers.

Context (who/what touches the system from outside) → Container (what are the separately deployable pieces and how do they talk) → Component (inside one container, what modules does a request move through). Satellite → airplane → street view.

Draw the Container diagram from memory: the three clients, the single front door, and what sits behind it.

Three clients (Web Dashboard, System Admin Panel, Mobile App) all speak HTTPS/REST to one API Gateway. Behind it: the Express API on Lambda — the only thing that touches data — talking to MySQL 8 (RDS), S3, and KMS. The mobile app also taps the real terminal over NFC off to the side.

List the four middleware in the chain, in order, with the one question each answers.
  1. verifyToken — is the token valid (org / mobile / system)? 2. extractTenant — which tenant, and is the org active? 3. validateRequest — does the body match the schema? 4. checkRoleAccess — is this role allowed on this route? Only if all four pass does it reach a controller.
What are the three token types, who uses each, and which two are mutually exclusive?

Org token (bhtoken, web users), mobile token (the app), system token (systemToken, Ayris admins). Org and system are mutually exclusive — logging in as one clears the other.

Recite the card_requests status lifecycle that drives the business flow.

draft → submitted → approved → assign_card → shipped → completed. A reject sends it back to the org to fix and resubmit.

Trace the layered path inside the Express API from route to database, in one line.

Route → middleware chain → Controller (HTTP shape) → Service (business logic) → ModelKnex (scopedQuery adds WHERE org_id = ?) → MySQL. Services also reach out to S3 (files) and KMS (keys).

In the tap-capture path, which store gets the structured transaction and which gets the artifacts, and why split them?

Structured EMV data (cryptograms, counters, Track 2) → MySQL (queryable). Bulky artifacts (receipts, raw APDU logs) → S3 (cheap blob storage). The dashboard’s Transactions module reads both to review the tap. Split because relational stores are for queryable rows, object storage for large opaque blobs.

Which two dashboard modules are cross-cutting rather than peers, and how do you spot one?

Audit Trail (records every write across all tables) and Report (aggregates reads from everywhere). The tell: a module that touches “ALL modules” sits across the others, it doesn’t own a workflow of its own.


Still Unclear?

Paste any of these into Claude to go deeper:

  • “Walk me through what happens when an org user with the wrong role calls an Assure PAT endpoint — at which of the four middleware does it get rejected, and what does the response look like?”
  • “Explain why a multi-tenant system enforces org_id scoping at the data-access layer (scopedQuery) instead of trusting the API routes or the UI to filter. What goes wrong if it doesn’t?”
  • “Why run the Express API on Lambda via serverless-http instead of an always-on server? Walk me through the trade-offs for connection pooling, cold starts, and cost for a system like this.”

Why AI Can’t Do This For You

AI can describe what a C4 diagram is, but it cannot hold your system in its head — it has never seen the private assurepat schema, the 186 role mappings, or why the terminal sits outside the box. The skill that matters is sitting at a whiteboard and redrawing all three levels from memory while a senior engineer pokes holes in it. That comes from tracing real requests through real code until the boundaries are yours, not from a prompt. When production breaks at 2 a.m., you won’t have time to ask an AI where org_id scoping lives — you’ll already need to know.


One-line summary

Assure PAT is a multi-tenant D-PAS card-testing platform: org users build EMV profiles and route a card request through approval, a tester emulates the card over NFC and taps a real terminal, and the captured transaction flows back through API Gateway → Express-on-Lambda → MySQL/S3 for review — all isolated per tenant by org_id, guarded by a four-step middleware chain and three token types.


Module done? Add it to today’s tracker

Saves your progress on this device.