Skip to content

Repo and module structure

This page is the canonical reference for the directory layout of all eight code repositories. Every new module in every repo must follow this layout exactly. See ADR-043 for the rationale and decision record.


Repo-to-system mapping

Repo System domain Modules
bank-platform SD07 Data Platform & Governance MOD-042–048, MOD-062–063, MOD-075–076, MOD-079, MOD-097, MOD-099–100, MOD-102–104, MOD-156–160
bank-core SD01 Core Banking MOD-001–008, MOD-110–112, MOD-118, MOD-125, MOD-130, MOD-133–134, MOD-140, MOD-143
bank-kyc SD02 KYC Platform MOD-009–015, MOD-153
bank-aml SD03 AML Monitoring MOD-016–019
bank-payments SD04 Payments MOD-020–026, MOD-061, MOD-067, MOD-081–082, MOD-084, MOD-114, MOD-119–120, MOD-122–124, MOD-135–137, MOD-141, MOD-144–145, MOD-149, MOD-154
bank-credit SD05 Credit MOD-027–031, MOD-059, MOD-065–066, MOD-115–117, MOD-121, MOD-128, MOD-132
bank-risk-platform SD06 Risk Platform MOD-032–041, MOD-055–058, MOD-060, MOD-080, MOD-085–086, MOD-098, MOD-101, MOD-105–107, MOD-147, MOD-150, MOD-152
bank-app SD08 App MOD-049–054, MOD-064, MOD-068–074, MOD-077–078, MOD-083, MOD-108–109, MOD-113, MOD-126–127, MOD-129, MOD-131, MOD-138–139, MOD-142, MOD-146, MOD-148, MOD-151, MOD-155

Repo root structure

Every code repo has this layout at the root:

{repo}/
├── .github/
│   ├── workflows/
│   │   ├── mod-001.yml          ← generated by bank-wiki generate-workflows.py (FR-735)
│   │   ├── mod-002.yml
│   │   └── ...
│   └── CODEOWNERS               ← optional; owners for review assignment
├── MOD-001-{slug}/              ← one directory per module (see below)
├── MOD-002-{slug}/
├── ...
├── docs/
│   ├── design/
│   │   ├── MOD-001.md           ← module design doc; SSM outputs table for IaC modules
│   │   └── MOD-002.md
│   └── handoffs/
│       ├── MOD-001-complete.handoff.md
│       └── MOD-002-complete.handoff.md
├── .nvmrc                       ← Node.js version pin (e.g. "20")
├── .gitignore
├── CLAUDE.md                    ← session startup instructions (see template below)
├── package.json                 ← root workspace package (see below)
├── pnpm-workspace.yaml          ← pnpm workspace config (see below)
└── tsconfig.base.json           ← optional shared tsconfig (bank-core pattern)

package.json (root):

{
  "name": "{repo}",
  "version": "0.0.0",
  "private": true,
  "packageManager": "pnpm@9.12.0",
  "engines": { "node": ">=20" },
  "scripts": {
    "typecheck": "pnpm -r typecheck"
  }
}

pnpm-workspace.yaml:

packages:
  - "MOD-*"
bank-platform also includes "packages/*" for the shared-iac package. Application repos do not.


Module directory layout

Lambda module (application logic + infra)

The most common type. Implements one or more Lambda handlers with SST infrastructure.

MOD-NNN-{slug}/
├── infra/
│   ├── index.ts                 ← barrel: exports all infra outputs
│   ├── api.ts                   ← API Gateway / Function URL definitions
│   ├── functions.ts             ← Lambda Function definitions
│   ├── queues.ts                ← SQS queues (if applicable)
│   ├── tables.ts                ← DynamoDB tables (if applicable)
│   └── ssm-outputs.ts          ← SSM Parameter Store outputs (required)
├── src/
│   ├── handlers/                ← Lambda entry points (one per endpoint/event)
│   ├── services/                ← business logic (no AWS SDK, no I/O)
│   ├── lib/                     ← shared utilities (logger, errors, trace, emf)
│   └── types/                   ← TypeScript types and Zod schemas
├── tests/
│   ├── unit/                    ← pure logic, no I/O; vitest, ≥80% coverage
│   ├── integration/             ← against deployed dev; one test per FR
│   ├── policy/                  ← one test per policies_satisfied row
│   │                               GATE: negative test; LOG: immutability test
│   └── contract/                ← event schema and API shape tests
├── package.json                 ← module package (see below)
├── sst.config.ts                ← per-module SST app (see below)
├── tsconfig.json
└── vitest.config.ts

IaC module (infrastructure only, no Lambda handlers)

Provisions AWS resources. No src/handlers/ or src/services/. Tests are integration-only (no unit tests possible without a running stack).

MOD-NNN-{slug}/
├── infra/
│   ├── index.ts
│   ├── {resource}.ts            ← one file per logical resource group
│   └── ssm-outputs.ts           ← required; verified by reusable-iac.yml post-deploy
├── tests/
│   └── integration/             ← AWS SDK assertions against deployed dev; one per FR
├── package.json
├── sst.config.ts
└── tsconfig.json

Script module (non-SST provisioner)

Used for REST-API-based provisioning (e.g. MOD-103 Neon, MOD-156 GitHub). No SST deploy step. Has a bespoke workflow (not generated by generate-workflows.py).

MOD-NNN-{slug}/
├── src/
│   ├── config/                  ← typed configuration constants
│   └── provision*.ts            ← idempotent provisioner scripts
├── __tests__/                   ← Jest or Vitest at module discretion
│   ├── unit/
│   └── integration/
├── package.json
└── tsconfig.json

Per-module package.json

{
  "name": "@{repo}/mod-{NNN}-{slug}",
  "version": "0.0.0",
  "private": true,
  "type": "module",
  "scripts": {
    "typecheck":        "tsc --noEmit",
    "test":             "vitest run",
    "test:unit":        "vitest run tests/unit tests/contract",
    "test:integration": "vitest run tests/integration tests/policy --testTimeout=30000",
    "deploy":           "sst deploy",
    "diff":             "sst diff",
    "remove":           "sst remove"
  }
}

Notes: - test:unit runs tests/unit/ and tests/contract/ together. Both run without live AWS. - test:integration runs tests/integration/ and tests/policy/ together against deployed dev. - deploy and diff run inside the module directory against the module's own sst.config.ts. - coverage is configured in vitest.config.ts, not via CLI flags.


Per-module sst.config.ts

/// <reference path="./.sst/platform/config.d.ts" />

export default $config({
  app(input) {
    return {
      name: "{repo}-mod-{NNN}",           // e.g. "bank-core-mod-001"
      removal: input?.stage === "prod" ? "retain" : "remove",
      home: "aws",
      providers: {
        aws: {
          region: "ap-southeast-2",
          defaultTags: {
            tags: {
              module_id: "MOD-{NNN}",
              system_id: "SD0N",
              repo: "{repo}",
              environment: input?.stage ?? "dev",
            },
          },
        },
      },
    };
  },
  async run() {
    const mod = await import("./infra/index");
    return mod.outputs;
  },
});

SST app naming: {repo}-mod-{NNN} — always repo + literal mod + zero-padded module number. This produces a unique CloudFormation stack name per module per stage. Example: bank-core-mod-001-dev.


vitest.config.ts

import { defineConfig } from "vitest/config";

export default defineConfig({
  test: {
    environment: "node",
    include: ["tests/**/*.test.ts"],
    exclude: ["node_modules", "dist", ".sst"],
    coverage: {
      provider: "v8",
      // Scope to unit-testable files only. Handlers and services that
      // require a live database or AWS SDK are covered by integration
      // and policy suites; including them here makes the 80% gate
      // meaningless.
      include: [
        "src/lib/**/*.ts",
        "src/services/**/*.ts",   // pure logic only; exclude service files
      ],                           // that depend on db.ts or AWS SDK
      thresholds: {
        lines: 80,
        functions: 80,
        branches: 75,
        statements: 80,
      },
    },
  },
});

Test categories

Directory Runner AWS required? When required
tests/unit/ Vitest, in-process No All Lambda and hybrid modules
tests/contract/ Vitest, in-process No Any module that publishes/consumes EventBridge events
tests/integration/ Vitest, against deployed dev Yes All modules; one test per FR
tests/policy/ Vitest, against deployed dev Yes Any module with policies_satisfied entries

File naming convention: {fr-or-policy-id}-{description}.test.ts

Examples: - tests/integration/fr-045-balanced-atomic-commit.test.ts - tests/policy/pay-001-gate-unbalanced-rejection.test.ts - tests/contract/posting-completed-event.test.ts

GATE policy tests must include a negative test: the control must block the bad outcome, not just log it. The test title should state what is blocked.

LOG policy tests must include an immutability test: records cannot be deleted or mutated after creation.


docs/design/MOD-NNN.md

Required for all modules. The reusable-iac.yml workflow reads this file after deploy to verify every declared SSM output path resolves.

Minimum sections:

# MOD-NNN — {title}

## Purpose
[What this module does and why]

## SSM outputs       ← required for IaC and hybrid modules
| Output | SSM path | Consumed by |
|---|---|---|
| Role ARN | /bank/{env}/iam/cicd/arn | MOD-156 |

## Handler contracts  ← required for Lambda modules
[Request/response schemas, error responses]

## Policy satisfaction
[How each policies_satisfied row is implemented]

docs/handoffs/MOD-NNN-complete.handoff.md

Required to close a module build. The wiki's handoff processor reads this from docs/handoffs/ at the repo root. Minimum content:

# Handoff: MOD-NNN build complete

**Module:** MOD-NNN — {title}
**System:** SD0N ({system-name})
**Status on commit:** `build_status: Built` requested

## What was delivered
[List of files created; FRs implemented; policy rows satisfied]

## What was NOT delivered
[Explicit out-of-scope items; deferred items with reason]

## Wiki updates requested
[update-wiki.py command to run]

CI/CD integration

Per-module workflow files are generated by bank-wiki/scripts/generate-workflows.py (FR-735) and written to .github/workflows/mod-{NNN}.yml. They reference the reusable templates in bank-platform:

  • Lambda/hybrid modules: totara-bank/bank-platform/.github/workflows/reusable-lambda.yml@main
  • IaC modules: totara-bank/bank-platform/.github/workflows/reusable-iac.yml@main

The generated workflow passes module_dir: MOD-NNN-{slug} which sets working-directory for all pipeline steps. This means:

  • pnpm install --frozen-lockfile runs inside MOD-NNN-{slug}/
  • pnpm typecheck runs inside MOD-NNN-{slug}/
  • pnpm test:unit --coverage runs inside MOD-NNN-{slug}/
  • pnpm run deploy --stage dev runs inside MOD-NNN-{slug}/

The SSM output verification step reads docs/design/MOD-NNN.md from the repo root (not from inside the module directory).

Re-run the generator whenever a module is added or a module slug or title changes:

cd bank-wiki
python3 scripts/generate-workflows.py --repo {repo}

CLAUDE.md template

Every code repo has a CLAUDE.md at the root. The content follows the template below. The only things that change per-repo are: the system domain header, the wiki URL references (same for all), the update-wiki.py repo flag, the module table, and the key ADRs list.

# {repo} — CLAUDE.md
# SD0N: {System Domain Name}
# Repo: {repo} | Wiki: https://bank-wiki.pages.dev

---

## Operating rules — read before anything else

The bank-wiki at https://bank-wiki.pages.dev is the **binding specification**
for everything in this repo. It is not advisory. It is not a starting point.
Every module spec, FR, data model, interface contract, observability standard,
error handling standard, and policy satisfaction in the wiki must be implemented
exactly as written.

If something in the spec is unclear or ambiguous: stop and ask the human
orchestrator. Do not assume and build.

## Session startup — mandatory every session

1. Fetch ALL six wiki context URLs simultaneously before doing anything else:
   - https://bank-wiki.pages.dev/ai-context/systems/
   - https://bank-wiki.pages.dev/ai-context/architecture/
   - https://bank-wiki.pages.dev/ai-context/policies/
   - https://bank-wiki.pages.dev/ai-context/design/
   - https://bank-wiki.pages.dev/ai-context/delivery/
   - https://bank-wiki.pages.dev/ai-context/index/

2. Confirm to the human orchestrator what you are starting.

## Repo structure — mandatory reference

See: https://bank-wiki.pages.dev/architecture/repo-structure/ (ADR-043)

Directory layout for this repo:
  MOD-NNN-{slug}/           ← module directory at repo root
  MOD-NNN-{slug}/src/       ← Lambda handlers and business logic
  MOD-NNN-{slug}/infra/     ← SST infrastructure (IaC)
  MOD-NNN-{slug}/tests/unit/
  MOD-NNN-{slug}/tests/integration/
  MOD-NNN-{slug}/tests/policy/
  MOD-NNN-{slug}/tests/contract/
  MOD-NNN-{slug}/sst.config.ts   ← per-module SST app (name: "{repo}-mod-{NNN}")
  MOD-NNN-{slug}/vitest.config.ts
  docs/design/MOD-NNN.md         ← at repo root (not inside module dir)
  docs/handoffs/MOD-NNN-complete.handoff.md

Package manager: pnpm. Never use npm or yarn.
Test runner: Vitest. Never use Jest (legacy only in bank-platform).
SST app name: {repo}-mod-{NNN} (e.g. bank-core-mod-001).

## Module build procedure — mandatory for every module

READ before writing any code:
  ../bank-wiki/source/entities/modules/MOD-NNN.yaml
  ../bank-wiki/source/entities/modules/MOD-NNN.md
  ../bank-wiki/source/pages/goals/fr-register.md

CONFIRM to orchestrator (wait for acknowledgement):
  a) What this module does — one paragraph
  b) Every FR — ID and full requirement text
  c) Every policies_satisfied row — mode, exact test description
  d) Every Postgres table read or written
  e) Every SSM path read (upstream) and written (downstream outputs)
  f) Every EventBridge event published or consumed
  g) Module type: pure IaC / hybrid / application Lambda
  h) Complete list of files to be created

PRODUCE (all required before marking Built):
  1. Implementation code
  2. tests/unit/ ≥80% line coverage
  3. tests/integration/ — one test per FR
  4. tests/policy/ — one test per policies_satisfied row
  5. tests/contract/ — one test per event published/consumed
  6. docs/design/MOD-NNN.md (SSM outputs table for IaC/hybrid)
  7. docs/handoffs/MOD-NNN-complete.handoff.md

## Quality gates

All modules:
  compile --check passes on the handoff file

IaC modules:
  SSM outputs table in docs/design/MOD-NNN.md, verified accurate
  GATE: negative test required | LOG: immutability test required

Lambda/hybrid modules (add to above):
  Unit tests ≥80% line coverage
  Structured log format test
  Idempotency test where applicable

## Updating build status

    cd ../bank-wiki && python3 scripts/update-wiki.py \
      --module MOD-NNN --status Built \
      --commit $(git -C ../{repo} rev-parse HEAD) --repo {repo}

---

## Modules

[module table — build in phase order]

## Key ADRs

[ADR list relevant to this system domain]

Known divergences from this standard

These are documented here so Claude Code sessions are not confused by them.

Repo Divergence Status
bank-platform Existing modules (MOD-043 to MOD-097) use Jest, not Vitest Accepted — built modules are not migrated
bank-platform Existing modules use src/stacks/ for IaC, not infra/ Accepted — built modules are not migrated
bank-kyc Root sst.config.ts and npm workspaces (MOD-009 era) Must migrate before MOD-010 is built
bank-kyc Single SST app name bank-kyc instead of bank-kyc-mod-009 Must migrate as part of above