JWNET・接続テスト
本書は AGENTS.md の分冊(正典・AGENTS.md と同格)。優先順位は「実コード → AGENTS.md(本書を含む)→ DESIGN.md → PRODUCT.md」。 節は AGENTS.md から逐語で移設した(PR #nnn・2026-09-16)。対象パス:
apps/api/src/routes/jwnet.ts` `apps/api/src/jwnet/**` `packages/shared/src/jwnet/**` `supabase/migrations/*jwnet*` `scripts/gateway/**。この領域に触るワーカー / レビュアは本書を読む。
JWNET Web-EDI(不変条件ラチェット・mig160/161/162/182/251)(Web-EDI・UI 導線は #587 で撤去済み・標準EDI 移行中 = ADR 0024)¶
⚠ 方式は ADR 0024(2026-08-13)で標準EDI のみへ確定(Web-EDI は本番運用せず・WE-01 不提出・Q3 完了目標・
wl-qvt=P1)。本節以下の Web-EDI 契約は出荷済みコードの不変条件の記録として維持する(Web-EDI 資産の撤去は標準EDI 接続テスト通過後の段階 2 = ADR 0024。それまで下記の guard/RPC/proof は現物のまま守る)。方式選定の現行正典は ADR 0024。
JWNET への電子マニフェスト報告を Web-EDI 機能(拡張Z手順・IPsec・常駐ゲートウェイ・EDI事業者登録・接続テストのいずれも不要)で行う——というのが ADR 0023 までの方式(上記のとおり supersede 済み)。要求/結果ファイルのレイアウトは標準EDI機能と同一で、搬送だけが HTTPS + 送受信BOX に替わる。遍歴は ADR 0019→0020→0021→0022→0023→0024 で、mig205-210 は標準↔Web-EDI の往復で net Web-EDI、prod への影響は jwnet_credentials を空で再作成するだけである。標準EDI + 常駐ゲートウェイ(specs/2026-07-25-jwnet-gateway-design.md・ADR 0024 で改訂予定)が本番方式となる。
-
運用時間は方式ごとに違う。 標準EDI 本番 4:00–24:00 / 接続テスト 平日 9:00–17:00(月〜金・ 利用期間2週間・EDI 仕様書 3-12 実測。以前の「19:00–23:00」は電子契約機能の時間の取り違えで誤り・ 2026-09-03 訂正)/ デモ 8:00–18:00・月次メンテ停止日あり(1/1-3・5月第1日曜・8月第2/3土日・ 10月第2日曜ほか)。Web-EDI 機能は 6:00–23:00・土日祝停止。 常駐ゲートウェイは標準EDI 側なので前者。Web-EDI 節の時間表を持ち込んではならない。
-
接続テスト経路(env='test')は実運用経路と分離する(wl-u733 slice B / wl-gyvi C2): 両APIは collector admin限定(normalも拒否)。request/resultとも自tenantの
test資格情報がconfiguredであることとKEK設定を要求する。resultは非秘密列だけをcaller JWTで読み、復号しない。/web-edi/test/request-fileは client 供給の事前登録番号を使い、jwnet_submissionsを作らずnext_jwnet_request_code('test')だけを消費する。結果判定/web-edi/test/result-fileは parse-only で DB 状態を変えず、record / apply / retry / resolve を呼ばない。 監査は許可される場合にjwnet_message_log(submission_idNULL)へ append するが、 現行 mig161 の fill_tenant は service_role + NULL submission を拒否するため B では書かず、unavailable_null_submissionと原本 SHA-256 を明示する。JWT 偽装や仮 submission で迂回しない。 collector admin の専用ページだけを設定からリンクし、グローバル nav・休眠 Web-EDI ページを復活させない。 接続テスト通過後に撤去または本番経路への統合を判断する。 D31(0403/0605/0207)はManifestRegistrationPayloadSchemaの連鎖入力を受け、buildManifestRegistrationRecordsで件数を配列から導出する。client件数は受け付けず、 envと排出加入者番号を実際のtest資格情報・HD2へ束縛する。D32は空固定。 後続を含む総データレコード300件以内を単一ファイルとして受理し、超過は採番前に400で全体拒否。 平坦な分割器・先頭300件の切出しでD31連鎖を断ってはならない。HD1/HD2・全体のSHIFT_JIS上限も維持する。 3101の初期値はsharedbuildD31PrefillFromInquiryに集約し、確認済みの番号・区間・収運加入者だけを写像する。 不明項目は未写像として残し、R19報告実績を登録値へ転用しない。初期値を登録の受理判定に使わない。 R35の予約/本登録区分を保持し、予約は0403、本登録済みは0605の明示操作へ渡す(0207は手動選択)。 0605/0207の既登録処分業者同一・収運追加のみは画面へ注記し、比較は本スライスでは行わない。 legacy 送受信ルート 6 本は 2026-09-09 に 410 で閉鎖(wl-dlfj):POST /jwnet/submissions・/jwnet/web-edi/request-file・/web-edi/retry・/web-edi/resolve・/web-edi/result-file・/web-edi/inquiry-resultはLEGACY_WEB_EDI_CLOSED_ROUTES+requireAuthより前の閉鎖ガードで 未認証でも 410(error: 'gone')を返し、DB RPC へ到達しない(handler 本体は残す・削除は wl-x3j3)。 KEEP は資格情報 2 本(GET/PUT/jwnet/credentials)・test 2 本(/web-edi/test/request-file・/web-edi/test/result-file)・照会 2 本(3101inquiry-file・3201inquiry-search-file)。 3201 は legacyinquiry-search-fileを env='test' で使うので閉じない(閉じると Day-1 の 3201 と S1 25 が壊れる)。3101inquiry-fileはstatus='submitting'の submission が前提で UI からは 到達しないが「照会 3101/3201 は温存」の判断に従い KEEP する。 -
搬送層は Phase を跨いで差し替え可能に保つ: 電文組立(
packages/shared/src/jwnet/**の純関数)と搬送(ファイル受け渡し/将来の拡張Z手順)を分離したままにする。接続仕様書 §1-5(3) のとおり Web方式・Web-EDI・標準EDI は併用可であり、Phase 2 では搬送だけを差し替える。 - 利用区分
'3'は env='test'の接続テストだけに許可する(ADR 0024 / mig251 の意図的変更): 標準EDI の接続テスト(2026-10-05〜18)で HD1 の'3'が必須になったため、既存の Web-EDI 電文組立・ファイル受け渡し経路を接続テストにも再利用する。webEdiRiyouKubunは共有定義JWNET_RIYOU_KUBUNからprod→'1'/demo→'2'/test→'3'を返し、demo/prod の従来値は不変とする。DB 側はassign/record/retry/resolve/applyの env whitelist に'test'を残すが、接続テスト経路が実際に使う RPC はnext_jwnet_request_codeのみ(/web-edi/test/request-fileが submission を作らず採番だけを消費し、/web-edi/test/result-fileは parse-only)であり、5 RPC の'test'許可は wl-dlfj の 410 閉鎖により API からは到達不能(DB の proof・S1 10 の env ゲート検証は残る)。mig256 で資格情報 bridge だけ'test'を許可する(他 RPC は接続テストで使う経路のみ・mig251)。推測で全 RPC を許可してはならない。 - Web-EDI の上限はサーバで fail-closed に強制する(
packages/shared/src/jwnet/web-edi.ts): 従属を含む総データレコード 1〜300件(§4-4 の処理機能件数より厳しい保守的制限を維持)/ファイル全体 ≤500000 byte(§4-3 Ⅳ の 500kbyte を保守解釈)/HD1 ≤75byte(EF01001)・HD2 ≤41byte(EF02002)/ファイル名は 20byte以下・半角英数とハイフン・.csv(大文字可)・先頭が.でない・拡張子込みの末尾が$$でない・空白なし。バイト長は必ず SHIFT_JIS で測る(全角2byte。string.lengthで測ってはならない)。自社名義の実到達最大は HD1=73 / HD2=39 byte で 75/41 には構造上到達しないため、当該ガードは防御的多重化として残す(不要と判断して外さない)。 - HD2 項番 5 は処理機能件数(head レコード数・従属レコードを除く)(
EE02009)。判定は shared のJWNET_HEAD_LAYOUTSに一元化し、未知レイアウト・head 0 件は throw。D31 の子や D60 の D62、D33 の D25 を数えない。総データレコード 300 件の上限とは別の数である。 - 全角文字は 4-3 Ⅱ の許容範囲に限る(IBM 拡張・NEC 特殊・半角カナは拒否・禁則文字
' % _ , < > " \r \nは値に含めない)。人名の髙・﨑等は JIS 第一/第二水準の代替文字を使う運用とし、範囲外文字を置換して通さない。半角の値は ASCII 0x20〜0x7E(禁則文字を除く)のみ。CSV 構文の引用符・カンマ・CRLF は値とは区別する。 - HD1 の加入者情報件数は
'1'固定が仕様どおり: 項番7 は9(3)+ 編集Z9(必要な桁数だけ・ゼロ埋めしない)。自社名義は HD2 が常に1件なので'001'は違反。buildEdiRequestTextの'1'を「ゼロ埋め漏れ」と誤認して直してはならない。 - 要求コード: 半角英数10byte以下・ブランク不可(
EF01005)・30日間は同一コードを再利用不可(EF01006)。採番はnext_jwnet_request_code(mig153・per tenant/env/day 行ロック直列採番・直近30日除外)に一元化し、client 側で組み立て直さない。要求ファイル名は{要求コード}.csvで決定的に導けるため保存しない。 - 資格情報(mig160): 接続登録番号 / 枝番 / 加入者番号 / 事業者区分は非秘密の運用識別子で平文列(admin 限定 RLS + 列 grant)。EDI利用確認キーだけが秘密で
ciphertext+key_versionに AES-256-GCM で保管する(鍵 = Wrangler secretJWNET_CREDENTIAL_KEK・base64 32byte・key_version=1・保存形iv(12)||ct||tag)。AAD にtenantId:envを束ねる(他行への暗号文移植を無効化)。KEK 未設定は 503 で平文フォールバックしない。ciphertextとkey_versionは読み側(SELECT/UPDATE 列 grant)では withheld にし、書込は guard で塞ぐ二層にする。INSERT 列 grant に含まれること自体は既存契約なので、読み側へ露出させず、guard を外してはならない。確認キーは応答・ログ・例外本文・画面・キャッシュに出さず、要求ファイル本文(HD2)にだけ存在する。 - mig149 の guard は service_role も拒否する(AGENTS.md 本体の通常方針と逆)。
jwnet_credentialsの保護列書込は service_role 限定 DEFINER bridgeupsert_jwnet_credential_from_service経由に限り、呼出元 tenant を明示引数で受けてその tenant の collector admin profile を名指しできることを再検証する。jwnet_credentials_fill_tenantの carve-out は postgres 限定 + 既存app.bypass_jwnet_credentials_guardのみで、JWT claims を偽造して本人になりすます実装を採ってはならない(同一Txの後続文へ任意 admin 権限が漏れ、かつ Supabase のauth.uid()実装詳細に依存して docker proof が通っても本番保証にならない)。 - 送信認可は「行の存在」ではなく「設定済み」を要求する:
jwnet_submission_authz_ok(mig160 でciphertext is not nullを追加)と API のhasCredential()(status='configured')を一致させる。前者は mig147 のトリガ経由でenqueue_jwnet_submissionの INSERT にも効くため、client 直 INSERT と RPC の両経路が同時に閉じる。 - 相関キー(mig161):
jwnet_submissions.web_edi_request_code(^[0-9A-Za-z]{1,10}$CHECK + 部分索引)が要求ファイルと submission を結ぶ唯一の器。バッチ用の新表を作らない(ファイル名は決定的、ハッシュ監査は append-only のjwnet_message_log)。この列は server-owned で、jwnet_submissions_guard_protected_colsが authenticated / anon / service_role の直接差替を拒否する。 assign_web_edi_request_code(mig161): env whitelist / role はIS DISTINCT FROMで staff 限定 fail-closed / NULL・重複・空・301件 拒否 /FOR UPDATEでロックし ロック件数と要求件数の不一致で raise(他テナント混入・非 queued・二重割当を黙って一部処理しない)/ UPDATE 後にROW_COUNT再検査。manifest_id is not nullを必須にする: enqueue は採番前キューのため NULL を許すが、それを要求ファイルへ載せるとrecord_web_edi_resultが manifests との inner join で対象から外し、バッチ全体がsubmittingのまま取込不能になる。- 採番の電子再検証(mig182): 上記 assign の契約を保ったまま、enqueue 後の紙戻しを許さない電子判定と
jwnet_submission_authz_ok再検証を追加した。DEFINER + bypass GUC 下では validate trigger が早期 return するため、assign 自身が確認しなければ誰も確認しない。 record_web_edi_result(mig161): マニフェスト番号で全単射検証(余り・不足のどちらも raise)/submitting以外を含む=二重取込を raise / 「失敗なのに理由なし」を拒否 /ROW_COUNT再検査。web_edi_request_codeは消さずに残す(どのファイルの結果か辿れるようにする)。- 結果判定はファイル→加入者ブロック→個票(mig167をmig257で是正): mig167では
p_hr1_return_code(必須)とp_hr2_return_code(任意)を状態適用のゲートとして導入した。 当時のHR2≠'0'を全件不受理とする解釈は誤りで、mig257で是正した(歴史的migrationは変更しない)。 - 仕様根拠は§6-1(4)(5)(8)④。HR2リターンコード
'1'はブロック内にE01がある印。 HR2リターンコードは判定に使わない。HR2自身のエラー件数と個票の直後のE01で判定する。 - HR1=
'1'だけがファイル全体の不受理で、1件もacceptedにしない。APIはRPCを呼ばず422。 - HR2のエラー件数>0(HR2直後にE01)なら当該ブロック全個票をerrorとし、 理由はHR2直後のE01とする。別ブロックへ波及させない。件数と直後E01の不一致は拒否する。
- HR2のエラー件数=0なら個票ごとに判定する。HR1=
'3'/HR2='1'でも 個票のerrorCount=0はaccepted、>0はerror。受理済みをerrorにしてretryへ渡すと二重報告になる。 部分成功を全件errorにしてはならないというmig167当時の指摘(codex terra)は維持する。 record_web_edi_resultのHR2引数はログ・戻り値専用。ブロック不受理は呼出元が個票errorへ変換する。 HR1の未指定・未知値、HR2の未知値(既知は'0'/'1'、省略時NULL)はraiseでfail-closed。- 旧署名(3引数・4引数)はdrop済み。proof J167-12の
pronargdefaults検査、全単射・ 二重取込拒否・理由必須・ROW_COUNT再検査を維持する。戻り値に両ヘッダコードを含める。 恒久チェックはshared/APIの仕様例fixture、S1シナリオ24、mig257-hr2-block-judgement.sql。 mig257のsentinelは後続mig258で追加したmig257_hr2_block_judgement_applied()。verify-jwnet-connection-test-rls.mjsはmarker確認後の未知HR2受容をFAILとし、未デプロイと混同しない。 submittingの脱出弁は「照合待ち」を作る(mig167):resolve_web_edi_submitting(staff fail-closed・理由必須・submitting → errorのみ・acceptedは巻き戻さない)。 出口が結果取込の完全全単射だけだと、結果ファイルが少し想定外なだけでバッチ全件が 恒久デッドロックする(結果はセンターで 2 週間しか保管されず再受信不可)。照会 3101 は実装済みだが対象はstatus='submitting'のみで、resolve 済み(error)は照会不可である。error_messageはreconciliation-required: <理由>にし、retry の対象外にする。 ただerrorにすると通常の再送導線に乗り、JWNET 側で受理済みの報告を再送して 二重報告になる(運用注意だけでは防げない・codex terra 敵対レビューで検出)。 解除の機械的経路は現状ない。reconciliation-requiredは JWNET 画面で人が確認して DB を直接直す運用に限る。自動解除を作るなら「resolve 済みを照会対象に含める」+「結果でaccepted/queuedへ振り分け」を同じスライスで実装する。- 理由の空判定はタブ・改行・全角スペースも落とす(API の zod は trim するが、
PostgREST 直呼びでは
\tだけの理由を送れる)。要求コードは消さない。 - 認可の再検証は self でも helper を通す(mig167):
retry_web_edi_submissionはjwnet_submission_authz_ok(s)を常に呼ぶ。nominee = operator(自社名義)を素通し条件に すると、helper が見ている credential 設定済み・manifest tenant 一致・4者整合まで飛ばして credential 未設定の self 行まで再送できる。委任の生存確認だけが目的ではない。 - 既知の残存リスク(follow-up・設計スライス): 結果適用 RPC は
authenticatedに grant されているため、同一テナントの staff は PostgREST 直呼びでacceptedを書ける(偽造 CSV の upload でも同じ)。mig167 が作った穴ではなく従来からの境界。恒久解は「結果原本と 外部照合根拠を append-only に保存してから適用する」+「適用 RPC を backend-only にする」で、 別スライスとして扱う。 - 照会(3101)の判定は R19 の自社区間の運搬終了日だけを根拠にする(wl-9tw.2・
packages/shared/src/jwnet/inquiry-decision.ts+apps/api/src/routes/jwnet.tsの inquiry-file / inquiry-result)。R35 の「運搬終了報告済フラグ」を使ってはならない (自己運搬・報告不要業者は未報告でも1= 受理の捏造 / 取消状態は報告済みでも0= 二重報告)。承認待ちフラグ(修正2/ 取消3)は運搬終了日より先に評価する (逆順だと取消承認待ちが accepted に落ちる)。判定対象は carrier_report のみ (処分終了報告を R19 で判定すると未達を受理済みと誤記録する — mig168 の RPC も message_type で fail-closed)。 - 照会は submission の状態を変えない:
assign_web_edi_request_codeは使えない (対象は既に submitting・mig161 の assign は queued 以外を raise)。採番はnext_jwnet_request_codeを直接呼ぶ。要求↔結果の相関は結果ファイルの HR1 要求コードが 権威(parseInquiryResultFileが不一致を throw・対象集合の事前永続化は持たない)。 ファイル不受理(HR1='1')は RPC を呼ばず 1 件も変えない。HR2自身のエラー件数>0の ブロックは共有判定でunchangedにし、他ブロックのR19判定は続ける。結果に現れない 番号は unchanged にすら数えない(何もしない)。照会の文言はapps/web/src/lib/jwnet/inquiry-messages.tsに一元化(設計 §6 の日本語をそのまま定数化・ コンポーネントに直書きしない)。3201 は D34 1 行・状態を変えない・返却 3,100 件/CA 打切りを表示し、 反映対象 300 件超は 400 で条件絞り込みを促す。 - 照会(3101/3201)は自社名義限定(wl-c5t の委任サーフェス閉鎖):
tenant_id・credential_tenant_idは呼出元テナント、nominee_tenant_idは呼出元テナントまたは legacy のNULLとし、違反は採番より前に 403 で拒否する(要求 JSON のnomineeTenantIdも受け付けない)。委任分の照会は、wl-6vy の採番台帳を credential tenant 単位へ変更・authz のFOR SHARE NOWAIT化・取込の nominee 束縛 + HR2 名義照合 + 監査の nominee key version + 二接続 proof が揃うまで開けない。 RLS で「見える」ことは照会権限を意味しない。結果適用 RPCapply_web_edi_inquiryは mig178 で行ごとにjwnet_submission_authz_okを再検証し、 credential 未設定・失効委任でのaccepted書込を封鎖する(retry の mig167 と同水準)。 self 名義の残存競合窓(credential soft-delete)は受容する。 - 承認待ちフラグは whitelist で判定する(
'1'のみ): R19 項番43 の'1' 待なしだけを 「承認待ちなし」として運搬終了日判定へ進め、'2'/'3'は承認待ち、それ以外はapproval_state_unknownで unchanged(fail-closed)。判定規則は「'1 待なし'以外なら 自動反映しない」であり、''(項目欠落)も「以外」に含める — この項目は理論上常設なので、 空フラグ + 運搬終了日ありは切断された行・列ずれの兆候である。誤 accepted は二重報告だが unchanged の副作用は「JWNET の画面で人が確認する」だけで、危険は非対称。'0'は R19 項番43 の定義に無いので whitelist に入れてはならず、'1'を外してはならない (外すと通常の報告が全て unchanged に落ち、結果待ちを一切解消できなくなる = 機能そのものが死ぬ)。 - 失効ホールドは再送で解除できない(mig167):
retry_web_edi_submissionはerror_message='revocation-hold'(mig147 のpropagate_jwnet_revocationが書く痕跡)を 対象外にし、委任の生存を明示的に再検証する(nominee = operatorの自社名義は委任不要)。 DEFINER + bypass GUC 下ではjwnet_submissions_validate_authzが早期 return するため、 ここで確認しないと誰も確認しない。入力検証(NULL 拒否・重複拒否・301 上限)は mig162 の 契約を一字も落とさない(簡略化すると既存 proof W162-1 の 7 通りの fail-closed が崩れる)。 - パーサは「意味を変えないゆらぎ」に寛容(mig167 同梱の shared 変更): 1 レコードに複数 E01
(必須項目が 2 つ以上欠落)は仕様上ありえるので代表コードを 1 つ選んで受理する。
未知のエラーコード接頭辞は throw せず
category: 'unknown'+requiresHumanOps: trueに 落とす(接頭辞追加で結果ファイル全体が解析不能になり恒久滞留するのを避ける)。 submittingは決して再送しない(mig162):retry_web_edi_submissionの対象はstatus='error'のみ。結果ファイルはセンターで作成日から2週間しか保管されず受信済みの再受信もできないため、取りこぼした報告は JWNET 側で処理済みの可能性があり、盲目的な再送は二重報告になる。照会 3101 は実装済みだがstatus='submitting'のみが対象で、resolve 済み(error)は照会不可である。acceptedを巻き戻す経路も作らない。再送はstatus='queued'へ戻しweb_edi_request_codeとerror_messageを NULL にする(前者はassignがis nullを条件にするため必須)。error_messageは server-owned(mig162 の companion guard)。これは mig149 の「service_role may write gateway result fields」を意図的に狭めている。Web-EDI では「JWNET がなぜ弾いたか」の運用記録になるため偽装させない。Phase 2 の常駐ゲートウェイも result fields は DEFINER RPC 経由で書くこと(直接 service_role DML にしない)。- DB を触る前に組立を空試行する:
POST /jwnet/web-edi/request-fileは要求コードの最大長プレースホルダで一度組み立ててからassignを呼ぶ。assignは submission をsubmittingへ動かし再送はerror限定なので、組立が後で落ちると復旧不能な滞留を作る。現状は各項目のshiftJisString検証と mig160 の CHECK により組立は失敗しないが、項目追加や上限変更でこの前提が崩れても罠が再発しないよう順序を保つ。 - 要求ファイルの束縛(mig182):
POST /web-edi/request-fileは採番前に record と submission のペアを検証する。D09=carrier_report / D60=disposal_report の種別整合、record のmanifestNoと DB のmanifest_numberの照合、delivery_method='electronic'の確認を行い、不一致・紙・未設定は 400 とする。manifest 番号は DB 値との照合で拒否する(client 値で上書きはしない)。 - SHIFT_JIS を文字列化しない(web): 要求ファイルは
res.arrayBuffer()で受けdownloadBlobで保存する(res.text()禁止・BOM 禁止。既存 CSV エクスポートは Excel 向けに BOM を付けるが JWNET は素の SHIFT_JIS を期待する)。結果ファイルはFile.arrayBuffer()→ base64(FileReader.readAsText禁止)。 - 推測で電文項目を埋めない:
manifestsには運搬終了日・運搬担当者・区間番号・単位コードが無い。buildD09Draft/buildD60Draftは埋められない項目をmissingに列挙してpayload: nullを返し、UI は全行が揃うまで作成させない。mapUnitToJwnetQuantityUnitCodeは判らなければnullを返す(コードを当てない)。simpleResultFlagの既定は'1'(通常結果ファイル要求)——既存 R09/R60 パーサはフルレコード前提なので簡易'2'を既定にしない。 - 残存リスク(wl-jiw): record の運搬終了日・区間・数量など manifests に列が無い他項目は client 供給のままで、送信原本の snapshot もまだ無い。DB 導出・原本固定・委任送信の credential 実測は別スライスで扱う。
- 結果レコードの索引は「CSV位置」列が正典: 結果情報の見出しは CSV に現れない(CSV位置
-)。HR1 リターンコード=idx9 / 件数=idx10、HR2=5/6、R09 errorCount=14、R60=17。off-by-one を入れないこと。 - 恒久チェック: docker proof
scripts/proofs/mig160-jwnet-credential-branch.sql(C160-SENT/1〜15)・mig161-jwnet-web-edi-exchange.sql(W161-SENT/1〜12)・mig162-jwnet-web-edi-retry.sql(W162-SENT/1〜9・核心は W162-6「再送後にassignが同じ行を再度拾える」)をscripts/proofs/*.sql全流しで検証する([[local-docker-proof-must-run-all-proofs]])。全流しは psql の exit code も判定する([OK]文字列だけだと ERROR 中断を見逃す)。gitleaks は git 履歴を走査するのでコミット後に実行する(コミット前の実行では検出されない)。prod-critical(migration + 資格情報 + DEFINER RPC)は敵対レビュー必須(担当は AGENTS.md 本体の Routing 節・2026-08-02 以降は Claude/[[adversarial-workflow-and-new-table-protected-cols]])。 - Web-EDI の運用制約(画面に出す): 本番の運用時間は 平日 6:00–23:00(土日祝停止)。結果ファイルはセンターで作成日から 2週間保管・受信済みの再受信は不可。送信前のウィルスチェックは必須。1要求ファイルの実務目安は 50〜200件(最大300件)。
JWNET 担当テナント検査 + Form3 年度整合(不変条件ラチェット・mig190/193/194)¶
- mig190: Form 3 の
form3_aggregation()は SECURITY DEFINER で tenant NULL を拒否し、discharger 以外を fail-closed で拒否する。実行権限は authenticated のみに grant し、quantity の非有限値は 0 へ無害化する。 - mig193:
jwnet_submission_authz_okはmessage_type='carrier_report'ならmanifests.carrier_tenant_id、'disposal_report'ならdisposal_tenant_idが非 NULL かつ nominee と不一致なら false(NULL=段階未登録は許可・webisSendCandidateと同一契約)。register/final_report/amend/cancelの担当検査は各送信経路を実装するスライスで定義する。この 1 関数で enqueue(mig147 トリガ)・採番(mig182)・再送(mig167)・照会反映(mig178)の 4 経路が同時に閉じる — 経路を足すときは authz_ok を必ず通すこと。 - 恒久拒否の UI 文言: 採番/再送の 403 は恒久条件(交付区分・担当段階・資格情報)として案内する。「時間をおいて再度お試しください」に落とさない(事務が永久リトライする)。
- mig194: Form3 の
fiscal_yearsとgrouped_rowsは同じ除外済みscoped_manifestsCTE から導く。除外式はisExcludedFromForm3(電子かつ登録済み)と論理同一のまま維持し、isJwnetReportable(電子のみ)と混ぜない。片側だけ除外条件を変えると「対象行の無い年度が選択肢に戻る」(mig194 が直した事故そのもの)。認可は discharger tenant のみ・role 条件なし。 - 回帰:
scripts/proofs/mig193-jwnet-stage-authz.sql(proof 内ミューテーション S193-MUT)・mig194-form3-fiscal-years.sql(MUT-F194-SCOPE)を全流しで検証。
接続テスト送受信キュー(不変条件ラチェット・mig269)¶
標準EDI 接続テストの要求・結果ファイルを、常駐ゲートウェイ経由で搬送するための
接続テスト専用の送受信キュー。設計正典は
docs/superpowers/specs/2026-09-10-jwnet-connection-test-e2e-gateway-design.md。
実体: supabase/migrations/00000000000269_jwnet_connection_test_exchange.sql。
- 器は接続テスト専用(
env='test'CHECK・本番は別の器)。接続テストは JWNET 側の 事前登録マニフェスト番号で作るため本番の enqueue 経路(jwnet_submissions・採番 RPC のmanifest_id必須)は使えない。本番の enqueue 消費は別スライスで、本表と混ぜない。 - 要求 bytes は KEK 封筒暗号化・列権限で withheld:
request_ciphertextとrequest_key_versionはauthenticatedの SELECT 列 grant から外す(jwnet_credentialsの ciphertext withheld と同じ作法)。AAD はjwnet-cte:<tenant_id>:<env>:<request_code>で 行に束縛する。結果 bytes(確認キーを含まない)は管理者が判定画面から読む。 - ゲートウェイは service-role DEFINER bridge 経由のみ・直接 DML 禁止:
R1/R2 は
authenticatedのみ、R3〜R7 はservice_roleのみ EXECUTE。service_roleに表の grant を与えない(BYPASSRLS でも grant までは迂回できない)。 呼出元判別は mig262 の作法coalesce(pg_catalog.current_setting('role', true), 'none')。 - in-flight 一覧は状態を変えない読取専用の bridge: R7 は
sending/sentの行をclaimed_at, id順に返すだけで、状態遷移も events 追記もしない(再開判断を誤っても行を壊さない)。 - 1 ファイルずつ・前の結果受信後に次: R3 は同一 (tenant, env) に
sending/sentの 行があるうちは 0 行を返す(EDI 仕様書 1-3(3)(4) の搬送制約を DB で強制)。 in-flight 検査→取得→更新は (tenant, env) 単位の advisory xact lock で直列化し、 同時 claim でも 2 件目は 0 行にする(S139-jwnet-connection-test-exchange-claim)。 R2/R4〜R6 は単一行のFOR UPDATE+ROW_COUNTでその行に直列化するため同じキーは取らない。 - R4〜R6 は第 1 引数
p_tenant_idと対象行の tenant を照合し、不一致は 42501 (値不変・R7 と同じ順序: 呼出元判別 → tenant 検証 → 状態遷移)。 「claim した id だけを知る」前提を認可条件と書かない。旧署名は残さない。 - 状態遷移の whitelist:
queued → sending → sent → received(R3/R4/R5)、sending|sent → error|reconciliation-required(R6)、queued → cancelled(R2)。 外はillegal transition <from> -> <to>で raise(42501)。引数不正は 22023。 - events は append-only: UPDATE/DELETE は全ロール raise。
tenant_idは DEFINER fill が 親から毎回再導出(BEFORE INSERT OR UPDATE全列・UPDATE OFに限定しない)。 detail は sha256・transport id・エラーコードのみ(秘密・payload なし)。 - R1 の資格情報ゲートは「設定済み」: API
hasCredentialと同じ条件 (jwnet_credentialsが env='test'・削除なし・status='configured')。行の存在だけでは足りない。 - 回帰:
scripts/proofs/mig269-jwnet-connection-test-exchange.sql(X1〜X20・変異 7 系統の歯)・scripts/verify-jwnet-connection-test-exchange-rls.mjs(read-only・漏洩と 42501 の退行だけ FAIL)・ S139-jwnet-connection-test-exchange-claim(同時 claim の直列化)。