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/*" 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-lockfileruns insideMOD-NNN-{slug}/pnpm typecheckruns insideMOD-NNN-{slug}/pnpm test:unit --coverageruns insideMOD-NNN-{slug}/pnpm run deploy --stage devruns insideMOD-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:
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 |