# Way4 "Mastercard Interchange Interface" vs. Mercury — Similarity & Gap Analysis

**Source reviewed:** `docs/settlement_recon/way-4-mc-interface.txt` (OpenWay Way4™ Mastercard
Interchange Interface Operation Manual, v03.49.30, 26.06.2020) plus the 12 figures supplied
separately in chat (Fig. 1–12: interface functionality, import data model, MC.File Management
and MC.Reports menu trees, MC Received/Sent Files forms, Logical Files, Original Docs and
Log Fields forms).

**Also worked against:**
- `docs/settlement_recon/ipm-clearing-formats-15-march-2022.pdf` — Mastercard's real *IPM
  Clearing Formats* specification (1300 pages). Chapters 1, 2, 5, 7 and 8 read in full; addendum
  and message-reason-code chapters skimmed for structure only.
- `docs/settlement_recon/m-gcmsrefmanual.pdf` — the real *Global Clearing Management System
  Reference Manual*, 3 June 2025 (496 pages) — the very document the IPM spec kept deferring to
  for file transmission and delivery. **It resolves the one gap the first pass of this document
  left unverified**: record framing (§11 update below). Read in relevant part — Chapters 1–3 on
  clearing processing, file delivery and the RDW; the settlement-service and business-service
  chapters for Customer ID/capacity semantics.
- Real Mercury Mastercard membership data (user-supplied, 2026-08-25): two Customer IDs (ICAs
  035862 acquiring, 025241 issuing), Acquirer BIN 957382, card BIN 53255811 — now seeded as real
  configuration, not hardcoded (§7.1 update below).

Every spec claim in this document carries its page number.

**Compared against:** this umbrella as of `feature/monitoring-reports` — principally
`clearing_core`, `settlement_core`, `scheme_core`, `dispute_core`, `reporting_core` and the
`platform_web` Clearing / Reconciliation screens.

**Author's framing:** Way4 is a full issuer **and** acquirer processor with a document/accounting
core (DOC posting, card contracts, service packages, holds, member settlement position).
Mercury TMS is acquirer-side, with the authorisation switch living outside this repo
(`mercury_device_middlelayer`) and — today — an external acquirer (YSP) still absorbing the
scheme clearing leg.

**Mercury is a Mastercard principal member.** That is not a detail; it removes an entire
category from the first version of this analysis. A principal member holds its own ICA, settles
directly with the scheme rather than through a sponsor, is named in the header of every file it
originates, and receives its own settlement position. So the three items originally marked
"N/A — that's a member-settlement processor's job" are **not** N/A: member settlement position,
interbank/member-to-member activity, and per-ICA reconciliation are all Mercury's own
obligations. §2.5 and §7 below are rewritten accordingly.

Verdicts used throughout:

- **MATCH** — we do the same job, possibly by a different mechanism.
- **GAP** — Way4 does it, we would need it, we don't have it.
- **CLOSED** — was a gap in the first pass of this document; built since, with the spec in hand.
- **N/A** — genuinely not ours to do even as a principal member.

---

## 1. Executive summary

### 1.1 Where this stands after the build

| | First pass | After the spec build | After transport + SAFE + sample data |
|---|---|---|---|
| Way4 capabilities substantially matched | 6 | 6 | 7 (dispute cycle + file transport) |
| Partial — model existed, execution leg did not | 5 | 1 | 1 (SAFE, Phase 1 of 3) |
| Closed by this build | — | 9 | **12** |
| Genuine gaps remaining | 9 | 8 | 6 |
| Out of scope even for a principal member | 3 | 1 | 1 |

This session added three things beyond the codec itself, each answering a direct follow-up: real
IPM sample files an operator can open and verify without a database (`mix generate_ipm_sample`,
§9.1), SFTP transport for the Mastercard channel matching the YSP/Bank/AlipayPlus pattern exactly
(§7.2), and a SAFE plan with Phase 1 (case tracking) built (§2.7).

**What the specification changed.** The first pass of this document called the missing IPM
byte framing "the keystone gap" and noted that `IpmParser` could only decode an *already
field-delimited* DE map. With the real spec, the whole message layer is built and validated
against the spec's own worked examples:

- `Ipm.MessageCodec` decodes and encodes real IPM messages — MTI, primary and secondary bit
  maps, every data element in ascending order with its LL/LLL length prefix. Its test suite
  reproduces the spec's p. 37–39 worked example **byte for byte** in both directions.
- `Ipm.Pds` implements the tag-length-data scheme across all five carriers (DE 48 → 62 → 123 →
  124 → 125), including the rules that no PDS may be split across carriers and no tag may be
  duplicated, plus PDS 0158's own nested subfield TLV.
- `Ipm.FileReader` splits a physical transmission into logical files and validates **eight**
  structural rules the spec states, each raising a distinct exception type.
- `Ipm.FileWriter` builds customer-generated files, owning DE 71 sequencing, PDS 0301
  checksums and PDS 0306 counts so a caller cannot get them wrong.
- `Ipm.Charset` carries the CP037 EBCDIC table and detects encoding from the known `1644` MTI
  of a file's first message.

**What the specification did *not* contain, stated plainly:** record framing. The spec defines
message *content* and defers physical framing to the GCMS Reference Manual, which this project
does not have — a search of all 1300 pages for `RDW`, `VBS`, `block` and `1014` returns nothing
on the subject. `Ipm.Framing` therefore implements the conventional 4-byte RDW and 1014-byte
blocked forms with auto-detection, and its moduledoc says exactly this. It is the one module
here not backed by a document in hand, and the only one that changes if a real T112 sample
disagrees.

### 1.2 The correction worth reading first

The pre-spec DE catalog was built from a Visa-TCR ↔ Mastercard equivalence table — the only
reference available at the time — and it was **wrong in nine places**. Six columns named data
elements IPM does not define at all:

| Old mapping | Reality (spec p. 208–210) |
|---|---|
| DE 11 → `stan` | **IPM has no DE 11.** |
| DE 13 → `local_transaction_date` | **No DE 13.** DE 12 is one `YYMMDDhhmmss` value. |
| DE 15 → `settlement_date` | **No DE 15.** Settlement date is PDS 0159 subfield 8. |
| DE 39 → `response_code` | **No DE 39.** Clearing messages carry no response code. |
| DE 60 → `advice_reason_code` | **No DE 60.** |
| DE 90 → `original_data_elements` | **No DE 90.** DE 30 is Amounts, Original. |
| DE 18 → `merchant_type` | MCC is **DE 26** (Card Acceptor Business Code). |
| DE 25 → `pos_condition_code` | DE 25 is **Message Reason Code**. |
| DE 62 → `transaction_id` | DE 62 is a **PDS carrier**, not an identifier. |

Migration `CreateIpmLogicalFilesAndCorrectIpmRecords` fixes all of it. Dropping columns was
safe and verified rather than assumed: nothing could read an IPM file until now, so
`mastercard_ipm_records` was empty in every environment.

**And the identifier was wrong too.** `ClearingBridge` matched Mastercard on DE 37 (Retrieval
Reference Number) because the old catalog knew of nothing better. DE 37 is only *conditionally*
present on a First Presentment/1240 (spec p. 59, Org = C). **DE 31 (Acquirer Reference Data)**
is mandatory, is what the entire chargeback cycle echoes back, and is the correct key. It now
leads, with DE 37 as fallback — and since DE 31 is the same concept as Visa's ARN, both networks
share `core_transactions.acquirer_reference_number`.

The three superseded modules (`IpmParser`, `IpmBuilder`, `IpmDeCatalog`) were **deleted**, not
deprecated. Leaving a catalog that maps MCC to a non-existent DE 18 would be a trap for the next
reader.

## 2. Fig. 1 — interface functionality, flow by flow

Fig. 1 shows eight arrows between the Mastercard Payment System and the Bank. Taking each:

### 2.1 Incoming presentments (clearing) — **CLOSED**

| Way4 | Mercury |
|---|---|
| `IPM Inward Processing` imports `IPM*.i*` T112 files from a watched directory; computes write-off amounts (Accept Clearing) separately from general document posting | `Ipm.Ingestor` reads a real physical file → `clearing_batches` + `clearing_logical_files` + one `mastercard_ipm_records` row per clearing message, then `SettlementCore.ClearingBridge` matches on DE 31 with a 0.01 amount tolerance and applies a `COALESCE(actual, estimate)` `interchange_fee` override |

Both gaps the first pass identified are closed:

1. **Real file reading.** `Ipm.FileReader` walks the framing, decodes each message, groups them
   into logical files, and validates the spec's structural rules. Amounts are converted from
   minor units using the currency's *own* exponent from `scheme_currencies` rather than an
   assumed 2 — correct for AED and USD, and the only way JPY (0) and KWD (3) come out right.
2. **The upload path.** [files.ex](apps/platform_web/lib/platform_web/live/clearing_live/files.ex)
   no longer hardcodes `Tc33Parser`. `detect_network/1` decides from the file's own bytes: a
   file that frames cleanly *and* opens with the literal MTI `1644` in either EBCDIC or ASCII is
   IPM, because a physical file's first message is always a File Header/1644 (spec p. 39). A
   Visa TC-33 file — undelimited fixed 168-byte records — satisfies neither test. Filename masks
   are a deployment convention we cannot rely on for an operator upload.

**PDS values that previously had nowhere to live now have columns**: GCMS Product Identifier
(PDS 0002), Interchange Rate Designator (PDS 0158 subfield 4), Mastercard Assigned ID (PDS 0176),
and Settlement Service ID / settlement date / reconciliation date / settlement cycle (PDS 0159
subfields 4, 6, 8, 9). Everything else stays in `raw_pds_map`, so promoting another tag to a
column later loses no history.

### 2.2 Outgoing presentments — **PARTIALLY CLOSED**

| Way4 | Mercury |
|---|---|
| `IPM Outward Processing` collects documents in `"To be Sent"` status, generates R111, writes to a directory, registers file + message rows for everything exported | `Ipm.FileWriter.build/2` and `build_transmission/2` produce real customer-generated files; `Ipm.FileSequence` allocates the File ID sequence number under a row lock |

**Closed:** the encoder. `FileWriter` owns every rule a caller would otherwise get wrong —
DE 71 starting at `00000001` and incrementing, header and trailer sharing PDS 0105, PDS 0301
hash total over every DE 4 present, PDS 0306 counting header and trailer in. `build_transmission/2`
handles the case that concatenating two `build/2` outputs would silently break: record framing
applies to the transmission as a whole, so blocking must happen once over the joined stream or
the second logical file ends up behind the first one's block padding, invisible to any reader.

PDS 0122 (Processing Mode) is a **required** argument, never defaulted. Submitting test volume
as production is exactly the class of mistake a default hides.

`Ipm.AcquirerReferenceData` builds DE 31 to spec — Mixed Use (1) + Acquirer's BIN (6) + Julian
`YDDD` (4) + sequence (11) + Luhn check digit (1) — and `valid_check_digit?/1` verifies one.

**Still a gap:** the **outbound lifecycle**. There is still no network-facing status on
`core_transactions` (`to_be_sent → sent → accounted | rejected | suspended`), no job that
selects owed transactions and drives `FileWriter`, no submission log, and no re-export control.
The encoder exists; the state machine that feeds it does not. This is now the single largest
remaining item and is #1 in §9.

Also still absent: Way4's intra-processor (on-us) export path, which matters once Mercury both
acquires and issues for the same BIN.

### 2.3 Dispute cycle transactions — **MATCH (different, more modern transport)**

Way4 carries chargebacks, second presentments and retrieval requests as message types *inside*
the same IPM clearing file (Fig. 6 shows `Level = "1 Chargeback"` alongside `Presentment` and
`Administrative` rows in one logical file).

We do the same business function over **Mastercom REST API v6** in `dispute_core`:
`MastercomClient` with from-scratch OAuth1 signing, `MastercomQueueSyncWorker` (pulls
`AcquirerFirstCBUnworked`, dedupes on `mastercard_claim_id`),
`MastercomOutcomeReconciliationWorker`, `RetrievalRequest` + `RetrievalImporter` +
`RetrievalExpiryWorker`, SLA monitoring, evidence storage, automation rules.

**Verdict: no gap — and this is the one place we are ahead of the 2020 Way4 manual.** Worth
noting explicitly for anyone reading the Way4 doc as a to-do list: do **not** re-implement
dispute exchange as IPM message types.

One residual: Way4's `MC Dispute Statistics` report (grouped by institution and inward/outward)
has no direct equivalent; `DisputeCore.Analytics` covers similar ground but is not the
scheme-shaped statistic.

### 2.4 Settlement transactions — **GAP (partly de-risked)**

Two different legs get conflated by Fig. 1's single arrow:

- **Network → us (scheme settlement position).** Way4 receives settlement messages in the IPM
  file and reports them via `MC Settlement Report` / `MC Settlement Result`. We still have no
  ingestion of the position itself. What *is* now in place is the reading machinery and the
  schema: `MessageCatalog` classifies **Settlement Position Detail/1644-688**, **Financial
  Position Detail/1644-685** and **File Currency Summary/1644-680**; `PdsCatalog` names the
  reconciliation PDSs those messages carry (0359 Reconciled Settlement Activity, 0380/0381/0384
  debits/credits/net in reconciliation currency, 0390/0391/0394 the same in settlement currency,
  0395/0396 net fee and net total, 0400–0402 transaction counts); and `Ingestor` **stores** those
  messages rather than discarding them, because they are exactly what an acquirer reconciles
  against. What is missing is the aggregation on top — a settlement-position table per ICA, per
  settlement date, per currency, and its reconciliation against what we cleared.

  This matters more now than the first pass implied. As a principal member Mercury receives its
  own position directly; there is no sponsor absorbing the reconciliation.

  `NetworkReconciliation`'s moduledoc still says a full network-settlement ingestion "is the
  next step". That is still true, and it is now #2 in §9.

- **Us → merchant (payout).** Unchanged and strong: `PayoutGenerator`, `PayoutBatch`/`PayoutItem`,
  `Bank.PayoutTransmitter` + SFTP, `BankConfirmationService`, `SettlementMisGenerator`,
  `VatMisGenerator`, `ledger_core`.

### 2.5 Interbank / member-to-member transactions — **GAP (was wrongly marked N/A)**

The first pass marked this N/A on the reasoning that member-position accounting belongs to a
member-settlement processor. **Mercury is a principal member, so it is one.** A principal holds
its own position with the scheme, and fee collections, adjustments and funds transfers between
members settle against it.

What exists: `MessageCatalog` covers the whole Fee Collection/1740 family — customer-generated
(700), returns (780), resubmissions (781), arbitration returns (782), clearing-system-generated
(783), and funds transfers (790/791) — so these messages are classified and stored rather than
silently dropped. `PdsCatalog` names PDS 0137 (Fee Collection Control Number) and the
0265/0266/0267 initial-presentment / first-chargeback / second-presentment fee data.

What does not exist: any accounting on top. A fee collection is stored as a record and goes
nowhere. This belongs with the settlement-position work in §9 #2 rather than as a separate
project — they are the same ledger seen from two angles.

### 2.6 Additional information (BIN tables, stop lists, currency, MCC) — **PARTIAL**

| Way4 file | Purpose | Mercury |
|---|---|---|
| T067/T167 MPE Daily Update | BIN table + currency/country/MCC handbook updates | `SchemeCore.BinRange` — real Mastercard Account Range (`IP0040T1`) data already imported, `canonical_bounds/1` normalising 6–19 digit ranges. **But loaded via `mix load_scheme_bin_ranges --file=*.csv`, not a native T067 reader.** |
| T068/T168 MPE Full Replacement | full BIN table replacement | **GAP** — no full-replacement semantics (our loader is idempotent upsert with fail-closed overlap validation, which is good, but "replace everything not in this file" is a different operation) |
| T057/T058 Currency Rate File | scheme FX rates | **GAP** — `SchemeCore.Currency` holds ISO codes and `decimal_precision`; no rate table, no rate file loader anywhere |
| T314/T316 Electronic Warning Bulletin | stop list | **GAP** — no stop-list table or loader in any app (`SchemeCore.Bulletin` is *mandate notices* — e.g. AN 1026 — not card stop lists; the name collision is misleading) |

`scheme_core` also has `MccRule`/MCC catalog, `Country`, `HolidayCalendar`,
`SettlementCalendar`, `InterchangeRate`, `Capk`, `EmvAid` — richer reference data than the Way4
chapter covers. The gap is uniformly **ingestion of the scheme's native file formats**, not the
data model.

### 2.7 Fraud prevention services (SAFE) — **PLANNED, Phase 1 built**

Way4 has four SAFE touchpoints: `SAFE Outward Processing` (R847), `SAFE Inward Processing`
(T848 confirmations, T849 monthly), `Generate SAFE Negative Responses` (a nil return for a
calendar month with no fraud reports), and SAFE file monitoring forms.

**No SAFE wire-format specification exists in this project, confirmed by checking both real
documents in hand.** Neither the 1300-page IPM spec nor the 496-page GCMS Reference Manual
mentions "SAFE" even once — a `grep -i safe` across both returns nothing. Both defer to a
separate Mastercard document ("SAFE Support" / "SAFE Reporting Services User Guide") this
project doesn't have; Way4's own manual does the identical thing — *"Rules for working with the
System to Avoid Fraud Effectively in Way4 are described in the document 'SAFE Support'."* So
building a real R847/T848/T849 codec today would mean inventing field layouts — the same mistake
the pre-spec IPM catalog made, corrected only once a real specification arrived (§1.2). This
gap is handled the honest way instead: plan the whole thing now, build only what doesn't require
guessing at a format nobody has confirmed, and leave the codec for when a real SAFE document
does.

**Where SAFE fits versus what already exists.** `risk_core` (rules, hits, policy catalog,
Cat A–D real-time behavioural detection) answers *"is this transaction suspicious right now"* —
a different question from SAFE's *"has this merchant/transaction been reported to Mastercard as
fraud, and did they confirm it."* `MerchantCore.Screening` (the ADDQ Rule 7.1.1 read-only view
over MMS's LSEG/World-Check pre-onboarding checks) is different again — due diligence *before*
a merchant is boarded, not incident reporting *after* fraud is found. SAFE is the missing third
leg: escalating a risk_core detection (or a manual compliance decision) to the scheme, and
tracking what came back. That's why it was built as `RiskCore.SafeCase` rather than bolted onto
either existing module — it's genuinely adjacent to both, identical to neither.

#### Phased plan

**Phase 1 — case tracking (built).** `RiskCore.SafeCase` (`safe_cases` table) gives compliance
operations a system of record today, independent of file automation — the same relationship
`ClearingBatch` had to `Ipm.MessageCodec` before the real spec arrived. It covers:

- **Outward fraud reports** — `direction: "outward"`, `case_type: "fraud_report"`, optionally
  linked to the `risk_core.RiskRuleHit` that triggered it via `Context.create_safe_case_from_hit/2`
  (nullable — a case can also originate from a manual compliance decision with no automated
  hit behind it).
- **The monthly negative report** — `case_type: "negative_report"`, validated to require a
  `reporting_period` and to *reject* a `transaction_id` (a negative report is a periodic filing,
  not a per-transaction one; conflating the two would misrepresent what was actually reported).
  Way4 treats this as mandatory — *"Generate SAFE Negative Responses… preparing outgoing files
  informing that no messages on fraudulent operations have been received… for a calendar
  month"* — and a missed one is itself a compliance gap, which is why it's a first-class case
  type here rather than an easy-to-forget afterthought.
- **Inward confirmations** — `direction: "inward"`, `case_type: "confirmation"`,
  `scheme_case_reference` for whatever identifier Mastercard's response carries (format unknown,
  stored freeform until confirmed).
- `Context.mark_safe_case_reported/2` stamps `reported_at`/`reported_by` — an honest record of
  when an operator actually submitted something through whatever channel exists today (manual),
  not a claim that automation did it.
- **Merchant block/unblock** — added after real domain knowledge arrived ahead of the actual SAFE
  spec: a SAFE case is expected to drive a merchant enforcement decision, not just a report.
  `merchant_action` (`none | block | unblock`) plus `merchant_action_reason/_at/_by` record that
  decision; `Context.block_merchant_via_safe_case/3` / `unblock_merchant_via_safe_case/3` set it,
  and `Context.merchant_blocked?/1` answers "is this merchant currently blocked" from the most
  recent case that recorded an action for them.

  **Scope stated honestly, because it would be easy to over-claim here:** TMS does not sit in the
  real-time authorization path. `SettlementCore.CoreTransaction`'s own moduledoc lays out its
  lifecycle as `unmatched → matched → risk_hold`, and `risk_hold` is set only *after* the EOD
  batch sync — i.e. after a sale already happened at the terminal. Real-time authorization is the
  switch's job (`mercury_device_middlelayer`), outside this repo. So `merchant_blocked?/1` today
  is a compliance-visible fact, not an enforcement mechanism — it is **not yet** wired to gate
  `core_transactions.risk_hold` for that merchant's future transactions, and real-time blocking at
  the terminal would need separate coordination with the switch team. Wiring either is a distinct,
  undecided next step; recording the decision correctly first is what this phase does.

No screen was built in this pass (all interaction is via `RiskCore.Context`); a CRUD screen
mirroring `/admin/schemes/member-institutions`' pattern is the obvious next increment and needs
no further design work.

**Phase 2 — file format (blocked on a real document).** Once the "SAFE Support"/"SAFE Reporting
Services" specification is available, this is where `Ipm.MessageCodec`'s approach repeats:
decode/encode against the real byte layout, validate against real worked examples, and — per the
same GCMS-manual-informed lesson from Framing (§1.1) — check whether SAFE files travel over the
same RDW-framed channel `Ipm.Framing`/`Ipm.SftpClient` already handle, or a distinct one. If the
same channel, most of the transport layer built in this pass (§7.2) is reusable directly; the new
work is purely the SAFE message codec.

**Phase 3 — automation.** Once Phase 2 exists: auto-draft a `SafeCase` from qualifying
`risk_rule_hits` (which hits qualify is a real policy question for compliance, not an
engineering default to invent), auto-generate the file, and a scheduled job for the monthly
negative report so it can never be silently missed — the exact failure mode Way4's own manual
implies is possible by giving it a dedicated menu item.



### 2.8 Reports — **GAP (large)**

Fig. 12's `MC.Reports` folder holds 13 items. Mapping every one:

| Way4 report | Mercury equivalent |
|---|---|
| MC Submission Report (briefly) | — |
| MC Acknowledgement Report (briefly) | — |
| MC Notification Report (briefly) | — |
| MC Settlement Report (briefly) | — |
| MC Settlement Results (briefly) | — |
| MC Conversion Report (briefly) | — |
| IPM Incoming Files Summary Report | partial — Clearing Dashboard batch counts, no amounts by transaction type/condition |
| IPM Outgoing Files Summary Report | — (nothing outbound exists to report on) |
| IPM Inward Monetary Transactions Report | partial — `ClearingLive.BatchDetail` lists decoded records, not grouped by settlement currency / clearing file |
| IPM Outward Monetary Transactions Report | — |
| ATM Location | **N/A** — no ATM estate |
| MC Reject Report | — (no reject feedback loop exists) |
| MC Dispute Statistics | partial — `DisputeCore.Analytics` |

**The inputs now exist even though the reports do not.** Every message class those six reports
summarise is classified and stored — presentments, chargebacks, fee collections, and the three
reconciliation message types — with amounts, currencies, IRDs and settlement dates on each row,
grouped by logical file. The reports are now aggregation over a populated table rather than
work that has to wait on ingestion. They still depend on §9.2 #1 and #2 for the outbound and
funded halves.

`reporting_core`'s 19 reports (`GrossDailySalesVolume`, `AverageTicketSize`,
`RetrievalsAndChargebacks`, `IssuerBinDistribution`, …) are **merchant/transaction monitoring**
reports, a different family entirely — they answer "is this merchant behaving oddly", not "did
Mastercard accept and fund what we submitted". Likewise `SettlementMis`/`VatMis` are
merchant-facing. **None of Way4's six core reconciliation reports has an analogue.**

---

## 3. Fig. 2 — the import data model

Way4: `FILE_INFO` (physical file, and separately each logical file) → `ORIGINAL_DOC` (one row
per message, all fields stored **packed**, unpackable later for field-level analysis) →
`DOC` (posted documents) + `ADDENDUM_DOC` (additional document parameters).

Ours:

| Way4 | Mercury | Note |
|---|---|---|
| FILE_INFO (physical) | `clearing_batches` | close match — filename, date, size, counts, status lifecycle, `uploaded_by`, `processed_at`, `processing_error` |
| FILE_INFO (logical) | `clearing_logical_files` — **CLOSED** | one row per File Header→File Trailer pair, with PDS 0105 decomposed into its four subfields, PDS 0110 Transmission ID, PDS 0122 processing mode, declared vs. actual message count and checksum, and the structural `problems` list |
| ORIGINAL_DOC (packed message) | `mastercard_ipm_records` / `visa_tc33_records`, with `raw_de_map` JSON | **conceptually the same idea, better executed** — `raw_de_map` preserves every DE verbatim including uncataloged ones, and needs no "Unpack All" step (Fig. 7's whole workflow) |
| DOC | `core_transactions` | match, via `ClearingBridge` |
| ADDENDUM_DOC | **partly closed** | every PDS is now decoded and kept in `raw_pds_map`, so DE 48 is no longer an opaque string, and `FileReader` enforces the spec's rule that a Financial Detail Addendum/1644 must immediately follow its presentment (p. 39). What is still missing is the 0500-series addendum *content* (passenger transport, lodging, vehicle rental, corporate line item) as its own table, and therefore the addendum-vs-MCC compatibility check Way4 does on export |

**Remaining structural gap:** addendum content decomposition (the 0500-series tags). The
logical-file level is closed.

**Structural strength worth keeping:** `raw_de_map` *and* `raw_pds_map` alongside named
columns. Way4's packed-blob-plus-unpack-tool design is a product of 1990s storage economics and
needs an explicit "Unpack All" step (its Fig. 7 workflow) before a field is readable; ours is
queryable as stored. The prediction in the first pass held: when the real framing arrived, only
the byte→DE-map extraction step was new — separating the semantic catalog from the framing was
the right call, even though the catalog's *contents* turned out to be wrong (§1.2).

---

## 4. Chapter 3.3 — file monitoring screens (Figs. 3–9)

| Way4 form | Mercury screen | Verdict |
|---|---|---|
| MC Received Files (Fig. 4) | [/admin/clearing/files](apps/platform_web/lib/platform_web/live/clearing_live/files.ex) + [/admin/clearing/batches](apps/platform_web/lib/platform_web/live/clearing_live/batches.ex) | MATCH, plus we have upload, which Way4 handles by copying to a watched directory |
| MC Sent Files (Fig. 8) | same screens, `direction: "outbound"` | model exists, **nothing ever writes an outbound batch** |
| MC Incoming/Outgoing Pending Files | — | GAP — no in-flight view; our lifecycle jumps `received → processing → matched\|error` inside one request |
| Logical Files (Fig. 5/9) | `clearing_logical_files` | **CLOSED** at the data layer — one row per logical file with counts, File ID subfields, processing mode and problems. The drill-down *screen* is not built yet; `Context.list_clearing_logical_files/1` and `count_records_by_logical_file/1` exist for it |
| Original Docs (Fig. 6) | [ClearingLive.BatchDetail](apps/platform_web/lib/platform_web/live/clearing_live/batch_detail.ex) | **data CLOSED**, screen pending. Every column Way4 shows now exists on the record: `message_type` is the `Level` column ("First Presentment", "First Chargeback (Full)", "Financial Position Detail"), `message_type_indicator` + `function_code` are the Message Code, `message_number` is DE 71, and `transaction_originator_institution_id` / `transaction_destination_institution_id` are Source and Target Member ID. `MessageCatalog.label/2` is what produces the label. The batch-detail template has not been extended to render them |
| Log Fields (Fig. 7) | `raw_de_map` + `raw_pds_map` on the record | data present and now *named* — `DeCatalog.name/1` and `PdsCatalog.name/1` give every DE and PDS its spec name, which is exactly what Fig. 7's two-column form shows. **Still no UI** rendering it |
| [Messages] button (process log per file) | `clearing_exceptions`, now carrying structural findings | **partly closed** — every structural violation `FileReader` finds becomes a `clearing_exceptions` row against the batch, naming the logical file and the rule broken. That is Way4's process log for the failure cases. What is still missing is the *informational* log (what was read, in what order, how long it took) — `settlement_file_audits`' shape, which clearing still does not share |
| File Export Reversal (Fig. 10) | — | **GAP** — no rollback of an import or an export anywhere in `clearing_core` |
| "Paid" marking (Fig. 11) → Outward Status `Accounted` | — | **GAP** — no way to record "settlement received for this file's documents" |

The nearest thing we have to Way4's file-lifecycle audit is the settlement side's
[File Tracker](apps/platform_web/lib/platform_web/live/reconciliation_live/file_registry.ex) and
[File History](apps/platform_web/lib/platform_web/live/reconciliation_live/file_history.ex)
pair (`settlement_file_audits`, with `reprocess` as an action) — a good pattern that
`clearing_core` does **not** currently share. Reusing it there is cheap and closes three rows of
this table at once.

---

## 5. Chapter 3.4 — the error taxonomy

This is the most under-appreciated part of the Way4 document. It enumerates a controlled
vocabulary of import/export failures. Ours is `ClearingException.exception_type` plus a free-text
`processing_error`.

**Import-side controls — mostly CLOSED.** `FileReader` validates eight structural rules and
`ClearingException` gained a distinct type per rule, so an operator filters on the specific
failure rather than on "malformed file":

| Way4 control | Mercury now |
|---|---|
| `"Member ID <member> not on file"` | **partly closed** — `scheme_member_institutions` exists (§7) and DE 32/33/93/94/100 are stored per record, but nothing yet *rejects* a file naming an unregistered ICA |
| `"File ID <ID> from <name> Already Processed"` | **still open** — we dedupe on `clearing_batches.filename` only. PDS 0105 is now parsed and stored per logical file, so the check is a query away, but it is not written. Note Mastercard's own rule is harsher than ours: a duplicate File ID rejects *the entire file* (spec p. 552) |
| `"<FILE> is a test file. Do you want to continue?"` | **CLOSED** — PDS 0122 is read per logical file and `Ingestor` refuses test files unless `allow_test_files: true` is passed explicitly. The Clearing Files screen surfaces the refusal with the offending File IDs |
| `"Missing logical file header"` / `"Unexpected file header message"` | **CLOSED** — `missing_file_header`, `missing_file_trailer`, `file_id_mismatch`, `empty_logical_file` |
| `"Invalid message sequence number"` | **CLOSED** — `bad_first_message_number` (DE 71 ≠ 00000001 in the header) and `message_number_not_increasing`, both per spec p. 48 |
| Trailer total reconciliation | **CLOSED and beyond Way4's list** — `message_count_mismatch` (PDS 0306) and `checksum_mismatch` (PDS 0301) compare the trailer's declarations against what the file actually contains, per spec p. 49 |
| Pre-validation | **partly closed** — validation happens during the read rather than as a separate pre-pass, and a logical file that fails does not stop its siblings loading. That is arguably better than Way4's all-or-nothing prevalidation, but it is a different guarantee, not the same one |

Two rules were implemented that Way4's error list does not mention, because the spec states them:
`orphan_addendum` (a Financial Detail Addendum/1644 not immediately following a presentment) and
`dangling_message_exception` (a Message Exception/1644 with no rejected message after it), both
p. 39.

**Export-side controls Way4 has and we don't** — these are the revenue-affecting ones:

- `"Transaction was downgraded. Use Repost"` — **interchange downgrade detection.** Silent
  downgrades are one of the classic sources of unexplained acquirer margin leakage.
- `"IRI in ARN <ARN> Not valid for ECRD"` / `"AID in ARN Not valid"` — Interchange Rate
  Designator validity per processing class.
- `"Illegal SIC code <SIC> for ECHA transaction"` / `"SIC Code incompatible with transaction
  type"` — MCC ↔ transaction-type compatibility.
- `"Chip Data not present"` / `"Addendums requested in TRANS_CONDITION but not present"` —
  transaction-condition consistency.
- `"Posting date less than transaction date"` — date sanity.

**Still open, and now unblocked.** The first pass said this layer "should not wait for the file
framing". The framing is done, and the inputs are all in place:

- The **Interchange Rate Designator is now decoded** — PDS 0158 subfield 4, via
  `Pds.interchange_rate_designator/1`, stored on every record. Downgrade detection is
  comparing the IRD Mastercard returned against the one we submitted; both sides of that
  comparison now exist in the same table.
- `IpmRecord` carries the correct fields to check: `card_acceptor_business_code` (DE 26, the
  real MCC), `pos_data_code` (DE 22, all twelve positions), `processing_code` (DE 3),
  `acquirer_reference_data` (DE 31, whose check digit `AcquirerReferenceData.valid_check_digit?/1`
  verifies).
- `SchemeCore.InterchangeRate` carries MCC, transaction type, scope and card product;
  `MccRule` exists.

What is missing is the layer itself: nothing yet compares a submitted IRD to a returned one,
validates MCC against transaction type, or checks chip-data consistency. This is now #4 in §9
and is genuinely self-contained work.

---

## 6. Chapters 7–8 — merchant contracts and MoneySend

### 6.1 Mastercard Assigned ID (Ch. 7) — **CLOSED on the inbound side**

Way4 stores a scheme-assigned merchant identifier as a contract parameter, settable at merchant
**and** device-child-contract level, explicitly *"provided to the acquirer by Mastercard and may
not be independently specified by the Bank."*

In IPM this is **PDS 0176 (Mastercard Assigned ID)**, `ans-6` (spec p. 627), and it is now
decoded and stored on every record as `mastercard_assigned_id`. PDS 0158 subfield 11 (Mastercard
Assigned ID Override Indicator) is also catalogued.

**Still open:** the merchant-side half. `SettlementCore.TidMaster` and
`MerchantCore.LocationDetail` still hold only *our* identifiers (`mid`, `merchant_ref_number`,
`tid[]`, `mms_store_id`). Storing the Assigned ID on the merchant record — so it can be *sent*
on an outbound presentment, and so Mastercard's ADDQ / Location Compliance data joins to our
locations — is a small, well-defined addition, now #7 in §9.

### 6.2 MoneySend (Ch. 8) — **GAP, likely partly out of scope**

Way4 converts a MoneySend authorisation into a financial document online, sets a credit hold
(`PENDING=Y`), then matches the later clearing document (`Source Code 124020028A`,
`MC_FAST_FUNDS=Y`) as an **acknowledgement**, falling back to treating it as a presentment when
no original is found.

Nothing in this repo handles MoneySend's contract/hold mechanics. Per
`docs/Clearing-System/requirement-summary.md` §5, the switch (`mercury_device_middlelayer`)
already has a Mastercard Send / MPGS adapter, so the *disbursement* leg may exist outside TMS.

What the IPM build does add is the reading half of §8.5's control: PDS 0105 subfield 1 value
**905** is "Clearing file: Generated by Mastercard SEND" (spec p. 553) and
`ClearingLogicalFile.file_type_label/1` names it, so a Mastercard Send clearing file is now
identifiable as such on arrival rather than indistinguishable from ordinary clearing volume.

**Still open:** the clearing-acknowledgement matching itself — "did the clearing file confirm the
money we already released?" If Mercury runs Mastercard Send in production today, that control is
missing, and it is worth confirming scope with the switch team before building it.

---

## 7. Cross-cutting: the three things Way4 takes as given

### 7.1 ICA / Member ID registry — **CLOSED, now with real data and multi-instance/multi-scheme confirmed**

Unchanged from the previous update in structure; two things sharpened since:

**Capacity — acquiring vs issuing — is now modeled.** Real Mercury membership data surfaced a
case the first version of `MemberInstitution` didn't have a field for: **Mercury holds two
separate Customer IDs under one Mastercard membership** — ICA `035862` for acquiring, a second,
dedicated ICA `025241` for its issuing program ("Program Segmentation"). The GCMS manual confirms
this is normal Mastercard practice, not a Mercury oddity, and that acquirer/issuer is actually a
**per-transaction role** (which DE carries the Customer ID — DE 94 sending = acquirer on a
presentment, DE 93 sending = issuer on a chargeback) rather than an inherent property of an ICA —
its own worked example states *"Customer ID 001234 is both an issuer and an acquirer."* Mercury
segmenting into two ICAs is its own choice, which the spec supports but doesn't require. A new
`capacity` field (`acquiring | issuing | both`) records that choice per row, and the "must have an
Acquirer BIN to activate" validation now checks it — a pure `issuing` row is correctly exempt,
since issuing activity never populates DE 31.

**Real data now seeded, proving the mechanism rather than just building it.**
`priv/repo/seeds/mastercard_member_institutions_seed.exs` creates both of Mercury's real ICAs and
its real Acquirer BIN as data:

| Value | Where it lives | What it is |
|---|---|---|
| ICA `035862`, capacity `acquiring` | `scheme_member_institutions` | Fills DE 32/33/94 and PDS 0105 subfield 3 on outbound files |
| Acquirer BIN `957382` | same row, `acquirer_bin` | DE 31 subfield 2 — confirmed by the GCMS manual to be the same field as its own "Acquirer Reference ID" (p. 39, p. 203) |
| ICA `025241`, capacity `issuing` | `scheme_member_institutions` | Segmented issuing Customer ID; no Acquirer BIN required |
| Card BIN `53255811` | `scheme_bin_ranges` (`SchemeCore.BinRange`), **not** the institution row | A different concept from the Acquirer BIN above — identifies cards Mercury issues, not the acquiring institution. Landed `draft`, matching the precedent for narrow ranges nested inside coarser active Mastercard ranges |

Getting the Acquirer BIN and card BIN confused would have repeated the exact category error the
pre-spec build made conflating DE 31 with DE 37 (§1.2) — worth stating explicitly because the two
real numbers *look* similar enough to invite exactly that mistake.

**Multi-instance and multi-scheme were already true by construction, now exercised.** Adding a
second acquiring identity — another Mastercard ICA, or a Visa/JCB principal's ICA once that work
starts — is filling in the `/admin/schemes/member-institutions` form or adding a seed entry, not
editing code: `scheme_id` already ties to `scheme_masters` (which already carries Visa, JCB and
the rest), and nothing in `MemberInstitution` assumes there is only one "our own" row. The seed
file is deliberately data, not logic, for exactly this reason.

### 7.2 File transport to/from Mastercard — **CLOSED (transport primitive); onboarding still open**

Built this pass, mirroring the exact pattern already proven for YSP, the settlement bank and
Alipay+ (`SettlementCore.Ysp.SftpClient`, `Bank.SftpClient`, `AlipayPlus.SftpClient` — same
`:ssh_sftp` wrapping, same config shape):

- **`Ipm.SftpClient`** — bidirectional (list/download/upload/rename), since Mastercard clearing
  needs both directions on one connection, unlike YSP's inbound-only or the Bank client's
  upload-plus-poll split. Disabled by default (`host: nil`, same convention as `:ysp_sftp`) —
  the Clearing Files screen's manual upload keeps working regardless.
- **`Ipm.InboundPollWorker`** — polls rather than fires at a fixed time (`BankAckPollingWorker`'s
  pattern, not `Ysp.SftpFetcher`'s), because the GCMS manual gives six real clearing cutoffs per
  day (p. 36: 18:00, 21:00, 00:00, 03:00, 06:00, 08:00 Central) but no delivery-latency SLA to
  schedule a precise fetch against. Matches by the real bulk-type identifiers from the manual's
  own Bulk IDs table (p. 51: `T112`/`T113`/`T120`/`T121`), downloads, calls `Ipm.Ingestor.ingest/3`,
  archives with a `.done` suffix.
- **`Ipm.OutboundSubmitter`** — the transport half of outbound: writes a `FileWriter`-built file
  locally, records it as an outbound `ClearingBatch` (`generated → submitted | error`), uploads
  when SFTP is configured. Explicitly **not** the outbound lifecycle (§9.2 #1 below) — it has
  nothing to call it yet, by design, so the lifecycle work can call `submit/2` once built rather
  than inventing its own upload path.
- **`Ipm.Scheduler`** — GenServer registered in `ClearingCore.Application`, anchors polling to
  the six real cutoffs (with a stated, provisional Central→UTC offset and delivery buffer — the
  one piece genuinely uncalibrated until a real channel exists). A cron safety net
  (`config.exs`, every 2 hours) covers a missed GenServer cycle, matching the YSP precedent.

**What's still open is onboarding, not code**: no Mastercard connection exists yet (MFE /
CONNECT:Direct / whatever the sponsor mandates), so `:mastercard_sftp` stays `host: nil` in every
environment until one is provisioned. Everything above activates the moment it is.

**The framing question is resolved, not just deferred** (§1.1, §11): the GCMS manual confirms
`:rdw` is what a real integration receives — CONNECT:Direct customers are explicitly unaffected
by RDW handling — so `Ipm.Framing`'s `:rdw` mode is the one to configure, not a coin flip between
two equally-likely options.

### 7.3 Release-cadence management — **still GAP**

Way4 §1.2.1 calls out Mastercard's April/October release cycle. We have `SchemeCore.Bulletin`
with `implementation_status` and a `ComplianceReviewWorker` — the right shape — but nothing
connects a bulletin to the parser/builder version it affects.

This is more concrete now than it was: the DE and PDS catalogs are versioned artefacts derived
from a dated specification (15 March 2022), and the next release will add PDSs and possibly
data elements. `MessageCodec` deliberately **fails loudly** on a bit map flagging a DE it does
not know (`{:undefined_data_element, n}`) rather than returning a plausible partial decode — so
a release we have not implemented announces itself instead of corrupting data. That is the right
failure mode, but it is a tripwire, not release management.

## 8. Where we are ahead of Way4

Stating these so the doc doesn't read as a one-way scorecard:

- **Dispute exchange via Mastercom REST** rather than IPM message types (§2.3) — modern, live
  against the real sandbox, with queue sync, outcome reconciliation, SLA monitoring, evidence
  storage and automation rules.
- **`raw_de_map` / `raw_pds_map` over packed blobs** (§3) — no unpack step, uncataloged DEs and
  PDSs preserved and queryable as stored.
- **Partial-success ingestion** — a transmission's logical files are independent (spec p. 48), so
  one with a broken trailer checksum is flagged while its siblings load normally. Way4's
  prevalidation rejects the file as a unit.
- **Structural findings as typed exceptions** — each of the eight validated rules raises its own
  `clearing_exceptions` type, so an operator filters on the specific failure. Way4 surfaces them
  as free-text process-log messages.
- **Merchant settlement depth** — payout generation, bank transmission and acknowledgement,
  MIS/VAT MIS, `ledger_core` double-entry. Way4's manual stops at the member boundary.
- **Merchant/transaction monitoring** — `reporting_core`'s 19 reports and the payments
  monitoring module have no counterpart in this Way4 chapter.
- **Fail-closed BIN import** — `load_scheme_bin_ranges` validates the whole file for overlaps
  before writing anything.

---

## 9. What was built, and what remains

### 9.1 Built in this pass

| Area | Modules |
|---|---|
| Message codec | `Ipm.MessageCodec`, `Ipm.Bitmap`, `Ipm.DeCatalog`, `Ipm.Charset`, `Ipm.Message` |
| PDS layer | `Ipm.Pds`, `Ipm.PdsCatalog` |
| Message classification | `Ipm.MessageCatalog` |
| Clearing identifier | `Ipm.AcquirerReferenceData` (DE 31 + Luhn) |
| File layer | `Ipm.Framing`, `Ipm.FileReader`, `Ipm.FileWriter`, `Ipm.FileSequence` |
| Persistence | `Ipm.Ingestor`, `ClearingLogicalFile`, corrected `IpmRecord`, two migrations |
| ICA registry | `SchemeCore.MemberInstitution` + context + `/admin/schemes/member-institutions` |
| Corrected matching | `SettlementCore.ClearingBridge` — DE 31 primary, DE 37 fallback, financial messages only |
| Upload path | `ClearingLive.Files` — network detected from file bytes |
| **File transport** | `Ipm.SftpClient` (bidirectional), `Ipm.InboundPollWorker`, `Ipm.OutboundSubmitter`, `Ipm.Scheduler` — real GCMS 6-cycle cadence, `:mastercard_sftp` config, `clearing` Oban queue |
| **Sample data** | `mix generate_ipm_sample` — builds a spec-valid logical file (5 messages: presentments, addendum, chargeback), verifies its own round-trip, writes raw `.ipm` (EBCDIC/RDW), an annotated `.decoded.txt` (every DE/PDS named — Way4's Fig. 6/7 shape), and `.decoded.json`, at `docs/settlement_recon/samples/` |
| **Multi-scheme configuration** | `mastercard_member_institutions_seed.exs` (real Mercury data: ICA 035862 acquiring/957382, ICA 025241 issuing) + `sample_scheme_member_institutions_seed.exs` (clearly-marked placeholder rows for Visa, JCB, Amex, RuPay, UnionPay — `status: "draft"`, never activatable) |
| **SAFE — Phase 1** | `RiskCore.SafeCase` + context — case tracking (fraud reports, monthly negative report, inward confirmations), no file-format codec (no spec exists) |

Test coverage: 65 codec/file tests in `clearing_core` (including a byte-for-byte reproduction of
the spec's own worked example) and 102 database-backed tests in `platform_web` covering
ingestion, reconciliation, the ICA registry, sequence allocation, outbound submission, and SAFE
case tracking. 167 total, 0 failures.

### 9.2 Remaining — P0, blocking a live Mastercard connection

File transport (previously #3 here) is closed — see §7.2. The two structural gaps remain:

| # | Gap | Notes |
|---|---|---|
| 1 | **Outbound presentment lifecycle** | The encoder and the transport (`Ipm.OutboundSubmitter`) both exist; the state machine between them does not. Needs a network-facing status on `core_transactions` (`to_be_sent → sent → accounted \| rejected \| suspended`), a job that selects owed transactions and drives `FileWriter` → `OutboundSubmitter.submit/2`, and re-export control. Way4's §3.3.2.2 "Paid"→Accounted marking is the closing half of this. |
| 2 | **Settlement position ingestion and accounting** | Store and aggregate 1644-680/685/688 into a position per ICA, per settlement date, per currency, and reconcile it against what we cleared. Fee Collection/1740 accounting belongs here too (§2.5) — same ledger, two angles. Sharpened by principal membership. |

### 9.3 Remaining — P1, operational parity

| # | Gap | Notes |
|---|---|---|
| 3 | **Outbound validation layer** | Downgrade detection, IRD validity, MCC ↔ transaction-type, chip-data/addendum consistency. Now unblocked — the IRD is decoded and stored (§5). Revenue-affecting. |
| 4 | **File ID dedupe** | PDS 0105 is parsed and stored; the uniqueness check is a query away and not written. Mastercard's own rule rejects the entire file on a duplicate. |
| 5 | **Logical-file and message-detail screens** | The data is all there — `list_clearing_logical_files/1`, `count_records_by_logical_file/1`, `message_type`, `DeCatalog.name/1`, `PdsCatalog.name/1`. What is missing is rendering: Way4's Fig. 5 (logical files), Fig. 6 (`Level`/Message Code/Member IDs) and Fig. 7 (named field values). `mix generate_ipm_sample`'s `.decoded.txt` output (§9.1) is effectively a preview of what this screen should render. |
| 6 | **Mastercard Assigned ID on merchant/location** | Inbound side closed (PDS 0176 decoded); the merchant-record half is not. Unblocks ADDQ joins. |
| 7 | **Import/export rollback** | Way4 §3.3.2.1, including its own warning that a re-export reuses the same File ID. |
| 8 | **The six reconciliation reports** | Submission, Acknowledgement, Notification, Settlement, Settlement Results, Reject. These close the submitted↔accepted↔funded loop and depend on #1 and #2. |
| 9 | **SAFE case-management screen** | `RiskCore.SafeCase` + context exist (§2.7 Phase 1); no UI yet. Mirrors `/admin/schemes/member-institutions`'s pattern directly. |
| 10 | **Wire SAFE merchant blocking to an actual enforcement point** | `Context.merchant_blocked?/1` exists and is correct, but nothing reads it yet. Two separate, undecided integrations: (a) TMS-side — set `core_transactions.risk_hold` for a blocked merchant's future transactions post-EOD-sync, the same mechanism the risk engine already uses; (b) real-time — stopping a sale at the terminal needs coordination with the switch team (`mercury_device_middlelayer`), outside this repo. Neither is a default to pick unilaterally. |

### 9.4 Remaining — P2, coverage

| # | Gap | Notes |
|---|---|---|
| 11 | **SAFE Phase 2/3** (real file codec, automation) | Blocked on a real "SAFE Support" specification — see §2.7. Phase 1 (case tracking, including merchant block/unblock) is built. |
| 12 | **Native T067/T068 MPE loader, T057/T058 FX rates, T314/T316 stop list** | Data models mostly exist; ingestion does not. T068 full-replacement semantics need explicit design. |
| 13 | **0500-series addendum content** | Financial Detail Addendum message *structure* is validated; its content is not decomposed. Prerequisite for #3's addendum checks. |
| 14 | **Bulletin → catalog version linkage** | Turns `SchemeCore.Bulletin` from a register into release management (§7.3). |
| 15 | **Informational per-file process log** | Structural failures are logged as exceptions; the "what was read, when" audit trail is not. Reuse `settlement_file_audits`' shape. |
| 16 | **Real member data for schemes beyond Mastercard** | `sample_scheme_member_institutions_seed.exs` (§9.1) proves the mechanism with clearly-marked placeholders; real Visa/JCB/etc. onboarding data replaces it scheme-by-scheme as it arrives, the same way Mastercard's real data replaced nothing because it was the first. |

### 9.5 Genuinely N/A

- **ATM Location report** — no ATM estate.
- MoneySend contract/hold mechanics — issuer-side; confirm the clearing-acknowledgement control
  with the switch team (§6.2).

Interbank/member-to-member is **no longer** on this list — see §2.5.

## 10. Corrections made

Both items the first pass flagged are fixed:

1. **`ClearingLive.Files` invited Mastercard IPM uploads it could not handle** — the dropzone
   offered both networks while the module was hardcoded to `@source "visa_tc33"` and
   `Tc33Parser`, so an IPM file was parsed as fixed 168-byte Visa records. Now `detect_network/1`
   routes on the file's own bytes and the IPM path is real.
2. **`SchemeCore.Bulletin` reads like a stop list and isn't** — still true, still worth
   remembering. In Mastercard vocabulary the Electronic Warning Bulletin (T314/T316) *is* the
   stop list; our `scheme_bulletins` table holds mandate notices (AN 1026 and similar). If a stop
   list is built (§9.4 #11), do not name it `Bulletin`.

A third correction, larger than either, is §1.2: nine wrong DE mappings in the pre-spec catalog,
the wrong match key in `ClearingBridge`, and three superseded modules deleted rather than left
in place to mislead.

## 11. What to trust, and what to re-check against a real file

Stated explicitly so nobody over-reads the green test suite.

**Solid — validated against the specification's own worked examples:**

- Message structure: MTI, both bit maps, DE ordering, LL/LLL length prefixes. The spec's
  p. 37–39 example round-trips byte for byte in both directions.
- PDS encoding: the p. 455 DE 48 example decodes and re-encodes exactly.
- DE and PDS attributes: taken from the spec's own listings (p. 208–210, p. 457–471), each
  cross-checked on its definition page.
- Structural rules: all eight cite a page.
- DE 31 structure and Luhn check digit: p. 349, verified against the textbook modulus-10 result.

**Now also confirmed — the GCMS Reference Manual closed the one gap the first pass left open:**

- **Record framing.** `Ipm.Framing`'s `:rdw` mode — a 4-byte length-inclusive-of-header prefix
  before each message — is no longer inference; the *Global Clearing Management System Reference
  Manual*, 3 June 2025, p. 54 states it directly: *"A Record Descriptor Word (RDW) field appears
  before each IPM message… a four-byte Record Descriptor Word field immediately preceding each
  IPM message holds the length of the data in each message."* The manual also settles which
  framing a modern integration actually sees: its delivery-method table (p. 55) states the RDW
  *"does not affect customers that send or receive IPM clearing files… through CONNECT:Direct"* —
  the modern, non-mainframe channel — meaning `:rdw` is the confirmed default and `:blocked_1014`
  (literal 1014-byte block chunks) is kept only as a fallback for a genuinely legacy raw-MIP
  integration, which no delivery path the manual describes actually hands a customer at the
  application layer.

**Still assumed — no document confirms these:**

- **EBCDIC code page.** CP037 is what the spec names for MVS interchange, but the spec is
  formally encoding-independent. A sponsor delivering a different code page would corrupt
  character fields while leaving amounts and bit maps intact — which would look like bad data
  rather than a decode failure, so check field *content* on the first real file, not just that
  it parsed.
- **Currency exponents.** Amount conversion reads `scheme_currencies.decimal_precision` and
  falls back to 2 when the currency is unknown to us. PDS 0148 (Currency Exponents) carries the
  authoritative exponent per message and is stored in `raw_pds_map` but is **not yet** consulted
  in preference to the table. For a file containing JPY or KWD, verify the amounts.
