# Fintech repository evidence map

Reviewed: 2026-08-22 (Asia/Dubai)

Reference repository (read-only):
`/Users/mustafa/Documents/Codex/2026-08-22/c/fintech-project`

This is the audit trail for `deliverable/01-repository-case-study.md`. All code
locators below are relative to the reference repository. The review uses three
labels deliberately:

- **Observed:** a fact visible at the cited file and line.
- **Documented:** an AWS or pattern semantic controlled by the cited claim and
  source IDs in `claim-register.md` and `source-ledger.md`.
- **Inference:** an architecture conclusion from observed code plus documented
  semantics; it is not a vendor guarantee.

## As-built runtime and infrastructure trace

| Path | Observed implementation | Exact repository route | Boundary / role |
|---|---|---|---|
| Order API | The production HTTP API exposes `POST /orders` through an AWS proxy integration to the order-handler alias. | `infra/environments/production/main.tf:419-477` | Synchronous client/API/Lambda request path. API success is not downstream business completion (C51/A53). |
| Input and identity | The handler parses JSON, validates the order, generates a new order ID, and builds a `PENDING` order record. | `code/lambdas/order-handler/handler.ts:114-153`; `code/shared/validation/schemas.ts:15-27` | Command validation and operational order-state creation. |
| Idempotency lookup | A supplied `Idempotency-Key` is read before order processing; lookup failure is explicitly fail-open. | `code/lambdas/order-handler/handler.ts:219-241` | Best-effort response cache, not business idempotency (C40/F11). |
| Order persistence | The order is written to the main DynamoDB table with a standalone `PutCommand`. | `code/lambdas/order-handler/handler.ts:155-165` | First durable side effect of order submission. |
| Idempotency persistence | A cached `201` response is conditionally put in a separate table after the order write and before publication; any store error is logged and ignored. | `code/lambdas/order-handler/handler.ts:81-104,167-181`; `infra/environments/production/main.tf:617-658` | Separate write and cache boundary. TTL is cleanup, not a correctness deadline (C32/A23). |
| `OrderPlaced` publication | The handler calls `PutEvents` after both database writes, catches thrown errors, but does not retain or inspect the API result. | `code/lambdas/order-handler/handler.ts:183-212` | Synchronous producer API call followed by intended asynchronous routing. `PutEvents` can partially fail; even zero failed entries plus `EventId` is only producer-API acknowledgement and can accompany a nonexistent-bus drop (C08/A06,A82). |
| Event bus and order target | The custom bus/rule configuration intends to route `trading.OrderPlaced` to trade-executor. The reusable target attaches a per-rule SQS DLQ ARN, but its module creates no queue resource policy granting EventBridge `sqs:SendMessage`. | `infra/environments/production/main.tf:345-359`; `infra/modules/eventbridge/main.tf:29-38,62-95`; bounded absence check below | EventBridge target delivery is distinct from Lambda runtime processing. The declared DLQ is not a usable recovery path until the queue policy exists (C05-C07,C57/A01,A04,A05,A81). |
| Trade execution | The handler validates the event, generates a trade ID, and calculates a random simulated price using binary floating point. | `code/lambdas/trade-executor/handler.ts:65-96` | This is a simulation inside Lambda, not an order-book/matching-engine implementation. |
| Trade persistence | A trade item keyed by the order ID is conditionally inserted. | `code/lambdas/trade-executor/handler.ts:83-104` | Duplicate trade-row guard for that key; not an atomic business workflow. |
| Order mutation | A separate update changes the order to `EXECUTED`; it has no condition requiring the previous state to be `PENDING`. | `code/lambdas/trade-executor/handler.ts:106-118` | Second DynamoDB side effect. |
| `TradeExecuted` publication | A third standalone operation sends an integration event and ignores the `PutEvents` result. | `code/lambdas/trade-executor/handler.ts:120-148` | Producer/database dual-write boundary (C08, C22, C38). |
| Notification fan-out | One EventBridge rule targets notification-sender; the handler validates and publishes JSON to an SNS topic. | `infra/environments/production/main.tf:360-370`; `code/lambdas/notification-sender/handler.ts:43-90` | Derived client-notification path, not authoritative financial state (C46). |
| Portfolio fan-out | A separate rule targets portfolio-updater and declares a separate SQS DLQ ARN. The handler reads a portfolio, applies the trade, and conditionally replaces the item using its current version, retrying conflicts three times. No queue resource policy is declared for this DLQ either. | `infra/environments/production/main.tf:565-615`; `code/lambdas/portfolio-updater/handler.ts:103-198`; bounded absence check below | Asynchronous mutable projection. The event has no projection inbox/deduplication record or source sequence, and the declared DLQ lacks its required write permission. |
| Trade query | `GET /trades` invokes trades-reader, which queries `GSI1` by user and time prefix with a maximum result limit of 100. | `infra/environments/production/main.tf:695-729`; `code/lambdas/trades-reader/handler.ts:45-97` | Read path through an eventually consistent GSI (C21/A19). No pagination token is returned. |
| Daily snapshot | A scheduled rule invokes portfolio-snapshot. One filtered `Scan` result is converted in memory to one JSONL object and written to S3. | `infra/environments/production/main.tf:373-393`; `code/lambdas/portfolio-snapshot/handler.ts:64-107` | Derived batch/export path. It is not a proven complete point-in-time financial snapshot. |
| Event archive and target DLQs | The EventBridge module declares a 30-day archive, 14-day SQS queues, and queue-depth alarms; production separately declares the portfolio queue/alarm. Neither Terraform path declares `aws_sqs_queue_policy` or an `events.amazonaws.com` `sqs:SendMessage` grant, so EventBridge cannot be shown able to populate those queues. The depth alarms can remain zero while `InvocationsFailedToBeSentToDLQ` rises. | `infra/modules/eventbridge/main.tf:15-60,76-95`; `infra/environments/production/main.tf:575-605`; bounded absence check below | Archive is a bounded replay source; the SQS queues are only intended holding paths until permissions are fixed. Neither is reconciliation (C43,C57/A81). |
| Manual redrive | dlq-replayer receives at most ten messages, reconstructs `PutEvents` entries, deletes after the SDK call returns, and does not inspect per-entry failures or independently validate the named bus. | `code/lambdas/dlq-replayer/handler.ts:61-145` | Operator-triggered redrive. HTTP 200, zero failures, or `EventId` is only producer-API acknowledgement; deletion needs validated bus configuration, durable replay identity, and downstream reconciliation (C08/A06,A82). |
| Observability | Every Lambda module enables active X-Ray tracing, a 14-day log group, and a Lambda error alarm; handlers use Powertools Logger and propagate a correlation ID in events. | `infra/modules/lambda/main.tf:12-70`; `code/shared/types/events.ts:1-12` | Technical telemetry exists; business-correctness and reconciliation metrics are not apparent (C48, C50). |
| Deployment | Lambda aliases are the integration targets and CodeDeploy declares a 10%-for-five-minutes canary only for order-handler. | `infra/modules/lambda/main.tf:42-49`; `infra/environments/production/main.tf:479-517` | Deployment safety is narrower than end-to-end workflow compatibility. |
| IAM | Production defines per-function roles: order handler can write/read DynamoDB and publish events; trade executor can write/update/read and publish; notifier can publish SNS; snapshot can scan/write S3; portfolio updater can get/put; trades reader can query; replayer can receive/delete from named DLQs and publish. The `events.amazonaws.com` principals at lines 391, 401, 410, and 613 are Lambda resource permissions, not SQS queue policies. | `infra/environments/production/main.tf:102-239,386-411,521-549,608-615,645-773` | Function/resource permissions are visible, but the DLQ `sqs:SendMessage` grants are absent; this is not a complete security audit. |

## As-built flow

Solid arrows are synchronous request/API calls from the caller's perspective.
Dashed arrows are scheduled or broker-driven asynchronous handoffs. Cylinders
are stores; double-bracket nodes are holding/replay paths.

```mermaid
flowchart LR
    Client[Client] -->|POST /orders| API[API Gateway HTTP API]
    API --> OH[order-handler Lambda]
    OH -->|Put order| Main[(Main DynamoDB table)]
    OH -->|Put cached 201| Idem[(Idempotency DynamoDB table)]
    OH -->|PutEvents API| Bus{Custom EventBridge bus}
    Bus -. OrderPlaced target .-> TE[trade-executor Lambda]
    TE -->|Put trade; Update order| Main
    TE -->|PutEvents API| Bus
    Bus -. TradeExecuted target .-> PU[portfolio-updater Lambda]
    PU -->|Get; conditional Put| Main
    Bus -. TradeExecuted target .-> NS[notification-sender Lambda]
    NS --> SNS[SNS notification topic]
    Client -->|GET /trades| API
    API --> TR[trades-reader Lambda]
    TR -->|Query GSI1| Main
    Schedule[Daily schedule] -.-> PS[portfolio-snapshot Lambda]
    PS -->|filtered Scan| Main
    PS --> S3[(S3 JSONL snapshots)]
    Bus -. failed target delivery .-> DLQGap{{DLQ send denied:<br/>queue resource policy absent}}
    DLQGap -. InvocationsFailedToBeSentToDLQ .-> Metric[CloudWatch metric]
    DLQGap -. after required policy only .-> DLQ[[Declared per-target SQS DLQs]]
    Ops[Operator/manual invoke] --> DR[dlq-replayer Lambda]
    DLQ -->|Receive/Delete only if populated| DR
    DR -->|PutEvents API| Bus
```

Exact topology routes: API and routes
`infra/environments/production/main.tf:419-477,695-729`; bus rules and targets
`infra/environments/production/main.tf:345-411,565-615`; generic DLQ declarations
`infra/modules/eventbridge/main.tf:27-95`; missing-policy absence check below; schedule
`infra/environments/production/main.tf:373-393`.

## Authority classification

| Data/component | As-built role | Authority conclusion |
|---|---|---|
| Order row | Mutable command status (`PENDING`, then `EXECUTED`). | **Observed:** it is the only persisted order-state row. **Inference:** it is the apparent operational authority for order status, but the code does not enforce a full order state machine or reservations. |
| Trade row | One conditional record per order key containing the simulated fill. | **Observed:** it is the apparent persisted execution fact. **Inference:** without matching, clearing/settlement, or ledger postings, it is not sufficient authority for cash or securities ownership. |
| Portfolio row | Mutable holdings and cost-basis view updated from `TradeExecuted`. | **Inference:** a derived projection. It cannot safely enforce available-balance or ownership invariants (C46/F17,F18). |
| GSI1 trade view | Replicated DynamoDB index queried by user. | **Documented:** GSI reads are eventually consistent (C21/A19). It is a query projection, not an independent authority. |
| Idempotency row | Cached HTTP status/body with TTL. | A response cache. It is neither the protected order mutation nor proof of valid event routing or downstream progress. |
| EventBridge bus/archive and SQS DLQs | Intended transport, bounded archive, and declared terminal-delivery queues. | Not a ledger or reconciliation system; the current SQS DLQs are not shown writable because their EventBridge queue policies are absent (C43, C47, C57). |
| S3 snapshot | Daily JSONL derived from mutable portfolios. | Analytics/audit convenience output; current implementation does not prove completeness or point-in-time consistency. |
| SNS notification | User-facing derivative of a trade event. | A notification, never evidence that the authoritative business process completed. |

## Guarantee-boundary audit

| Boundary | What success proves | What it does not prove | Required design response |
|---|---|---|---|
| API Gateway to order-handler | The synchronous integration returned an HTTP response. | That a trade, reservation, ledger posting, projection, or notification completed (C51/A53). | Return an explicitly scoped acceptance state and expose order status. |
| DynamoDB order `PutCommand` | That one order-row write returned successfully. | Publication to EventBridge or creation of a trade. DynamoDB transactions cannot include EventBridge (C22/A20). | Commit state plus an outbox record locally, then publish asynchronously (C38). |
| `PutEvents` producer call | HTTP 200, zero failed entries, and `EventId` establish only producer-API acknowledgement for the named request. | Valid bus existence/configuration, matching/routing, target delivery, consumer durability, database effects, or business completion; a nonexistent bus can return those response signals and drop the event (C08/A06,A82). | Inspect entries, validate bus/configuration independently, retain stable replay identity, and reconcile downstream effects. |
| EventBridge bus to Lambda target | EventBridge owns target-delivery retry according to the target policy (C05/A05). | Function-runtime retry, handler rollback, one business effect, or a working target DLQ. The current queues lack the required EventBridge send policy (C57/A81). | Configure/observe target delivery separately; add rule-scoped queue policies and alarm both delivery failures and `InvocationsFailedToBeSentToDLQ`. |
| Lambda asynchronous invocation/runtime | Its retry and terminal destination are a separate boundary (C06/A01,A04,A05). | Atomicity across the handler's several SDK calls. | Configure runtime failure handling and make each business transition idempotent. |
| Trade handler's DynamoDB calls | Each returned write succeeded individually. | Atomic trade creation plus order transition; neither call includes EventBridge. | Use one DynamoDB transaction for compatible local items, with conditional state transition and outbox. |
| Portfolio optimistic lock | The replacement used the version read by that invocation. | That the event was new or in causal order. | Atomically store an inbox/event ID and expected business sequence with the projection update (C40-C41). |
| Target DLQ or event archive | The archive is configured for bounded retention; an SQS message is retained only if EventBridge can write it. Current queue declarations do not establish that permission. | A configured DLQ ARN, zero queue depth, diagnosis, safe redrive, exact business recovery, or reconciliation (C43,C57/A81). | Add/test the queue policies, alarm failed DLQ sends, and own triage, correction, paced replay, idempotency, and reconciliation. |
| Business completion | No single as-built signal establishes matched execution, reservations, balanced postings, clearing/settlement, and reconciled projections. | End-to-end financial correctness. | Define authoritative domain boundaries, ledger postings, and independent reconciliation (C46-C48). |

## Twelve required issue routes

These IDs are mirrored in `claim-register.md` and the deliverable. Each row
separates repository observation from controlled semantics and inference.

| ID | Required issue | Exact observed route | Controlled route |
|---|---|---|---|
| CS01 | DynamoDB-to-EventBridge dual write is non-atomic. | `code/lambdas/order-handler/handler.ts:155-165,167-181,183-208` | C22, C38 / A20, A21, F04 |
| CS02 | Cached idempotency success can mask an order with no valid routing/downstream evidence. | `code/lambdas/order-handler/handler.ts:167-180,229-241` | C08, C32, C40 / A06, A23, A82, F11 |
| CS03 | `PutEvents` partial failures and the nonexistent-bus acknowledgement boundary are ignored. | `code/lambdas/order-handler/handler.ts:193-205`; `code/lambdas/trade-executor/handler.ts:134-145`; `code/lambdas/dlq-replayer/handler.ts:114-133` | C08 / A06, A82 |
| CS04 | Trade insert, order update, and publication are non-atomic. | `code/lambdas/trade-executor/handler.ts:83-118,120-145` | C22, C38 / A20, A21, F04 |
| CS05 | A publication failure becomes unrecoverable through ordinary retry after the duplicate-trade guard. | `code/lambdas/trade-executor/handler.ts:79-104,120-145` | C08, C40 / A06, A82, F11 |
| CS06 | Portfolio locking lacks event idempotency. | `code/lambdas/portfolio-updater/handler.ts:145-186`; `code/shared/types/dynamodb.ts:63-79` | C40-C41 / F11 |
| CS07 | The portfolio depends on ordering that EventBridge does not establish. | `code/shared/types/events.ts:36-47`; `code/lambdas/portfolio-updater/handler.ts:145-178`; `infra/environments/production/main.tf:565-605` | C09, C34, C41 / A07, F01, F09, F24 |
| CS08 | Target DLQ, DLQ-write permission, and Lambda asynchronous failure are different boundaries; the queue policies are missing. | `infra/modules/eventbridge/main.tf:29-38,73-95`; `infra/environments/production/main.tf:575-605`; `infra/modules/lambda/main.tf:12-70`; bounded absence check below | C05-C07, C57 / A01, A03-A05, A81 |
| CS09 | Daily snapshot is one unpaginated filtered Scan assembled in memory. | `code/lambdas/portfolio-snapshot/handler.ts:64-107` | C21, C24 / A19, A22; direct bounded [DynamoDB export route](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/S3DataExport.HowItWorks.html) |
| CS10 | Monetary values use binary floating point. | `code/shared/validation/schemas.ts:21-27`; `code/lambdas/trade-executor/handler.ts:55-63`; `code/lambdas/portfolio-updater/handler.ts:64-84,121-135`; `code/lambdas/portfolio-snapshot/handler.ts:50-61` | C47 / A41, F08, F17 |
| CS11 | No authoritative append-only ledger or reconciliation mechanism is apparent. | `code/shared/types/dynamodb.ts:16-93`; `infra/modules/dynamodb/main.tf:1-60`; repository-wide absence check below | C46-C48 / A32-A35, A41, F08, F17, F18 |
| CS12 | Lambda is not automatically suitable for a latency-critical matching loop. | `code/lambdas/trade-executor/handler.ts:55-81`; `infra/modules/lambda/main.tf:12-31` | C49, C53 / A44-A51, A58-A60, F09, F19, F20 |

Repository-wide absence check used for CS11:

```sh
rg -n -i 'ledger|posting|reconcil|reservation|settlement|clearing' code infra
```

Result: no application implementation was found; the only unrelated match was
the word “reserved” in comments describing future sort-key use. Absence is
therefore stated as “not apparent,” not as proof that no external system could
exist outside this repository.

Repository-wide absence check used for CS08:

```sh
rg -n 'aws_sqs_queue_policy|sqs:SendMessage' infra -g '*.tf'
```

Result: no match. A separate search for `events.amazonaws.com` finds only
`aws_lambda_permission` principals in the production/staging composition files;
none grants `sqs:SendMessage` on either declared DLQ. The queue resources and
`dead_letter_config` blocks therefore show intent, not an operationally
authorized EventBridge-to-SQS path.

## Review notes outside the twelve-item acceptance set

- The production and staging composition files are not identical. In
  production, dlq-replayer IAM includes the separately declared portfolio DLQ
  (`infra/environments/production/main.tf:749-765`); staging grants only the
  module-managed DLQs (`infra/environments/staging/main.tf:697-722`). This does
  not create a thirteenth case-study issue, but a deployment review should
  resolve the environment drift before relying on portfolio redrive in staging.
- The case study does not infer authentication, WAF, CloudTrail, or KMS posture
  from their absence in the inspected application paths. Those belong to the
  later security chapter and require their own complete scope review.
