コンテンツにスキップ

委託先(external_partner)オンボーディング runbook

KYO-106 Phase3。委託先(external_partner 衛星テナント)が自社ログインで、共栄(collector)が保持する 「自社帰属の委託料精算(純額 + 品目別内訳・finalized のみ)」を 読み取り専用で越境閲覧できるように するための、作成〜紐付け〜解約の手順書。

関連 migration: mig97(種別 3 値化 + external_tenant_id 列 + INVOKER guard + link/unlink/get RPC)/ mig98(越境読取 DEFINER RPC get_external_partner_settlement_summary / _items)。


前提・不変条件(必ず守る)

  • 公開 signup は external_partner を作れない(要確認1=A)。SIGNUP_TENANT_TYPES(api/web)と signup_invite_codes CHECK(mig48)は 2 値のまま。衛星テナント + founding admin は 運営スクリプト(service-role INSERT)でのみ作成する
  • external_tenant_id を書く正規経路は link/unlink RPC 一本。既存 API / PostgREST 直書きは INVOKER guard(external_partners_guard_external_tenant_link)で遮断される。
  • 委託先ユーザーは role='admin' 固定・profiles は作らない(discharger と同型)。越境読取 RPC は role/profiles に依存せず current_tenant_type()='external_partner' + current_tenant_id() で解決する。
  • read RPC は fail-closed(collector/discharger/種別 NULL/tenant_id NULL は空)・finalized のみ・ deleted_at is null 再明示・他委託先非漏洩・生 sign 非返却(signed_amount = amount×sign)・ vehicle_lease 明細は非表示(総額のみ)。

1. オンボーディング(順序固定・G11 TOCTOU 回避)

必ず (1) → (2) の順。逆にすると link RPC が「該当する委託先アカウントが見つかりません」で恒久 fail する (衛星テナントの auth.users がまだ存在せず count=0 になるため)。

(1) 衛星テナント + founding admin を作成(運営スクリプト・service-role)

bootstrap-org.mjs が external_partner の正規作成経路(service-role で tenants を直接 INSERT する。 tenants_type_check は mig97 で 3 値化済みなので通る)。invite-org.mjs は使えない — invite-org は signup_invite_codes へ INSERT する公開 signup 系で、その CHECK(mig48)は collector/discharger の 2 値に 固定されているため --tenant-type external_partner は CHECK 違反で失敗する(要確認1=A・意図的に非 3値化)。

bootstrap-org の実引数(--email は無い。email は --admin-login-id から loginIdToEmail で合成される):

node scripts/bootstrap-org.mjs \
  --org-name "◯◯運輸(委託先)" \
  --tenant-type external_partner \
  --admin-login-id ep-maruunyu-keiri \
  --admin-name "◯◯運輸 経理" \
  --password "<8文字以上の強いパスワード>"
  • 作成される admin の login email は ep-maruunyu-keiri@members.waste-link.local(合成メール)。
  • app_metadata は全種別共通で { tenant_id, tenant_type: 'external_partner', role: 'admin' } が焼き込まれる(G18)。 external_partner は profiles を作らない(discharger と同型・app_metadata だけでテナント解決)。
  • login_id 命名規約(要確認8): 衛星ユーザーの login_id は collector 側の driver / staff と別にする。 同一 login_id は合成メール(<login_id>@members.waste-link.local)の auth.users.email 一意制約に 抵触して 409 になる。実在の人物が「委託先の経理」と「共栄の driver」を兼ねる場合でも、 衛星ログイン用に別 login_id(例 ep-<会社>-keiri)を発行する。

共栄(collector)側の担当者(admin または normal)が、自社の委託先マスタ(external_partners)行を 衛星テナントに紐付ける。web UI(後続タスクで用意)または RPC を直接呼ぶ:

select public.link_external_partner_to_tenant(
  '<external_partners.id(自社所有)>',
  'ep-maruunyu-keiri'   -- 衛星 admin の login_id(@ 無しは合成メール ...@members.waste-link.local に変換)。
                        -- 合成メールをそのまま渡してもよい。
);
-- 返り値: { external_tenant_id, name }
  • link RPC は DEFINER で、current_tenant_id() で所有検証する。他社の external_partners 行や削除済み行は 「external_partner not found」で拒否(存在漏洩なし)。
  • email → 衛星テナント解決は tenant_type='external_partner' 厳密 + count(distinct)=1。不在/不一意は 「該当する委託先アカウントが見つかりません」(内部 warning にコードを残す)。
  • link 後、委託先が自社ログインで /external-partner/settlements(web)にアクセスすると、共栄が finalize 済みの自社帰属精算を閲覧できる。

現在の紐付け先の確認:

select public.get_external_partner_tenant_link('<external_partners.id>');  -- { external_tenant_id, name } | null

2. オフボーディング(解約・批評 critical#1)

委託先との取引を終了するときは、必ず以下の両方を実施する(二重防御)。read RPC は external_tenant_id(紐付け)と deleted_at(ソフトデリート)の両方で 0 件化するが、運用としては両方揃える。

select public.unlink_external_partner_from_tenant('<external_partners.id>');  -- { ok: true }
  • external_tenant_id を null に戻す。以降、衛星ログインでの越境読取は 0 件になる(join 述語で外れる)。
  • 他社行に対しては 0 行更新(存在漏洩なし)。

(b) 委託先マスタのソフトデリート(必要に応じて)

委託先マスタ自体を廃止する場合は external_partners.deleted_at をセット(法定保存ソフトデリート)。 read RPC は deleted_at is null を再明示しているため、削除済み委託先の精算は衛星に返らない。

衛星テナントの auth.users / tenants 行そのものの扱い(無効化 / 削除)は運営ポリシーに従う。 read RPC は紐付けと削除状態で閉じるため、アカウント残置でも精算は漏れない。


3. dev シード(rls-proof 用)

scripts/verify-external-partner-settlement-rls.mjs(rls-proofs CI)は、dev に以下が揃うと positive path (衛星 admin が自社 finalized 精算を取得)まで検証する。未整備の間は skip→exit 0(プロジェクトを 恒久的に赤くしない)。seed 手順:

  1. 上記 (1) で dev に衛星テナント + admin(既定 login_id demo-external-partner・password は demo 共通 Demo!2026)を作成。別 login_id を使う場合は CI env DEV_EP_SATELLITE_LOGIN を合わせる。
  2. 上記 (2) で collector demo の external_partners 行を衛星に link。
  3. collector demo 側で対象月(既定 2027-03-01・CI env DEV_EP_FINALIZED_MONTH)の委託精算を recomputefinalize(Phase2 の RPC)して finalized 精算を 1 件用意する。

fail-closed(N1 collector / N2 discharger が空)は衛星 seed が無くても検証される。


4. 参照

  • 種別 3 値化ドリフト防止: scripts/tenant-types-drift.test.mjs(8 箇所に external_partner が揃うこと + 公開 signup が external_partner を含まないことを機械固定)。
  • docker proof: scripts/proofs/mig97-external-partner-phase3.sql / mig98-external-partner-phase3.sql (allow/deny を素の postgres:15 で実証・#1–16)。
  • mobile は委託先未対応: 衛星ログインは起動時に封鎖(signOut)され driver UI には着地しない(§7.5)。