Skip to content

Repository files navigation

Swift Sentinel (bank-core-java)

Swift Sentinel is the Spring Boot backend in this repo: it ingests SWIFT FIN MT and ISO 20022 (MX / CBPR-style) traffic, validates and enriches it, transforms it through versioned mapping profiles, routes outcomes through governance and repair, and serves operator REST APIs plus the embedded dashboard SPA at /dashboard/app/.


Processing engine

REST intake, optional file hot-folder, and optional MQ adapters all converge on IntakeOrchestrationService, so the same orchestration handles messages regardless of how they arrive. HTTP entry points optionally wrap expensive work behind ComputeExecutionService for compute governance—see IntakeController.

Layered message flow

End-to-end flow is ingress → classify → validation → corridor-specific enrichment/transform → governance tail. Validation is deterministic rule + fact pipelines; governance is repair queue, approvals, lifecycle ledgers, and optional settlement-graph hooks where the corridor demands it.

flowchart TB
  subgraph ingress ["Ingress channels"]
    ch[REST / file inbox / MQ]
    ch --> track[Ingestion tracking]
    track --> orch[IntakeOrchestrationService]
  end
  subgraph shared ["Shared stages"]
    httpwrap[ComputeExecutionService on REST only]
    orch --> sniff[classify FIN block 2 or MX hint]
    sniff --> mtval[MtValidationEngine + corridor context]
  end
  subgraph byfamily ["Branch by MT family"]
    mtval --> pay[MT103 / MT202 settlement + CBPR transforms]
    mtval --> ini[MT101 / MT102 batch initiation]
    mtval --> st[MT940 / MT950 statements]
    mtval --> free[MT199 / MT299 / MT999 ops free-format]
  end
  subgraph tail ["Governance tail"]
    pay --> gov[Compliance + policy overlays]
    ini --> gov
    st --> gov
    free --> gov
    gov --> done[Accepted / repair / rejected + ledgers]
  end
Loading

Legend: Ingress includes correlation via ingestion events where the channel participates. Paths after branching still share compliance, ComplianceGateService, enforcement, and OperatorGovernanceService semantics; transforms for MX emission are TransformService plus MappingProfileCatalog (not every MT family produces full CBPR payloads on every outcome).

Thin HTTP sketch (not the full macro flow above):

flowchart LR
  IC[IntakeController] --> CE[ComputeExecutionService]
  CE --> IO[IntakeOrchestrationService]
Loading

Validation stack and semantic layers

This is the vertical decision stack inside validation (orthogonal to “which MT family?” in the previous diagram).

  • Issue tiersValidationTier classifies each finding as STRUCTURAL (parser / shape integrity), SEMANTIC (financial meaning), or COMPLIANCE (policy). ValidationDispatchPolicy explains how semantic and compliance issues relate to suppressing financial XML / MX dispatch, with PolicyEnforcer producing L5 dispatch decisions (PolicyEnforcer.java). See also docs/adrs/001-transform-envelope-authority-contract.md and the OpenAPI sketch at openapi.json.
  • Structural vs semantic stages inside L2MtValidationRulePipeline runs STRUCTURAL rules on raw/block4 context vs SEMANTIC multiplexed rules on parsed payloads. MtValidationEngine is the L2 orchestrator and keeps those families separated.
  • PRE-IR → semantic IRPreIrGateResult gates whether MtValidationEngine.attachPreIrAndSemanticIr may attach MtSemanticIrBundle. On reject, semantic IR stays off the MtValidationContext; on allow, the bundle may be populated.
  • Semantic IR artifact — MT semantic intermediate representation is built by the sibling swiftbridge-mt-semantic library (MtSemanticIrBuilder, MtSemanticIrBundle; package com.swiftbridge.mt.semantic). Maven builds bank-core-java through the reactor parent so that module resolves locally (see repo pom.xml).
  • L0 / ladderMtValidationContext documents execution mode driving L0 (XSD) and L1–L3 pipeline activation**. L3 derives the financial slice; ValidationEnforcer governs whether blocking L2 outcomes abort downstream L4 construction (MtValidationEngine javadoc). L5 is policy enforcement / dispatch verdict. Exact activation varies by message type and engine mode.
  • Corridor lensCorridorResolver + CbprProfileResolver interpret rails / CBPR context layered on MtValidationContext.

Validation stack sketch:

flowchart TB
  subgraph tiers ["Finding taxonomy"]
    STRUCTURAL --> SEMANTIC
    SEMANTIC --> COMPLIANCE
  end
  PRE_IR[PRE-IR gate] --> L2[L2 rule pipeline]
  L2 --> SEM_IR[Semantic IR bundle optional]
  SEM_IR --> L3[L3 financial model]
  L3 --> L4[L4 downstream build gated by ValidationEnforcer]
  L4 --> L5[PolicyEnforcer L5 dispatch]
Loading

Supported message formats

MT categories (validation classifier)

Enums and routing use FIN block 2 wire identifiers. Supported types appear in MtMessageType: MtMessageType.java

Enum Wire (block 2)
MT101 101
MT102 102
MT103 103
MT202 202
MT199 199
MT299 299
MT999 999
MT940 940
MT950 950

MT ↔ ISO 20022 mapping profiles

Transform routes registered in MappingProfileCatalog (non-exhaustive summary; authoritative list lives in MappingProfileCatalog.java):

Direction / route ISO message (schema id)
MT103 → MX pacs.008.001.08
MT202 → MX pacs.009.001.08
MT101 / MT102 → MX pain.001.001.09
MT940 / MT950 ↔ MX camt.053.001.08

Reverse MX→MT mappings are likewise catalogued (e.g. pacs.008.001.08→MT103).

Reality check: initiation, payment, statement, and free-format corridors do not all share identical intake branches—consult IntakeOrchestrationService and transform services before assuming parity.


Package layout (com.swiftbridge.core)

Implements the layers described above—this table maps folders to intent (names stay SwiftBridge-prefixed in bytecode).

Area Role
api REST adapters (@RestController), ApiProblems, ApiObservabilityExceptionHandler
config Spring beans, SecurityConfig, @ConfigurationProperties
domain Records / domain payloads (intake, governance, validation reports) isolated from controllers
service Stateless @Service domains: intake, transform, validation, policy, governance, intelligence, replay, etc.
canonical Syntax-independent canonical model (see package-info.java; purity enforced via ArchUnit CanonicalPurityArchitectureTest)
security JWT decoding, IAM admission PAT filter, JDBC identity repositories
observability SafeLoggerFactory, forensic crypto properties, retention
compute/runtime Compute boundary helpers used by controllers
ui SPA shell / dashboard routing controllers

Additional architecture regression suites live under src/test/java/com/swiftbridge/core/architecture.


How the API is secured

Configured in SecurityConfig: SecurityConfig.java

  • Spring Security 6 with @EnableMethodSecurity where service methods declare pre/post checks.
  • CSRF disabled on this chain—clients authenticate with bearer tokens rather than cookie sessions (browser CSRF defenses are layered at the SPA / gateway when applicable).
  • CORS via corsConfigurationSource: sane localhost defaults plus optional patterns / permissive toggle for tunnels.
  • Authentication
    • OAuth2 Resource Server JWT bearer validation with a custom JWT→**GrantedAuthority** converter (roles, realm_access.roles for Keycloak-shaped tokens).
    • IamAdmissionAuthenticationEntryPoint on the JWT authentication entry point for IAM-friendly error surfaces.
    • PersonalAccessTokenAuthenticationFilter runs before BearerTokenAuthenticationFilter (PersonalAccessTokenAuthenticationFilter.java).
    • TenantContextFilter installs tenant scope before AuthorizationFilter.
  • Authorization: explicit authorizeHttpRequests matchers per REST path verb; dashboards static assets /dashboard/app/assets/** are WebSecurityCustomizer#ignoring so ES module loads are not intercepted by Bearer filters (SecurityConfig.java** comments).
  • Passwords: BCryptPasswordEncoder bean (SecurityConfig). Signup/login via AuthApiController persists bcrypt hashes (AuthApiController.java).

Important: application.yml documents that JWT login flows use POST /api/v1/auth/login. HTTP Basic is not wired on this resource-server filter chain—call APIs with Authorization: Bearer <token> (login-issued JWT, enterprise issuer, PAT, etc.). The spring.security.user.*/APP_BASIC_* settings still populate the UserDetailsService bean backed by InMemoryUserDetailsManager (SecurityConfig) for Spring conventions, but do not imply Basic-auth API access.

flowchart LR
  Client[Client] --> PAT[PersonalAccessToken filter]
  PAT --> Jwt[OAuth2 JWT resource server]
  Jwt --> Tenant[TenantContext filter]
  Tenant --> AuthZ[authorizeHttpRequests]
Loading

Cryptography and data protection

Concern Implementation
TLS Terminate HTTPS at ingress (reverse proxy, service mesh)—not mandated inside Boot itself.
Forensic symmetric seal ForensicCryptoService AES-256-GCM (AES/GCM/NoPadding) with random IVs; gated on bank.observability.forensic.aes-key-base64 (ForensicCryptoService.java / BankObservabilityProperties)
Evidence packs metadata EvidencePackProperties default encryptionStandard = AES-256-GCM (EvidencePackProperties.java)

Operational ownership of KMS / key material stays with your deployment process.


Logging and observability

  • SafeLoggerFactory / SafeLogger provide redaction-conscious logging hooks used by ingestion and ledger pathways (e.g. MessageLifecycleLedgerService constructs loggers via the factory (SafeLoggerFactory.java).
  • Prefer SLF4J parameterized APIs everywhere else (for example logger.info("processed {}", correlationId)).
  • Expose actuator health/prometheus per application.yml; /api/v1/system/health and actuator health matchers stay permit-all in SecurityConfig.

Spring Boot and Java engineering practices

  • Constructor injection on controllers/services (see IntakeController pattern).

  • @EnableAsync, @EnableScheduling, and @EnableConfigurationProperties on BankCoreApplication (BankCoreApplication.java).

  • Bean Validation (@Valid DTOs) on auth endpoints (AuthApiController).

  • Centralized RFC7807-ish problems: ApiObservabilityExceptionHandler plus ApiProblems.

  • Flyway-managed schema boots with the app (spring.flyway.enabled: true in application.yml).

  • Testcontainers-backed integration suites locally (Docker required) plus com.swiftbridge.core.architecture.* regression tests guarding boundaries (canonical purity, corridors, governance).

  • Canonical package intent (syntax-neutral financial entities, no sideways service imports) summarized in canonical/package-info.java.

  • Further reading: docs/adrs/001-transform-envelope-authority-contract.md (transform envelope authority).


Local bootstrap & operations

Prerequisites

  • Docker (for PostgreSQL)
  • Java 21

1) Start local PostgreSQL

cd bank-core-java
docker compose up -d

2) Run app (Flyway migrations run automatically)

Maven must use the repo-root reactor (../pom.xml) so sibling swiftbridge-mt-semantic builds and resolves locally (that artifact is not on Maven Central).

cd bank-core-java
./mvnw -f ../pom.xml -pl bank-core-java spring-boot:run

Obtaining tokens

  1. Prefer POST /api/v1/auth/login JSON credentials or your enterprise JWT issuer (SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI / JWK_SET_URI).
  2. Or mint local HMAC dev tokens (APP_JWT_SECRET per below).

Development-only defaults mirrored in application.yml (spring.security.user) populate UserDetailsService but remember: API calls still need Bearer JWTs, not Basic auth.

Overrides:

  • APP_BASIC_USER / APP_BASIC_PASSWORD / APP_BASIC_ROLES (mapped into spring.security.user + bank.security.local-basic-roles; see application.yml)

OIDC/JWT mode (bank SSO path)

  • Resource server support is enabled.
  • For enterprise IdP, set:
    • SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI
    • or SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI
  • Role claims accepted:
    • roles (array)
    • realm_access.roles (Keycloak style)
  • Roles are mapped to Spring authorities as ROLE_<UPPERCASE_ROLE>.

Local JWT testing (HMAC)

  • Local decoder uses APP_JWT_SECRET.
  • Example token generation:
node -e "const c=require('crypto');const h=(b)=>Buffer.from(b).toString('base64url');const s=process.env.APP_JWT_SECRET||'swift-bridge-local-hmac-secret-change-me-32chars';const header=h(JSON.stringify({alg:'HS256',typ:'JWT'}));const payload=h(JSON.stringify({sub:'ops-user-1',roles:['admin','operator'],exp:Math.floor(Date.now()/1000)+3600}));const sig=c.createHmac('sha256',s).update(header+'.'+payload).digest('base64url');console.log(header+'.'+payload+'.'+sig);"
  • Use with:
curl -H "Authorization: Bearer <token>" http://127.0.0.1:8088/api/v1/intake/metrics

3) Run tests

cd bank-core-java
./mvnw -f ../pom.xml test

Integration tests use Testcontainers and require Docker running locally.

Run only the scripted E2E bank simulation (File + MQ + repair + maker-checker + replay + dispatch):

cd bank-core-java
./mvnw -f ../pom.xml -Dtest=BankSimulationE2ETest test

4) One-command startup + test flow

cd bank-core-java && docker compose up -d && ./mvnw -f ../pom.xml test && ./mvnw -f ../pom.xml -pl bank-core-java spring-boot:run

5) Production compose profile (app + db + health checks)

cd bank-core-java
cp .env.prod.example .env.prod
# Edit .env.prod with real secrets and issuer settings
docker compose --env-file .env.prod -f docker-compose.prod.yml up -d --build

Stop:

docker compose --env-file .env.prod -f docker-compose.prod.yml down

Useful endpoints

  • Health: http://127.0.0.1:8088/api/v1/system/health
  • Intake process: POST /api/v1/intake/process
  • Intake metrics: GET /api/v1/intake/metrics
  • Ingestion events: GET /api/v1/intake/ingestion/events
  • Ingestion stats: GET /api/v1/intake/ingestion/stats
  • Lifecycle ledger events: GET /api/v1/intake/lifecycle/events
  • Operational alerts: GET /api/v1/intake/alerts
  • Ingestion controls read: GET /api/v1/intake/controls
  • Ingestion controls update: POST /api/v1/intake/controls
  • MQ harness status: GET /api/v1/intake/mq/status
  • MQ harness process: POST /api/v1/intake/mq/smoke/process
  • MQ harness publish: POST /api/v1/intake/mq/smoke/publish
  • MQ harness DLQ replay: POST /api/v1/intake/mq/smoke/replay-dlq
  • Evidence pack generate: POST /api/v1/governance/evidence-pack/generate
  • Evidence pack latest: GET /api/v1/governance/evidence-pack/latest
  • Evidence pack history: GET /api/v1/governance/evidence-pack/history
  • Dashboard playground: GET /dashboard/playground (paste MT and dry-run conversion + issues)
  • Transform envelope authority (ADR): docs/adrs/001-transform-envelope-authority-contract.mddecisionCore latch; issueRegistry diagnostics; validationReport L2 trace; OpenAPI sketch in /contracts/openapi/openapi.json

Runbook:

  • docs/runbooks/audit-evidence-pack.md
  • docs/runbooks/e2e-bank-simulation.md
  • Governance cases: GET /api/v1/governance/repair-cases
  • Mapping profile catalog: GET /api/v1/transform/profiles
  • Operator UI (production): http://127.0.0.1:8088/dashboard/app/ (Spring Boot, React SPA at dashboard-ui/). Rebuild the SPA into src/main/resources/static/dashboard/app/: run pnpm install at the repository root (pnpm workspace), then ./mvnw -f ../pom.xml -pl bank-core-java generate-resources -P dashboard-ui (or package with that profile).
  • Legacy operator UI (Node static, archived): cd legacy/typescript-mvp && npm install && npm run dev then http://127.0.0.1:3847/ops.html (deprecation banner displayed; set API base to http://127.0.0.1:8088 in legacy/typescript-mvp/public/ops.config.js). Retained only so existing bookmarks keep resolving — slated for removal at Phase 8 sign-off.

File ingestion listener (hot folder)

The backend can poll a local inbox directory and auto-route messages through intake.

Enable with env vars:

export BANK_INGESTION_FILE_ENABLED=true
export BANK_INGESTION_FILE_INBOX=./data/ingestion/inbox
export BANK_INGESTION_FILE_ARCHIVE=./data/ingestion/archive
export BANK_INGESTION_FILE_ERROR=./data/ingestion/error
export BANK_INGESTION_FILE_POLL_MS=5000

Then run:

cd bank-core-java
./mvnw -f ../pom.xml -pl bank-core-java spring-boot:run

Drop MT/MX payload files into the inbox directory. Processed files are moved to archive (accepted) or error (repair/rejected/failed).

MQ ingestion adapter (retry + DLQ)

The MQ adapter is wired through Spring JMS and shares the same intake and tracking pipeline.

Enable and configure:

export BANK_INGESTION_MQ_ENABLED=true
export BANK_INGESTION_MQ_INBOUND_QUEUE=SWIFT.IN
export BANK_INGESTION_MQ_DLQ=SWIFT.DLQ
export BANK_INGESTION_MQ_MAX_ATTEMPTS=3
export BANK_INGESTION_MQ_BACKOFF_MS=1000
export BANK_INGESTION_MQ_HARNESS_ENABLED=true
export IBM_MQ_QUEUE_MANAGER=QM1
export IBM_MQ_CHANNEL=DEV.APP.SVRCONN
export IBM_MQ_CONN_NAME=127.0.0.1(1414)
export IBM_MQ_USER=app
export IBM_MQ_PASSWORD=change-me

Notes:

  • IBM MQ Spring starter auto-provides JMS ConnectionFactory when IBM MQ properties are set.
  • On repeated processing failure, payload is published to DLQ with source reference and reason header text.

Mapping profiles and error taxonomy

  • Transform endpoints accept ?profile=v1 or ?profile=v1.1 (default is v1.1).
  • v1 vs v1.1 affects MT103 ingest most visibly: v1.1 uses Prowide (ProwideMt103ParserSwiftMessage.parse). v1 keeps the legacy hand-extracted tags path in TransformService (extractTag / substring on :32A:) for backwards compatibility—not Prowide.
  • Other Prowide-backed FIN families (profile independent in code paths shown):
  • MT940 / MT950 statements use deterministic in-house parsers (block‑4 extraction, :61: line lexer, statement canonical build in TransformService / Mt940Field61LineParser, etc.)—not the Prowide SwiftMessage façade used for 103/202/101/102 canonical projection.
  • Deterministic mapping errors are returned as JSON:
    • errorCode
    • field
    • message

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages