アクセス制御設計書 — 機能 × 権限マトリクス¶
目的: テナント種別(discharger / collector / external_partner)× ロール(admin / normal / driver)で、各機能の利用可否と強制レイヤ(route guard / nav / API middleware / RLS / mobile)を定義する設計書。
作成: 2026-07-07(
origin/develop実コードの横断監査。監査時点 99 migrations)。最終更新: 2026-07-10(設計書ドリフト訂正・監査時点 115 migrations)。file:line は監査時点のorigin/develop。実装変更時は本書も更新すること(特に RLS ポリシー・ルートガード・middleware)。関連: 認証・テナント運用 / ADR-0005 ロールモデル / ADR-0007 can_drive 能力フラグ / ADR-0017 RLS ハードニング
1. 権限モデル概説¶
1.1 テナント種別(3 値)¶
app_metadata.tenant_type に denormalize され、JWT 経由で RLS に反映される。Web は TENANT_TYPES allowlist で受理(未知値は null に落として fail-closed)。
| 種別 | 意味 | データ所有 | 実装根拠 |
|---|---|---|---|
discharger |
排出事業者(発注側)。案件・マニフェスト発行 | 自社データを持つ | AuthContext.tsx:21, nav-config.tsx:49-67 |
collector |
処理業者(受注側・収集/運搬/処分)。本システムの主要ユーザー | 自社データを持つ | AuthContext.tsx:21 |
external_partner |
委託先(衛星テナント)。自前業務データを持たず、collector 保持の自社帰属委託料精算を越境読取のみで閲覧。role='admin' 固定 |
データを持たない | AuthContext.tsx:16-21,244-246, TENANT_TYPES = ['discharger','collector','external_partner'] AuthContext.tsx:140 |
- 公開サインアップ(
POST /signup)はcollector/dischargerのみ。external_partnerは運営スクリプト(invite-org.mjs)でのみ作成(AuthContext.tsx:139コメント)。
1.2 ロール(3 値)¶
app_metadata.role 由来。collector 専用の概念で、discharger は現状ロール未設定(null)でも admin 相当として動作する。
| ロール | 意味 | 実装根拠 |
|---|---|---|
admin |
テナント管理者。メンバー管理・車両/工場新規作成(mig44)・精算 finalize(mig94)など admin 限定操作 | AuthContext.tsx:29,44-46 |
normal |
一般事務スタッフ(collector 専用)。車両/工場の新規作成は不可 | AuthContext.tsx:32,35-37 |
driver |
現場ドライバー(collector 専用)。事務操作不可・配車/実績の自分の行のみ | AuthContext.tsx:29,60-62 |
COLLECTOR_STAFF_ROLES = ['admin','normal'](AuthContext.tsx:32)— driver を除外する「事務ロール」の集合。
1.3 can_drive(能力フラグ・ロールと直交)¶
app_metadata.can_drive(boolean)。ドライバー画面・配車候補・「本日のルート」ナビの唯一の判定基準。
canActAsDriver(role, canDrive) = role==='driver'
|| ((role==='normal' || role==='admin') && canDrive===true)
AuthContext.tsx:60-62)
admin + can_driveは admin 権限(メンバー管理・作成等)を一切失わず、運転も兼務できる(AuthContext.tsx:58)。- ナビは
can_drive=trueの normal/admin に「本日のルート」(/driver) を先頭追加する(nav-config.tsx:155-181,206-215)。
1.4 強制レイヤ(多層防御・5〜6 層)¶
| 層 | 役割 | 実装 |
|---|---|---|
| Web ルートガード | 種別/ロール/運転能力でページ到達を制御 | RoleGuard.tsx:41-144(ProtectedRoute が前段の認証ゲート ProtectedRoute.tsx) |
| Web ナビ絞り | メニュー表示を種別+role+can_drive で出し分け(表示制御) | nav-config.tsx:201-215(resolveNav) |
| API middleware | エンドポイント認可 | auth.ts: requireAuth/requireCollectorAdmin/requireCollectorStaff/requireDischarger/requireManifestCreator |
| DB RLS | 行レベル最終防御(current_tenant_id()/current_user_role()) |
supabase/migrations/** |
| DEFINER RPC | 越境読取のみを制御(RLS をバイパスし body 内で再フィルタ) | mig98(委託精算), mig94(finalize/recompute) |
| Mobile ルーティング | ネイティブ起動時の種別ベース封鎖 | routing.ts:16-36(homeForRole/shouldLockoutMobile) |
認可の権威は JWT の app_metadata(tenant_id/tenant_type/role/can_drive)。profiles.role/can_drive は表示コピーでドリフト対策(team.ts が両方を更新)。
1.5 ランディング(初期遷移先)¶
resolveLandingPath(tenantType, role)(AuthContext.tsx:231-249):
| 種別/ロール | 着地先 |
|---|---|
| discharger | /discharger |
| collector + driver | /driver |
| collector + admin/normal/null | /collector |
| external_partner | /external-partner/settlements |
| 不整合(tenant_type 未設定) | null(未割当エラー) |
- Mobile: discharger→
(discharger)/ collector+driver→(driver)/ collector+staff→(collector)/ それ以外(external_partner・null・未知)→(locked)で起動時signOut(fail-closed・routing.ts:20-35)。 - 特例(
error-no-role): collector かつrole=nullの拒否だけはリダイレクトすると/collectorへ戻り無限ループになるため、Navigate せずインラインのエラー画面を出す(RoleGuard.tsx:87-101,123-141)。
2. 機能一覧(業務領域別)¶
各機能に (対象テナント種別, 対象ロール, web/mobile, 主な強制箇所) を付す。
2.1 認証・組織オンボーディング¶
| 機能 | 種別 | ロール | web/mobile | 主な強制 |
|---|---|---|---|---|
| サインイン | 全(公開) | 不問 | web/mobile | ガードなし App.tsx:148 |
| 組織登録(サインアップ) | collector, discharger | 公開 | web | App.tsx:149。POST /signup(招待コード照合 + register_signup_invite_attempt DEFINER RPC・原子的試行上限)signup.ts / mig48・mig50 |
| Auth コールバック | 全(公開) | — | web | ガードなし App.tsx:150 |
2.2 メンバー・組織管理¶
| 機能 | 種別 | ロール | web/mobile | 主な強制 |
|---|---|---|---|---|
| メンバー一覧画面 | collector | admin/normal(閲覧)※書込導線は admin | web | App.tsx:347-355(allowedRoles=COLLECTOR_STAFF_ROLES) |
| メンバー作成/招待 | collector | admin | web→API | POST /team/members team.ts:177 + requireCollectorAdmin auth.ts:66-75 |
| パスワード初期化 | collector | admin | API | team.ts:248 |
| ロール変更 | collector | admin | API | team.ts:290 + 最後の admin ロック(409・TOCTOU 対策) |
| can_drive 変更 | collector | admin | API | team.ts:400 |
| メンバー削除 | collector | admin | API | team.ts:469 + 最後の admin ロック |
| 会社情報/プロフィール | collector, discharger | collector: admin/normal | web | App.tsx:325-333(/collector/profile) |
RLS: profiles は SELECT がテナント全開放(mig8:38-40)、UPDATE/DELETE は admin のみ(mig8:47-63)。書込 API は service_role 経由。
2.3 案件(Jobs・discharger)¶
| 機能 | 種別 | ロール | web/mobile | 主な強制 |
|---|---|---|---|---|
| 案件一覧/新規/詳細 | discharger | admin 相当(role 不問) | web/mobile | App.tsx:163-192(allowedTenantType='discharger')。Mobile (discharger) group |
| 見積(AI マッチング) | discharger | — | web | App.tsx:194-207 は ComingSoonPage(準備中) |
2.4 マニフェスト・OCR¶
| 機能 | 種別 | ロール | web/mobile | 主な強制 |
|---|---|---|---|---|
| 排出側 マニフェスト一覧/新規/詳細 | discharger | admin 相当 | web/mobile | App.tsx:208-236(allowedTenantType='discharger') |
| 自社マニフェスト一覧 | collector | admin/normal | web | App.tsx:303-312 |
| 自社マニフェスト新規(KYO-104 Phase A) | collector | admin/normal(driver 除外) | web | App.tsx:315-324 |
| マニフェスト OCR(Claude/Vision 課金) | discharger, collector | discharger: 全 / collector: admin|normal | API | POST /manifests/ocr manifests.ts:42 + requireManifestCreator(canCreateManifest auth.ts:143-164) |
RLS carve-out(mig88:182-197 INSERT / :199-222 UPDATE): tenant_id 一致 AND (tenant_type='discharger' OR role IN ('admin','normal'))。driver は弾かれる。API canCreateManifest はこの判定面と一致。collector が担当段階(carrier/disposal)を stamp する UPDATE は mig89(ja.status in ('accepted_by_discharger','completed') の担当割当が前提・列制限はガードトリガ)。
2.5 取引先・委託先マスタ¶
| 機能 | 種別 | ロール | web/mobile | 主な強制 |
|---|---|---|---|---|
| 取引先(partners)一覧/詳細/CSV 取込(本体・回収先) | collector | admin/normal | web | App.tsx:357-397(COLLECTOR_STAFF_ROLES)。RLS mig87 書込 admin|normal |
| 委託先(external_partners)一覧/詳細 | collector | admin/normal | web | App.tsx:399-408,454-463。RLS mig91:72-100(SELECT/INSERT/UPDATE=admin|normal・DELETE=admin ソフトデリート) |
| 委託先 CSV 取込(委託先/支払単価/車両リース) | collector | admin/normal | web | App.tsx:411-440 |
| discharger 側 処理業者一覧/詳細 | discharger | admin 相当 | web | App.tsx:238-256 |
RLS mig92: 金額 2 表(external_partner_item_rates/vehicle_leases)は SELECT も書込も admin|normal のみ(driver は 0 行・原価秘匿 mig92:12-16)。external_partner_drivers(タグ付け)は staff=テナント全体・driver=自分の active 所属のみ(mig99:88-109 で封鎖)。
2.6 配車・ドライバー現場¶
| 機能 | 種別 | ロール | web/mobile | 主な強制 |
|---|---|---|---|---|
| 配車ボード | collector | admin/normal | web | App.tsx:563-572(COLLECTOR_STAFF_ROLES) |
| コースビルダー | collector | admin/normal | web | App.tsx:578-587 |
| 本日のルート | collector | driver | can_drive な normal/admin | web/mobile | App.tsx:590-598(allowDriverCapable)。Mobile (driver)/index |
| 運行ログ | collector | 同上 | web/mobile | App.tsx:610-618。Mobile (driver)/daily-log |
| 履歴 | collector | 同上 | web/mobile | App.tsx:600-608 |
| 工場ボード | collector | 同上 | web/mobile | App.tsx:620-628 |
RLS dispatch_stops: 書込 admin|normal(mig41:29-31)、SELECT はテナント全件("tenant reads dispatch stops" mig41:35-36・mig55/mig72 で再宣言)、driver の自 stop UPDATE は列をガードトリガで status/completed_at/note に限定(mig25:92-108)。→ §4 の受容リスク参照。
2.7 計量(weighings・法定記録)¶
| 機能 | 種別 | ロール | web/mobile | 主な強制 |
|---|---|---|---|---|
| 計量一覧/新規/CSV 取込 | collector | admin/normal | web | App.tsx:464-483(COLLECTOR_STAFF_ROLES) |
| 自分の計量実績(driver 可視化・KYO-106 Wave C) | collector | driver | can_drive な normal/admin | web/mobile | App.tsx:630-638(allowDriverCapable)→ DriverWeighingsPage(notes 除外) |
RLS(mig99・二段変更): weighings/weighing_items SELECT を「staff 限定(admin|normal・mig99:27-33,55-65)」+「driver-own 派生(attribution_driver_id→profiles で自分のみ・mig99:37-49,69-83)」に分割。permissive OR 合成漏れを避けるため既存 SELECT を staff 限定へ絞ってから driver-own を加算。INSERT は collector(mig66:51-53)。complete_weighing RPC は SECURITY INVOKER。
2.8 車両・コスト¶
| 機能 | 種別 | ロール | web/mobile | 主な強制 |
|---|---|---|---|---|
| 車両一覧/詳細 | collector | admin/normal | web | App.tsx:498-507 |
| 車両 CSV 取込(新規作成含む) | collector | admin のみ | web | App.tsx:510-519(allowedRole="admin")。RLS mig44 vehicles INSERT=admin |
| コスト集計/新規 | collector | admin/normal | web | App.tsx:520-539 |
| 請求書 OCR 取込(KYO-100・Claude 課金) | collector | admin/normal(PR#139 で admin 限定→開放) | web→API | App.tsx:543-552 + POST /vehicle-costs/ocr vehicle-costs.ts:26 + requireCollectorStaff auth.ts:87-96。RLS mig43 INSERT=admin|normal |
| 車両日報 新規 | collector | admin/normal | web | App.tsx:553-561 |
RLS: vehicles/factories の INSERT(新規作成)は admin のみ(mig44:46-52)。UPDATE/DELETE・日報・コストは admin|normal(mig43)。vehicle_cost_entries は admin=全車両、driver=自担当車両のみ SELECT/INSERT/UPDATE(mig22:67-145)。
2.9 締め・委託精算¶
| 機能 | 種別 | ロール | web/mobile | 主な強制 |
|---|---|---|---|---|
| 委託精算(collector 所有・Phase2) | collector | admin/normal(閲覧・recompute・adjustment)/finalize=admin | web | App.tsx:444-452(/collector/external-partner-settlements)。RLS mig94 SELECT=admin|normal(:214)/直接 INSERT/UPDATE/DELETE=全て false(:216-222) |
| 精算 recompute / adjustment | collector | admin/normal | DEFINER RPC | mig94 recompute_*(:281 staff ゲート)/ add_*_adjustment(admin|normal) |
| 精算 finalize(draft→finalized) | collector | admin のみ | DEFINER RPC | mig94 finalize_external_partner_settlement(:447 is distinct from 'admin' → raise・NULL fail-closed) |
| 委託精算 越境読取(external_partner 側・Phase3) | external_partner | admin(固定) | web/mobile 不可 | App.tsx:271-279(/external-partner/settlements)→ DEFINER RPC mig98 |
| 月次集計 | collector | admin/normal | web | /collector/monthly(App.tsx の CollectorMonthlyPage ルート)。ナビからは PR #207 でメニュー除外済(nav-config.tsx:109 コメント「月次集計は準備中のため一旦メニューから除外…ルート実体は温存」)=status:'soon' ではなく nav 項目自体が非掲載。ルート実体・URL 直打ちは温存 |
精算不変性は方針B=スナップショット: 金額 4 列は RLS 全面 false + eps_guard_finalized トリガ(finalized→finalized の金額改竄を authenticated 直書きで遮断・mig94:182-202)+ recompute 本体の if status='finalized' then raise。
2.10 外部パートナー(衛星テナント・越境読取)¶
| 機能 | 種別 | ロール | web/mobile | 主な強制 |
|---|---|---|---|---|
| 委託精算サマリ/明細 閲覧 | external_partner | admin(固定) | web のみ | App.tsx:271-279(allowedTenantType='external_partner'・allowedRole は付けない=role='admin' 固定運用)。専用ナビ EXTERNAL_PARTNER_NAV(nav-config.tsx:75-80・fail-open G7 是正)。Mobile は (locked) 封鎖 |
越境読取は DEFINER RPC のみ(mig98):
- get_external_partner_settlement_summary(date) / _items(date) — GET 相当・引数に tenant を取らない(越権封じ)。
- gate: coalesce(tenant_type='external_partner' AND tenant_id is not null, false) is not true → '[]'(fail-closed・裸比較の NULL fail-open を回避 mig98:35-36)。
- body 内で ep.external_tenant_id = current_tenant_id()・ep.deleted_at is null・status='finalized' を再明示(RLS バイパスのため)。draft/soft-delete/unlink は遮断。
- 共栄内部列(driver_id/attribution_driver_id/note/原価・vehicle_lease 明細・生 sign 列・quantity)は非返却。sign は signed_amount = amount×sign に潰す(mig98:76)。
2.11 設定・課金¶
| 機能 | 種別 | ロール | web/mobile | 主な強制 |
|---|---|---|---|---|
| 設定(一般/通知/セキュリティ) | 全 | 全(ロール制約なし) | web | App.tsx:642-664(ProtectedRoute のみ・RoleGuard なし) |
| 請求・サブスク | collector, discharger(external_partner 除外) | admin | web→API | App.tsx:667-679(allowedTenantTypes=['collector','discharger'] + allowedRole='admin')。API billing.ts が isExternalPartnerTenant→403(auth.ts:113-115) |
| JWNET 連携 | discharger | admin 相当 | web | App.tsx:267-275(/discharger/jwnet)。ナビ status:'soon'・UI 到達不可・ページ実体温存(nav-config.tsx:54-60) |
3. 権限マトリクス(主要機能 × テナント×ロール)¶
凡例: ○=可 / ×=不可 / △=条件付き(脚注)/ —=そのロールは当該種別に存在しない
| 機能 | col-admin | col-normal | col-driver | dis-admin | ep-admin |
|---|---|---|---|---|---|
| ダッシュボード(自種別) | ○ | ○ | ○(driver) | ○ | ○(settlements) |
| 案件(Jobs) | × | × | × | ○ | × |
| マニフェスト作成/OCR | ○ | ○ | ×¹ | ○ | × |
| マニフェスト閲覧 | ○ | ○ | △² | ○ | × |
| 取引先マスタ | ○ | ○ | × | × | × |
| 委託先マスタ | ○ | ○ | × | × | × |
| 配車ボード/コース | ○ | ○ | ׳ | × | × |
| 配車 stop 閲覧(RLS) | ○ | ○ | ○⁴ | — | — |
| ドライバー画面(本日/実績/ログ/工場) | △⁵ | △⁵ | ○ | × | × |
| 計量 一覧/新規(staff 面) | ○ | ○ | × | × | × |
| 自分の計量実績(driver 可視化) | △⁵ | △⁵ | ○⁶ | × | × |
| 車両 新規作成/CSV 取込 | ○ | ×⁷ | × | × | × |
| 車両 閲覧/コスト/OCR/日報 | ○ | ○ | ×⁸ | × | × |
| 委託精算(collector・閲覧/recompute) | ○ | ○ | × | × | × |
| 委託精算 finalize | ○ | ×⁹ | × | × | × |
| 委託精算 越境読取(衛星) | × | × | × | × | ○ |
| メンバー管理(作成/ロール/削除) | ○ | ×¹⁰ | × | ×¹¹ | × |
| 請求・課金設定 | ○ | × | × | ○ | ×¹² |
| 設定(一般/通知/セキュリティ) | ○ | ○ | ○ | ○ | ○ |
| JWNET 連携 | — | — | — | △¹³ | — |
※列: col=collector / dis=discharger / ep=external_partner。dis-normal / dis-driver / ep-normal / ep-driver / ep-driver は実運用上存在しない(discharger は role 未設定=admin 相当、external_partner は admin 固定)ため列から省略。
脚注
1. マニフェスト作成/OCR: driver は canCreateManifest で false(auth.ts:143-150)・RLS mig88 carve-out も admin|normal のみ。
2. マニフェスト閲覧: collector driver は SELECT ポリシー依存(staff/driver 面の詳細は各表 RLS)。creation は不可。
3. 配車ボード(web): allowedRoles=COLLECTOR_STAFF_ROLES で driver は到達不可。ただし RLS 上の stop 閲覧は別(脚注4)。
4. 配車 stop 閲覧(RLS): dispatch_stops SELECT は "tenant reads dispatch stops"(mig41:35-36)でテナント全件開放 → driver も RLS 上は全 stop を読める。UI は配車ボードを staff に絞るだけ(受容リスク・§4)。
5. can_drive=true な admin/normal はドライバー画面・自分の計量実績にアクセス可(canActAsDriver AuthContext.tsx:60-62)。can_drive=false なら不可。
6. 自分の計量実績: driver は RLS 上 attribution_driver_id=自分 の行のみ(mig99:37-49)。
7. 車両新規作成/CSV 取込: normal は RLS mig44(INSERT=admin)で 0 行 + web allowedRole="admin"(App.tsx:514)。
8. 車両コスト等: driver は自担当車両のみ(mig22)だが web の staff 面(/collector/fleet/*)は COLLECTOR_STAFF_ROLES で到達不可。
9. finalize: mig94:447 で admin 以外は raise(NULL fail-closed)。normal は recompute/adjustment までは可。
10. メンバー管理書込: requireCollectorAdmin(auth.ts:66-75)で normal は 403。閲覧画面は staff で到達可。
11. discharger のメンバー管理 API は requireCollectorAdmin が tenant_type==='collector' を要求するため discharger は 403(現状 collector 専用機能)。
12. external_partner は allowedTenantTypes から除外(web)+ API isExternalPartnerTenant→403(auth.ts:113-115)。
13. JWNET はナビ status:'soon' で UI 到達不可(機能未提供・ページ実体は温存)。
4. RLS レベルの補足(UI で隠れても DB で防がれる/防がれない点)¶
4.1 UI 制限だけで RLS はテナント全開放(受容リスク)¶
| テーブル | RLS SELECT | UI 制限 | リスク |
|---|---|---|---|
profiles |
テナント全開放(mig8:38-40・role 述語なし) |
メンバー画面は staff 到達 | 同一テナントの authenticated(driver 含む)が API を直叩けば全メンバーの表示名/ロール/login_id 相当を読める。書込は admin のみで防御。受容(同一組織内の名簿共有は許容範囲) |
dispatch_stops |
テナント全開放("tenant reads dispatch stops" mig41:35-36) |
配車ボードは COLLECTOR_STAFF_ROLES | driver も RLS 上は全 stop を読める(自分の割当外含む)。mig41 コメントが意図的な設計と明記。書込は admin|normal(mig41:29-31)+ driver 自 stop の限定列 UPDATE(mig25:92-108)で防御。受容 |
dispatch_route_templates |
テナント全開放(mig41:24-25) |
同上 | 同上。受容 |
これらは「UI で隠れていても RLS では防がれない」典型。ただし書込は role/自行に限定されており、越境(他テナント)は tenant_id = current_tenant_id() で常に遮断される。
4.2 UI で隠れており RLS でも防がれる(多層一致)¶
- 金額秘匿(driver 除外):
weighings/weighing_items(mig99 staff 限定+driver-own)、external_partner_item_rates/vehicle_leases(mig92 admin|normal のみ・driver 0 行)、external_partner_settlements(mig94:214 SELECT=admin|normal)。UI ガードだけでなく RLS でも driver に金額を見せない。 - 越境(テナント跨ぎ): 全表
tenant_id = current_tenant_id()。external_partner の越境読取は通常 RLS では構造的に不可能で、DEFINER RPC(mig98)のみが唯一の経路。RPC はfinalized・deleted_at is null・自社紐付けを body 内で再適用し fail-closed で[]を返す。 - 精算改竄: 金額列 RLS 全面 false + finalized 後は
eps_guard_finalizedトリガ(authenticated 直書き遮断)+ recompute の finalized-raise。UI に finalize ボタンが無くても DB が防ぐ。 - 車両新規作成: RLS mig44(INSERT=admin)が web
allowedRole="admin"と一致。normal は RLS 上も INSERT 0 行。
4.3 fail-closed の徹底¶
current_tenant_id()/current_user_role() は JWT claim 欠落時 NULL を返し、=/in の 3 値論理で行が落ちる。DEFINER RPC の gate は coalesce(..., false) と is distinct from で NULL fail-open を明示的に潰している(mig98:35-36, mig94:447)。
5. レイヤ不整合の指摘¶
実装は概ね多層一致だが、以下は「層ごとに強制水準が異なる」箇所として設計上把握しておくべき点。
- 配車 stop 閲覧: nav/route は staff 限定・RLS はテナント全開放(意図的)
-
Web は
/collector/dispatch*を COLLECTOR_STAFF_ROLES に絞る(App.tsx:563-587)が、RLSdispatch_stopsSELECT はテナント全件(mig41:35-36)。driver は API 直叩きで全 stop を読める。mig41 が「SELECT=テナント全件」を明示設計しておりバグではなく受容リスク。ただし将来 driver に自 stop のみ見せたい要件が出たら RLS 側の絞り込み(mig99 の weighings と同型の staff+driver-own 二段化)が必要。 -
profiles: 名簿がテナント全 authenticated に開放
-
メンバー管理 UI は staff 到達だが、
profilesSELECT は role 述語なし(mig8:40)。driver も全メンバーを読める。書込は admin のみで実害は限定的だが、RLS 単体では「driver に他メンバー情報を見せない」保証はない(UI と RLS の水準差)。 -
external_partner の role 固定は「運用規約」で機械強制されない層がある
-
Web ルートは
allowedTenantType='external_partner'のみでallowedRoleを付けない(App.tsx:270-279・G9 コメント)=role='admin' 固定は「衛星は admin でしか作られない」運用前提。もし将来 external_partner に normal/driver を発行すると、ルートガードは素通りする(越境データは DEFINER RPC が gate するため実害はないが、種別だけで通す設計であることを明記)。 -
課金除外は Web/API 二重で一致(不整合なし・良い例)
-
external_partner の billing 除外は Web(
allowedTenantTypes除外)と API(isExternalPartnerTenant→403)の両層。多層防御が正しく効いている参考例。 -
discharger のロール未設定(null)を admin 相当で通す設計
- discharger 系ルートは
allowedRoleを付けず role=null を許容(resolveLandingPathも /discharger へ)。collector と異なりerror-no-role特例の対象外(RoleGuard.tsx:96-99)。discharger に将来ロール概念を入れる場合はこの暗黙 admin 相当を明示化する必要がある。
検証根拠(file:line は origin/develop・当初 99 migrations で実確認・2026-07-10 に 115 migrations で再点検)¶
- 3 値テナント:
AuthContext.tsx:16-21,140/ mig97-99 実在(ls supabase/migrations= 115 本・2026-07-10 時点) - ルート表:
App.tsx:148-703(全ルート実読) - ガード純粋関数:
RoleGuard.tsx:41-101 - API middleware:
auth.ts:66-164 - ナビ:
nav-config.tsx:49-215 - Mobile:
routing.ts:16-36 - RLS: mig8(profiles)/ mig25・41・55・72(dispatch_stops)/ mig44(vehicles admin)/ mig88-89(manifests)/ mig92(rates/leases/drivers)/ mig94(settlements finalize)/ mig98(越境 RPC)/ mig99(driver 計量可視化)
- team API:
team.ts:177,248,290,400,469
矛盾採用: レンズ間で異なっていた「dispatch_stops driver SELECT」は、mig25 の driver-only ポリシーが mig27→mig41 で "tenant reads dispatch stops"(テナント全開放)に置換されているため最新の mig41:35-36 を採用(一部レンズが mig25 の古い記述を引いていたのを訂正)。stale tree(ad4347/mig96/2値テナント)は一切参照していない。