この銀行システムの仕組み
エンジニアでなくても、「振込ボタンを押した後、裏側で何が起きているか」がざっくり分かるように書いたページです。専門用語より先に、身近な言葉で説明します。
Minibank全体の地図、システム開発の基本、学び方の土台を作る。
先に学ぶ内容 — Prerequisite: なし学ぶこと — LEARNING GOALS
このページで学ぶこと
- 振込が「安全に」完了するまでの10段階の流れ
- 銀行の残高が単なる数字ではない理由(複式記帳)
- この銀行がどうやって「安全性を意識している」と言えるのか
- 裏側で動いている監視・検査の仕組み
- 現時点で 407件 の自動テストが毎回チェックしていること
体験10 · HOW IT WORKS(全体像の日常語版)
振込ボタンの裏側にある、10段階のチェックを確かめよう
まずは予想をひと言。本文を読んだ後、実際の画面と記録を見比べます。
- 01まず予想する — PREDICT
まずここだけ:思ったことをひと言書く
振込ボタンを押した後、その処理が「安全に完了した」とどうやって確認できると思いますか?
正解でなくて大丈夫です。「〜だと思う」から始めてみましょう。
ヒント1を見る
「成功」という表示は、どの記録を根拠にしていると思いますか?
ヒント2を見る
10段階のうち、どれか1つでも失敗したらお金は動くでしょうか?
もっと詳しく:この問いで使う専門用語
- 複式記帳は、1回のお金の動きをマイナスとプラスの2つの記録として同時に残す方法です。
- Idempotency(冪等性)は、同じ依頼を何度送っても結果が1回分だけになる性質です。
- Audit Log(監査ログ)は、誰が・いつ・何をしたかを消せない形で残す記録です。
- Integrity Check(整合性検査)は、帳簿の合計と実際の残高がズレていないかを機械的に見張る仕組みです。
日常語で理解する — PLAIN LANGUAGE
まず、ここだけつかむ
振込の途中でお金の記録の更新に失敗すると、その部分だけ元の状態に戻します。ただし、振込を依頼した記録自体は消えず、「失敗した」という記録として残ります。この考え方を「ロールバック」と呼びます。
振込の本人確認には、スマートフォンの認証アプリ(TOTP)が表示する6桁のコードを使います。銀行側がコードを作って画面に表示することはなく、銀行のデータベースにコードは保存されません。確認は「この振込の内容」に結び付けられ、一度しか使えません。ただし、認証アプリのコード自体は振込先や金額を証明するものではありません。
振込ボタンを押してから残高が動くまで、実は10個のチェックを順番に通っています。どれか1つでも引っかかれば、お金は動きません。
残高は書き換えられる1つの数字ではなく、これまでの取引記録を積み上げた結果です。過去の記録は消さず、間違いは打ち消す記録を追加して直します。
安全性は1つの仕組みではなく、本人確認・権限・行単位の制限・使い捨てコードなど、何重にも重ねた仕組みで成り立っています。
順番に理解する — DIFFICULT PARTS, STEP BY STEP
難しいポイントを、順番にほどく
専門用語を覚えることより、「なぜ必要か」「このシステムのどこで使うか」を理解することが目標です。
複式記帳
1回のお金の動きを「マイナス」と「プラス」の2つの記録として同時に残す方法です。
片方だけ記録されると、帳簿上でお金が消えたり増えたりしてしまうためです。
振込元の口座からマイナス、振込先の口座へプラスという2行を、必ず同じtransactionの中で作成します。
Idempotency(冪等性)
同じ依頼を何度送っても、結果が1回分だけになる性質です。
通信エラーで利用者が「もう一度送信」しても、二重に振込が実行されると困るからです。
依頼ごとに発行される一意なキーで、同じ内容の再送は最初の結果をそのまま返します。
Audit Log(監査ログ)
誰が・いつ・何をしたかを、あとから消せない形で記録する仕組みです。
何か問題が起きたときに、原因と経緯を後から正確に説明できるようにするためです。
重要な操作のたびに、追記専用の監査記録を残します。
Integrity Check(整合性検査)
帳簿の合計と実際の残高がズレていないかを、機械的に見張る仕組みです。
バグや想定外の操作で帳簿がズレても、気づかずに放置されるのを防ぐためです。
定期的に台帳の合計と口座残高を突き合わせ、ズレがあれば検出します。
発展 — ADVANCEDもっと詳しく確認する最初の学習では読まなくても問題ありません。仕組みをもっと深く理解したい場合に開いてください。
金融更新を戻す範囲と、失敗後に残す記録
金融更新の仕訳・transfers・残高cache・成功audit(TRANSFER_COMPLETEDと内側の変更trigger)は、confirm_transfer_otpの内側の例外blockでまとめてrollbackします。先に保存済みのrequestは消えません。transfer_auth.consumeによる認可済みintentの消費(consumed_at)、request=failed、TRANSFER_FAILEDは内側のrollbackの対象外で、外側のDB transactionがcommitすると残ります。
TOTPコードの正誤はDBではなくSupabase Authが判定します。DBはNext.jsのtransfer_mfa_broker経由で検証前に試行を予約し、その時点でintentのattemptsを加算します(累積5回まで、間隔30秒以上)。認証に失敗しても変わるのはintentとAuth側の状態だけで、requestはpending_otpのまま、金融データも変わりません。新しい認可がないままconfirmすると、transfer_auth.consumeが42501で拒否し、その呼出し全体がrollbackします。request=failedは金融更新の例外を捕捉した経路です。未捕捉例外・deferred constraint・commit自体の失敗なら、その呼出し全体の変更がrollbackするため、失敗記録の永続化を無条件には保証しません。
| 場面 | 金融更新 | 外側commit後の状態・event |
|---|---|---|
| 依頼作成 | まだ記帳しない | pending_otp、TRANSFER_OTP_REQUESTED(event名は従来のまま)。確認コードは生成しない |
| TOTPの誤入力・Auth検証失敗 | 資金移動に入らない | 予約時点でintent.attemptsが加算済み(累積5回まで)。requestはpending_otp |
| 新しい認可なしのconfirm(未認可・60秒超過・別session・内容変更) | consumeが42501で拒否し、その呼出し全体をrollback | requestはpending_otp、intentは未消費のまま |
| 認可後の資金移動で捕捉した例外 | 内側の仕訳・transfer・cache・成功auditをrollback | intent消費済み、request=failed、TRANSFER_FAILED |
| 成功 | 金融更新と成功auditを保存 | request=completed、transfer_id、intent消費済み |
| 外側transaction/commit失敗 | その呼出し全体をrollback | その呼出しの失敗eventも保証しない。過去のcommit済み依頼とは別 |
振込intentに束縛した認証アプリTOTPと、再試行・上限契約
v7(migration 20260920074653と20260920083426)の振込認可は、Supabase Authの認証アプリTOTPを振込intentへ束縛する方式です。request_transferは確認コードを生成・返却せず(otp_plaintext列は応答互換のため残り、常にnull)、reissue_transfer_otpは常に42501で拒否します。prepare_transfer_mfaは所有者・Auth session・送金元・送金先・金額・memo・依頼人名・idempotency key・期限のhashをintentへ固定します。ブラウザはコードをSupabase Authへ直接送り、Next.jsとDBはコードも、そのhashも受け取らず保存もしません。DBはAuth所有のchallenge検証時刻、AAL2 session、同じsessionのTOTP AMRを読んで認可し、confirmは60秒以内の認可を1回だけ消費します。DEMO_OTP_DISPLAY_ENABLEDでDBのコード生成は復活しません。TOTPは時刻ごとの値で、振込先や金額への署名ではなく、フィッシング耐性も主張しません。稼働DBへの適用はこの教材ではUNKNOWNです。
| 対象 | コード上の契約 |
|---|---|
| intent | kind+requestごとに1件。owner・Auth session・支払内容のhashを固定。期限は作成から5分以内かつ依頼期限まで。内容やsessionが変わると使えない |
| 検証試行 | 検証前にserverが予約し、その時点で加算。intentごとに累積5回、試行間隔30秒以上。失敗・結果不明も回数に含む |
| challenge | intent作成後に作られ、束縛時点で作成30秒以内の未検証TOTP challengeだけを束縛。束縛は最大4件、間隔30秒以上。新しいchallengeで試行回数はリセットしない |
| 認可 | 予約より後にAuthが検証し(60秒以内)、intentのsessionがAAL2かつ同じsessionのTOTP AMRが予約・検証以降。別intentの認可から90秒以内は拒否 |
| 消費 | confirm_transfer_otpがtransfer_auth.consumeで1回だけ消費。認可から60秒以内・同じsession・未消費・内容hash一致が条件 |
| 旧再発行RPC | reissue_transfer_otpは常に42501。コードの再発行ではなく、新しいAuth challengeを束縛し直す |
| 期限 | 依頼は作成から15分。期限切れの依頼をconfirmするとrequest=expired。intentを別sessionへ移したり復活させたりしない |
| completedのconfirm再試行 | 所有者確認後、同じtransfer_idを返す。再送金も新しい認可の消費もしない |
| request冪等性 | 同一user+keyで送金先・金額・trim済み依頼人名・正規化memoを比較。相違はTR009。同内容pending_otp/completedは同じrequestを返す(otp_plaintextは常にnull) |
| 終了した依頼のkey | failed/expired/cancelled等でのrequest key再利用はTR009。confirmの完了再取得とは区別する |
| execute_transfer ACL | PUBLIC/anon/authenticated/service_roleのEXECUTEを剥奪。definerのconfirmから内部実行。旧認証済みPOST /transfersは409 OTP_REQUIRED |
| 振込上限 | 1回500,000円(依頼時にも検査)、送金元口座ごとJST日次1,000,000円。口座lock後、確定済みtransfersを集計 |
| 頻度 | 新規requestはuserごと直近1分5件まで。資金移動は送金元口座ごと直近1分10件まで。超過TR012 |
1. 安全な振込 — お金は10段階のチェックを通る
「振込」ボタンを押してから残高が動くまで、実はこのシステムは10個の関門を順番に通しています。どれか1つでも引っかかれば、お金は動きません。
- 01入力:振込先口座番号と金額を入力する
- 02本人確認状態チェック:本人確認(KYC)が済んでいるか確認する
- 03口座状態チェック:振込元・振込先の口座が凍結されていないか確認する
- 04残高確認:振込元の残高が足りているか確認する
- 05振込上限チェック:不正な大口送金を防ぐための上限(仕組み自体は準備段階)
- 06TOTP認可:認証アプリの6桁コードをSupabase Authで検証し、この振込に束縛した認可をDBが1回だけ消費する
- 07Idempotency確認:同じ依頼を二重に送っても、二重に振込しない仕組み
- 08Ledger記帳:振込元をマイナス、振込先をプラスとして、同時に記録する
- 09Audit Log:「誰が・いつ・何をしたか」を消せない形で記録する
- 10Integrity Check:あとから帳簿の合計が合っているかを機械的に検査する
| 段階 | なぜ必要か(平易な説明) | このシステムでの実装 |
|---|---|---|
| 本人確認状態チェック | 本人確認が済んでいない人にお金を動かす権限を与えないため | customers.kyc_status が verified であることをDB側で毎回確認 |
| 口座状態チェック | 凍結された口座からお金が抜けてしまうのを防ぐため | bank_accounts.status が active かをDB側で確認 |
| 残高確認 | 持っていないお金を送れてしまうのを防ぐため | 口座を一時的にロックしたうえで残高を再確認 |
| TOTP認可 | ログインだけでなく、この振込内容に対する新しい本人確認を得てから金融更新へ進むため。振込先・金額への署名やフィッシング耐性は保証しない | コードはブラウザからSupabase Authへ直接送り、DBには保存しない。DBは内容・sessionに束縛したintent、試行5回・期限5分、Auth側の検証記録を確認 |
| Idempotency確認 | 通信エラーで「もう一度送信」しても、二重送金にならないようにするため | 依頼ごとの一意なキーで、同じ依頼を再送しても結果は1回分だけ |
| Ledger記帳 | 「動いたお金」が必ず帳簿として残るようにするため | 振込元と振込先を同じtransactionの中で同時に記帳 |
| Audit Log | 後から「何が起きたか」を誰でも説明できるようにするため | 操作の記録を追記専用(あとから書き換え不可)で保存 |
| Integrity Check | 気づかないうちに帳簿がズレていないかを機械的に見張るため | 定期的に台帳の合計と口座残高を突き合わせる検査 |
2. Ledger(台帳)と会計 — 残高は「数字」ではなく「記録の積み上げ」
多くの人は「残高」をデータベースの1つの数字だと思いがちですが、この銀行システムでは違います。残高は「これまでのすべての取引記録を積み上げた結果」であり、数字そのものを直接書き換えることはできません。
- 複式記帳:1回の振込は「振込元の口座がマイナス」「振込先の口座がプラス」という2つの記録を必ずセットで作ります。
- 借方=貸方:この2つの記録の金額は必ず一致します。片方だけ記録される、ということはシステム上できません。
- Append-only(追記専用):一度記録した取引は、あとから書き換えたり削除したりできません。訂正が必要な場合は「反対の取引(Reversal)」を新しく追加します。
- Reversal(組戻し):間違った取引を消すのではなく、打ち消す記録を追加することで帳簿の履歴をすべて残します。
- 残高整合性:表示上の残高(cached_balance)は、あくまで台帳の記録を集計した「キャッシュ」。本当の正解は台帳の記録そのものです。
3. Security(セキュリティ) — 1つの鍵ではなく、何重にも守る
この銀行システムは「本人確認さえ通れば何でもできる」という作りにはなっていません。何段階もの独立したチェックを重ねています。
| 専門用語 | 平易な説明 |
|---|---|
| RLS(Row Level Security) | データベース自身が「この行はこの人にしか見せない」と判断する仕組み。アプリのコードにバグがあっても、他人のデータが漏れる最後の防波堤になります。 |
| 最小権限 | 各機能には「その処理に必要な最小限の権限」しか与えません。振込用の仕組みが、顧客情報を自由に書き換えられる、ということはありません。 |
| SECURITY DEFINER | 「決められた手順の中でだけ」強い権限を貸す仕組み。銀行の窓口係が、決裁された伝票のときだけ金庫を開けられるイメージです。 |
| Customer / Admin分離 | 一般のお客さまアカウントと、管理者アカウントは兼務できないようにDB側で強制しています。 |
| TOTP(認証アプリ) | 振込前に、認証アプリの6桁コードでこの振込への本人確認を行います。コードは銀行のDBに保存されず、確認は1回しか使えません。 |
| frozen account(凍結口座) | 不審な状況があれば、管理者が口座を一時停止できます。凍結中は振込などができません。 |
4. Operations(運用) — システムは作って終わりではない
安全な仕組みを作るだけでなく、「その仕組みがちゃんと機能し続けているか」を見張る運用の仕組みも用意しています。
- Audit Log:誰が・いつ・何をしたかを、すべての重要な操作について記録します。
- Integrity Check:台帳の合計と口座残高がズレていないかを機械的に検査します。
- Reconciliation(照合):帳簿同士を突き合わせ、矛盾がないかを確認する仕組みです。
- Observability(可観測性):システムの中で何が起きているかを、後から追跡・確認できるようにする考え方です。
- Admin Console:管理者が本人確認の審査、口座の凍結・解除、監視状況の確認などを行う専用の画面です。
5. Testing(テスト) — 「たぶん動く」ではなく「毎回確認して動く」
見た目が完成していることと、実際に安全に動くことは別問題です。このシステムでは、コードを変更するたびに自動テストがすべて通ることを確認しています。現時点の総テスト件数は 407件 です。
| レイヤー | 何を確認しているか | 件数 |
|---|---|---|
| DB / RPC | データベース関数・権限・RLSなど、DB側の正しさ | 375件 |
| API | 画面の裏で動くAPIが、想定通りの応答を返すか | 21件 |
| E2E(ブラウザ) | 実際に画面を操作したときに、最初から最後まで正しく動くか | 11件 |
| 合計 | 407件 |
体験学習の続き · LEARNING LOOP
ここまで読めましたか?続けて確認してみましょう
10段階のチェックを読んだところで、実際の画面と記録を見比べ、自分の言葉で説明してみます。
- 02実際に操作する — OPERATE
同じ仕組みを、動く画面から観察する
まず顧客画面を開いて観察し、この教材へ戻ります。リンクは新しいタブで開きます。ログインが必要です。
アカウントがない場合は新規登録が必要です。本人確認・口座開設には管理者承認が必要です。承認前や操作できない場合も、この教材内の図・説明・Source Trail(教材の参照元)で代替学習し、次へ進めます。
ログイン・新規登録の入口を開く ↗操作できない場合:教材の参照元で確認する ↓利用者の画面 · MINIBANK顧客ダッシュボード ↗表示された残高と、取引の入口を探してみましょう。
利用者の画面 · MINIBANK振込画面 ↗入力から確認へ進み、どこで操作を確定するのか観察します。学習用のデモ情報を使います。
もっと詳しく:管理者の画面で観察する
管理者ログインが必要です。権限がない場合は、次の「証拠を確認する」の教材による確認で進められます。
- 03証拠を確認する — EVIDENCE
何を見たか、1つずつ確かめる
Evidence(判断の根拠となる記録)を確認したらチェックします。各項目の「何を見るか」を手がかりにしてください。
画面を開けない項目は、Source Trail(教材の参照元)と教材の説明で確認できます。失敗を実際に起こす必要はありません。
- 04コードの処理経路を追う — CODE TRAIL
コードを書く必要はありません
ここでは「説明されている仕組みが、実際のMini Bankのどこに実装されているか」を確認します。まずは日本語の役割説明を読み、入力した依頼がどこへ渡るかをつかみましょう。コードの全文を読む必要はありません。
基本の順序は、画面 → API Route(依頼を受ける窓口)→ TransferService(処理をつなぐサービス)→ DB / RPC(記録の保存場所と、その処理の呼び出し)です。まず最初の2つを見て、続くサービスは「もっと詳しく」で確認します。
教材の参照元を確認する — Source Trail ↓利用者が振込を入力する画面。10段階チェックの最初の入口を探します。
src/components/customer/TransferForm.tsxAPI Route(依頼を受ける窓口)。入力を検査し、サービスへ渡す最初の関門を確認します。
src/app/api/v1/transfers/request/route.ts - 05自分の言葉で説明する — EXPLAIN
LEVEL 1 — やさしい説明(まずここだけ)
画面に「振込成功」と表示される前に、システム内部ではどんなチェックが行われていると思いますか? その中から2つを選んで、なぜそのチェックが必要か説明してください。
日常語だけで完了できます。専門用語の数や正誤を自動採点するものではありません。
ヒント1を見る
本人確認・残高確認・記帳・監査の中から、2つを選んで書いてみましょう。
ヒント2を見る
「なぜこのチェックがないと困るか」を添えて説明してみましょう。
もっと詳しく:LEVEL 2 — 専門用語を使った説明(任意)
複式記帳、Idempotency、Audit Log、Integrity Checkの4つを使って、10段階のチェックがどう安全性を積み重ねているか説明してみましょう。
挑戦する場合は、上の説明欄に追記できます。追加しなくても完了できます。
説明の確認ポイント — Checkpoint- 振込が単一のチェックではなく、複数の段階を通ると説明できる
- 残高の表示以外にも確認すべき記録があると説明できる
理解を確かめる — CHECK YOUR UNDERSTANDING
5問で理解チェック
答えを頭の中で考えてから、各項目を開いて確認してください。
Q1金融更新のrollbackで依頼や失敗記録も全部消えますか?
金融更新の仕訳・transfers・残高cache・成功audit(TRANSFER_COMPLETEDと内側の変更trigger)は、confirm_transfer_otpの内側の例外blockでまとめてrollbackします。先に保存済みのrequestは消えません。transfer_auth.consumeによる認可済みintentの消費(consumed_at)、request=failed、TRANSFER_FAILEDは内側のrollbackの対象外で、外側のDB transactionがcommitすると残ります。
Q2振込の確認コードは銀行が作って画面に表示し、DBに保存しますか?
v7(migration 20260920074653と20260920083426)の振込認可は、Supabase Authの認証アプリTOTPを振込intentへ束縛する方式です。request_transferは確認コードを生成・返却せず(otp_plaintext列は応答互換のため残り、常にnull)、reissue_transfer_otpは常に42501で拒否します。prepare_transfer_mfaは所有者・Auth session・送金元・送金先・金額・memo・依頼人名・idempotency key・期限のhashをintentへ固定します。ブラウザはコードをSupabase Authへ直接送り、Next.jsとDBはコードも、そのhashも受け取らず保存もしません。DBはAuth所有のchallenge検証時刻、AAL2 session、同じsessionのTOTP AMRを読んで認可し、confirmは60秒以内の認可を1回だけ消費します。DEMO_OTP_DISPLAY_ENABLEDでDBのコード生成は復活しません。TOTPは時刻ごとの値で、振込先や金額への署名ではなく、フィッシング耐性も主張しません。稼働DBへの適用はこの教材ではUNKNOWNです。
Q3振込の途中でエラーが起きたら、お金はどうなりますか?
資金移動部分の仕訳・transfer・残高cache・成功auditはまとめて戻ります。先に保存した依頼は消えません。認可済みintentの消費、request=failed、失敗eventは、外側のtransactionがcommitすれば残ります。TOTPの試行回数は資金移動とは別の呼出しで記録済みです。
Q4残高を直接書き換えることはできますか?
できません。残高は台帳の記録を積み上げた結果であり、過去の記録は追記専用で消せません。訂正は打ち消す記録の追加で行います。
Q5パスワードさえ分かれば振込を実行できますか?
できません。ログインに加えて、その振込intentへ束縛した認証アプリTOTPの検証をSupabase Authで完了し、DBが同じsessionでの新しい認可を確認する必要があります。そのうえでKYC・口座・残高・上限などの条件を通します。TOTPは振込先・金額への署名ではなく、フィッシング耐性も主張しません。
完全に暗記する必要はありません。大切な考え方を説明できそうなら押してください。
教材の参照元 — SOURCE TRAIL
根拠と関連教材
CURRENT IMPLEMENTATIONは確認したrepository内の実装です。HISTORICAL IMPLEMENTATIONは導入・修正の履歴で、後続migrationによる再定義があり得ます。RELATED LESSONは教材への案内で、実装の一次Sourceではありません。ASSUMPTIONは仮定、UNKNOWNは未確認を表します。
確認基点: d02eae8914f39cbf06bfe422dc39c71886f82b3e / 2026-09-11。稼働環境への適用確認: UNKNOWN。パス実在の自動検査と、記述した技術的事実の確認は別です。
- 現在の実装 — CURRENT IMPLEMENTATION — Primary Source
supabase/migrations/20260920074653_transfer_totp_intent_authorization.sqlpublic.request_transfer / public.confirm_transfer_otp / public.reissue_transfer_otp / public.prepare_transfer_mfa#128。依頼は確認コードを生成せずotp_plaintext=null。confirmはtransfer_auth.consumeで認可済みintentを消費してからexecute_transferを呼ぶ。reissueは常に42501。稼働DBへの適用はUNKNOWN。 - 現在の実装 — CURRENT IMPLEMENTATION — Primary Source
supabase/migrations/20260920083426_require_fresh_totp_on_intent_session.sqlpublic.transfer_mfa_broker#129。intentのAuth session自身に、予約・検証より後のTOTP AMRを要求する最新broker定義。 - 現在の実装 — CURRENT IMPLEMENTATION — Primary Source
supabase/migrations/20260920115556_scalar_journal_posting_atomic_cache.sqlpublic.execute_transfer最新定義。ledger.post_journal_entryの戻り値をscalar uuidで受ける。検証・lock・上限・冪等性は従来どおり。既存ACLは維持。 - 現在の実装 — CURRENT IMPLEMENTATION
src/app/api/v1/transfers/mfa/route.tsintent準備・challenge束縛・試行予約・認可完了のserver入口。TOTPコードは受け取らない。 - 現在の実装 — CURRENT IMPLEMENTATION
src/components/auth/TransferMfa.tsxブラウザがSupabase Authへchallenge/verifyを直接送る部分。成功の自己申告は認可にならない。 - 現在の実装 — CURRENT IMPLEMENTATION
src/app/api/v1/transfers/route.ts認証済みの旧直接振込入口はOTP_REQUIREDを返す。 - 現在の実装 — CURRENT IMPLEMENTATION
src/lib/errors/apiError.tsOTP_REQUIREDのHTTP statusは409。 - 現在の実装 — CURRENT IMPLEMENTATION
scripts/mfa/intent-db.test.mjsintent束縛・回数/期限・単回消費・session検査のSQL受入test。Auth行はfixtureで、実Authの証明ではない。当日のPASSも意味しない。 - 過去の実装 — HISTORICAL IMPLEMENTATION
src/lib/config/demoOtpDisplay.ts#128以前のデモコード表示フラグ。v7ではDBがコードを返さず、Productionでは常にfalse。 - 過去の実装 — HISTORICAL IMPLEMENTATION
scripts/regression/suites/transferOtp.mjs#128以前のデモOTP(誤入力・再発行・期限)の回帰test。v7の認可方式の根拠にしない。 - 過去の実装 — HISTORICAL IMPLEMENTATION
supabase/migrations振込・台帳・監査・整合性検査の実装(Migrationファイル群) - 現在の実装 — CURRENT IMPLEMENTATION
scripts/regression/suitesDB/RPC・APIレイヤーの自動テストの実装 - 現在の実装 — CURRENT IMPLEMENTATION
e2e/full-journey.spec.tsブラウザE2Eテストの実装