アクセス制御設計書 — 機能 × 権限マトリクス¶
目的: テナント種別(discharger / collector / external_partner)× ロール(admin / normal / driver)で、各機能の利用可否と強制レイヤ(route guard / nav / API middleware / RLS / mobile)を定義する設計書。
最終更新: 2026-08-06(マイグレーション本数は
ls supabase/migrations | wc -lが正。現行 Web/API/Mobile を反映)。現行ルートはapps/web/src/App.tsxのパスと各ページ関数、ナビはapps/web/src/components/shell/nav-config.tsxのresolveNav/weighingTabsを典拠とする。実装変更時は本書も更新すること(特に RLS ポリシー・ルートガード・middleware)。
ロールと工場スコープの判定¶
この図は、JWT の欠落・テナント不一致・ロール不許可・工場スコープ外をすべて拒否する判定順を示します。
ロールと工場スコープの判定フロー。
関連: 認証・テナント運用 / 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:180 |
| 組織登録(サインアップ) | collector, discharger | 公開 | web | App.tsx:181。POST /signup(招待コード照合 + register_signup_invite_attempt DEFINER RPC・原子的試行上限)signup.ts / mig48・mig50 |
| Auth コールバック | 全(公開) | — | web | ガードなし App.tsx:182 |
2.2 メンバー・組織管理¶
| 機能 | 種別 | ロール | web/mobile | 主な強制 |
|---|---|---|---|---|
| メンバー一覧画面 | collector | admin/normal(閲覧)※書込導線は admin | web | App.tsx:460-467(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 | web→API | POST /team/members/:id/profile team.ts:283 + requireCollectorAdmin。service-role で対象 profile を取得し tenant_id != 自テナント→403(越境防御)・更新列は display_name/email の2列のみ(role/can_drive/tenant_id を書かず権限昇格を構造的に排除)・auth user_metadata.display_name を同期。migration なし(mig138 で admin の同一テナント peer display_name/email UPDATE は RLS 開通済) |
| メンバー削除 | collector | admin | API | team.ts:469 + 最後の admin ロック |
| 会社情報/プロフィール | collector, discharger | collector: admin/normal | web | App.tsx:408-415(/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:195-223(allowedTenantType='discharger')。Mobile (discharger) group |
| 見積(AI マッチング) | discharger | — | web | App.tsx:225-239 は ComingSoonPage(準備中) |
2.4 マニフェスト・OCR¶
| 機能 | 種別 | ロール | web/mobile | 主な強制 |
|---|---|---|---|---|
| 排出側 マニフェスト一覧/新規/詳細 | discharger | admin 相当 | web/mobile | App.tsx:240-299(allowedTenantType='discharger') |
| 自社マニフェスト一覧 | collector | admin/normal | web | App.tsx:355-363 |
| 自社マニフェスト新規(KYO-104 Phase A) | collector | admin/normal(driver 除外) | web | App.tsx:367-375 |
| マニフェスト OCR(Claude/Vision 課金) | discharger, collector | discharger: 全 / collector: admin|normal|driver | API | POST /manifests/ocr manifests.ts:42 + requireManifestOcrCaller(作成権限者 + collector driver。mig175)。作成・確定(canCreateManifest)は driver に開いていない |
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:490-539(COLLECTOR_STAFF_ROLES)。RLS mig87 書込 admin|normal |
| 委託先(external_partners)一覧/詳細 | collector | admin/normal | web | App.tsx:543-550,598-605。RLS mig91:72-100(SELECT/INSERT/UPDATE=admin|normal・DELETE=admin ソフトデリート) |
| 委託先 CSV 取込(委託先/支払単価/車両リース) | collector | admin/normal | web | App.tsx:552-583 |
| discharger 側 処理業者一覧/詳細 | discharger | admin 相当 | web | App.tsx:300-318 |
| 二次搬出運搬費 | collector | admin/normal | web | /collector/secondary-transport-routes → SecondaryTransportRoutesPage。resolveNav |
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 | /collector/dispatch → DispatchBoardPage。COLLECTOR_STAFF_ROLES |
| コースビルダー | collector | admin/normal | web | /collector/dispatch/courses → CourseBuilderPage。COLLECTOR_STAFF_ROLES |
| 本日のルート | collector | driver | can_drive な normal/admin | web/mobile | /driver → CollectorDriverHome。allowDriverCapable。Mobile (driver)/index |
| 運行日報(本人入力) | collector | driver | can_drive な normal/admin | web | /driver/daily-report → DriverDailyReportPage。allowDriverCapable |
| 運行日報(事務閲覧) | collector | admin/normal | web | /collector/driver-daily-reports → DriverDailyReportsPage |
| 工場ボード | collector | driver | can_drive な normal/admin | web/mobile | /driver/board → DriverFactoryBoardPage。Mobile は (driver)/index から遷移 |
| 横乗りの完了/取消 | collector | 担当 driver / 横乗り driver | web/mobile | CollectorDriverHome の完了・取消処理。is_ride_along_for_stop(mig177) |
Web に /driver/history と /driver/weighings のルートは存在しない。配車履歴・計量実績は Mobile の (driver)/history.tsx / (driver)/weighings.tsx に限る。
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 | 主な強制 |
|---|---|---|---|---|
| 社内計量(single/run) | collector | admin/normal | web | /collector/weighings → CollectorWeighingPage。COLLECTOR_STAFF_ROLES |
| 計量一覧・後日修正・CSV 取込 | collector | admin/normal | web | /collector/weighings/list → WeighingHistoryPage、/collector/weighings/import は redirect のみ(UI 撤去・DB 関数温存) |
| 回収数量レポート・CSV / Excel | collector | admin/normal | web | /collector/reports/collection-volume → CollectionVolumeReportPage。COLLECTOR_STAFF_ROLES、自テナントの通常・コース確定明細のみ |
| 目方入力 | collector | driver | can_drive な normal/admin | web/mobile | /driver/run-weighing/:weighingId → DriverRunWeighingPage。allowDriverCapable |
| 目方入力済み一覧 | collector | driver | can_drive な normal/admin | web/mobile | /driver/mekata-entered/:weighingId → DriverMekataListPage |
| 持ち込み計量・計量一覧への統合 | collector | admin/normal | web | weighingTabs の /collector/walk-in-weighing → WalkInWeighingPage。一覧・検索・再発行・入金記録は計量一覧へ統合(#533) |
| 品目単位設定 | collector | admin/normal | web | /collector/item-unit-settings → ItemUnitSettingsPage |
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:697-705 |
| 車両 CSV 取込(新規作成含む) | collector | admin のみ | web | App.tsx:708-716(allowedRole="admin")。RLS mig44 vehicles INSERT=admin |
| コスト集計/新規 | collector | admin/normal | web | App.tsx:719-737 |
| 請求書 OCR 取込(KYO-100・Claude 課金) | collector | admin/normal(PR#139 で admin 限定→開放) | web→API | App.tsx:719-737 + POST /vehicle-costs/ocr vehicle-costs.ts:26 + requireCollectorStaff auth.ts:87-96。RLS mig43 INSERT=admin|normal |
| 車両日報 新規 | collector | admin/normal | web | App.tsx:744-752 |
| ドライバーマスタ | collector | admin のみ | web | /collector/drivers / /collector/drivers/:profileId → DriverMasterPage / DriverMasterDetailPage。allowedRole="admin" |
RLS: vehicles/factories の INSERT(新規作成)は admin のみ(mig44:46-52)。UPDATE/DELETE・日報・コストは admin|normal(mig43)。vehicle_cost_entries は admin/normal=自テナント全件(deleted_at is null)、driver は read/write とも不可。
2.9 締め・委託精算¶
| 機能 | 種別 | ロール | web/mobile | 主な強制 |
|---|---|---|---|---|
| 委託精算(collector 所有・Phase2) | collector | admin/normal(閲覧・recompute・adjustment)/finalize=admin | web | App.tsx:587-596(/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:334-342(/external-partner/settlements)→ DEFINER RPC mig98 |
精算不変性は方針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 | /settings → SettingsPage。ProtectedRoute のみ |
| 請求プレビュー | collector | admin/normal | web | /collector/billing/preview → CollectorBillingPreviewPage。COLLECTOR_STAFF_ROLES |
| 確定済み請求書一覧/詳細 | collector | admin/normal(閲覧)/finalize・soft-delete=admin | web→DEFINER RPC | App.tsx:660-668(/collector/billing/invoices)。mig203 finalize_billing_invoice / soft_delete_billing_invoice は admin のみ、直接 INSERT/UPDATE policy は無し |
| 請求書発行設定 | collector | admin のみ | web | /collector/settings/billing → CollectorBillingSettingsPage。allowedRole="admin" |
| 請求・サブスク | collector, discharger(external_partner 除外) | admin | web→API | /settings/billing → BillingSettingsPage。allowedTenantTypes=['collector','discharger'] + allowedRole='admin'。API(GET /billing/subscription・POST /billing/checkout)も requireBillingAdmin で admin 限定・DB subscriptions SELECT も admin 限定(mig265・current_tenant_type() in ('collector','discharger') を policy に含む・role / tenant_type の NULL は fail-closed)。POST /billing/webhook は認証なし・Stripe 署名検証のみ |
| JWNET 連携 | discharger | admin 相当 | web | /discharger/jwnet は標準EDI 移行中。旧 Web-EDI の送信 UI は #587 で撤去済み、DB/API の判定・資格情報基盤だけを温存 |
2.12 追加・更新された現行面¶
| Migration | 現行面と典拠 |
|---|---|
| mig116, 118, 126, 131, 143, 144, 200 | 社内計量・目方入力・目方入力済み一覧・品目単位設定。CollectorWeighingPage / DriverRunWeighingPage / DriverMekataListPage / ItemUnitSettingsPage |
| mig119〜122 | ドライバーマスタとドライバーファイル。DriverMasterPage / DriverMasterDetailPage |
| mig123 | OCR 取込・レビューキュー。/collector/manifests/import / /collector/manifests/review |
| mig124〜125, 129〜130, 132〜134, 141, 173, 186 | 収支・車両費用・日付/非有限値の強化。既存のダッシュボード・費用画面と集計関数に反映(新規ルートなしの migration を含む) |
| mig128, 181〜182, 190, 192〜194 | マニフェスト交付区分・JWNET 対象判定・Form 3。/collector/manifests と /discharger/manifests/form3 |
| mig135〜142, 152, 158, 169, 172, 177, 183〜188 | 配車、回収順、横乗り、完了/取消、車両・二次搬出。DispatchBoardPage / CollectorDriverHome / SecondaryTransportRoutesPage |
| mig145〜162, 167〜168, 178, 193 | JWNET 資格情報、Web-EDI、結果整合性、照会の DB/API 基盤。旧送信 UI は撤去済みで、標準EDI の認可・結果相関へ引き継ぐ |
| mig164〜166, 191, 196 | 回収先・車両・取引先のテナント境界と複数ドライバー対応。既存マスタ画面と RLS に反映 |
| mig174, 176, 179, 189, 195, 197 | 運行日報と soft-delete/claim。/driver/daily-report → DriverDailyReportPage、/collector/driver-daily-reports → DriverDailyReportsPage |
| mig203〜204 | 請求書の確定スナップショット・採番・確定済み一覧。/collector/billing/preview / /collector/billing/invoices |
| mig175, 201 | 二次搬出マニフェスト写真とコボレーン確認。CollectorDriverHome / KobolaneConfirmationSheet |
| mig199 | 持ち込み計量・債権情報。WalkInWeighingPage。一覧は計量一覧へ統合(#533) |
| mig127, 137〜138, 170〜171, 180, 187, 198 | DB hardening・権限/監査・resolver の強化(新規ユーザー画面なし) |
| mig202 | 請求プレビューと請求書発行設定。CollectorBillingPreviewPage / CollectorBillingSettingsPage |
3. 権限マトリクス(主要機能 × テナント×ロール)¶
凡例: ○=可 / ×=不可 / △=条件付き(脚注)/ —=そのロールは当該種別に存在しない
| 機能 | col-admin | col-normal | col-driver | dis-admin | ep-admin |
|---|---|---|---|---|---|
| ダッシュボード(自種別) | ○ | ○ | ○(driver) | ○ | ○(settlements) |
| 案件(Jobs) | × | × | × | ○ | × |
| マニフェスト作成・確定 | ○ | ○ | ×¹ | ○ | × |
撮影後 OCR(POST /manifests/ocr) |
○ | ○ | ○¹ | ○ | × |
| マニフェスト閲覧 | ○ | ○ | △² | ○ | × |
| 取引先マスタ | ○ | ○ | × | × | × |
| 委託先マスタ | ○ | ○ | × | × | × |
| 配車ボード/コース | ○ | ○ | ׳ | × | × |
| 配車 stop 閲覧(RLS) | ○ | ○ | ○⁴ | — | — |
| ドライバー画面(本日/日報/工場) | △⁵ | △⁵ | ○ | × | × |
| 計量(staff 面) | ○ | ○ | × | × | × |
| 目方入力・入力済み一覧 | △⁵ | △⁵ | ○ | × | × |
| 持ち込み計量・一覧 | ○ | ○ | × | × | × |
| 品目単位設定 | ○ | ○ | × | × | × |
| 自分の計量実績(mobile のみ) | — | — | ○⁶ | × | × |
| 車両 新規作成/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. マニフェスト作成・確定: driver は canCreateManifest で false(apps/api/src/middleware/auth.ts:143-150)・RLS mig88 carve-out も admin|normal のみ。撮影後 OCR(POST /manifests/ocr)は requireManifestOcrCaller が canRequestManifestOcr で判定し、discharger / collector staff に加えて collector driver も許可します(同ファイル:181-192)。作成・確定の権限は広げません。
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:712)。
8. 車両コスト等: admin/normal は自テナント全件(deleted_at is null)、driver は read/write とも不可。
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 の送信 UI は #587 で撤去済み。標準EDI 移行中のため、現行 UI から送信はできず、DB/API の資格情報・認可基盤だけを温存する。
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 でも防がれる(多層一致)¶
- 金額非表示の強制範囲:
external_partner_item_rates/external_partner_vehicle_leases(mig92 admin|normal のみ・driver 0 行)、external_partner_settlements(mig94 SELECT=admin|normal)は RLS で driver の読取を拒否する。weighings/weighing_itemsは staff + driver-own の二段 SELECT なので、driver 本人に可視な行まで一律に隠す契約ではない。画面が取得しない金額・notesの列非表示を RLS の列マスクと扱わない(RLS は行単位)。 - 越境(テナント跨ぎ): 全表
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:754-777)が、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:334-342・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 相当を明示化する必要がある。
検証根拠¶
- マイグレーション:
ls supabase/migrations | wc -lが正(本数・最新名は固定記述しない)。 - 3 値テナントと能力:
apps/web/src/contexts/AuthContext.tsxのTENANT_TYPES/canActAsDriver、apps/web/src/components/auth/RoleGuard.tsx。 - Web ルートとガード:
apps/web/src/App.tsxのルート定義、allowDriverCapable、allowedRole、allowedTenantType。 - ナビ:
apps/web/src/components/shell/nav-config.tsxのresolveNav/weighingTabs。 - Mobile:
apps/mobile/app/_layout.tsxと(driver)/history.tsx/weighings.tsx/run-weighing/[weighingId].tsx。 - API 認可:
apps/api/src/middleware/auth.tsのrequireCollectorAdmin/requireCollectorStaff/requireManifestCreatorとapps/api/src/routes/jwnet.ts。 - RLS/DB:
supabase/migrations/**の mig8, mig25, mig41, mig44, mig88〜89, mig92, mig94, mig98〜99, mig116〜202。dispatch_stopsの SELECT は最新のテナント全件ポリシーを採用する。 - 共有処理:
apps/web/src/pages/collector/collector-manifest-stages.ts、apps/web/src/pages/collector/partner-detail/PartnerWeightSection.tsx、apps/web/src/lib/dispatch/manifest-photo-gate.ts。
2026-09-07 の権限差分¶
JWNET接続テストは collector admin のみです。要求生成と結果判定の API も同じ制限で、normal の直呼びは拒否します。休眠中の実運用 Web-EDI API の staff 条件とは区別します。
メンバー一覧は事務が読めますが、作成・ロール変更・削除は admin のみです。入金記録の追加は事務、削除は admin、UPDATE は client 全員不可です。委託精算は事務が再計算・調整し、確定は admin、委託先は紐付いた確定分のみを列限定 RPC で読みます。
画面の非表示はDBの列マスクではありません。driver の金額・現場メモ非表示など、DBの行単位RLSと表示用projectionが担う範囲は分けて扱います。全画面の到達条件は操作手順書00のURL一覧を参照してください。