0007. can_drive 能力フラグ (ロールと運転能力の分離)¶
- ステータス: Accepted(実装済み)
- 関連: 0005 ロールモデル / 0006 工場スコープのアクセス制御 / 0008 配車コースモデル
コンテキストと課題¶
実務では「事務職 (normal) だが運転も担当する」「管理者 (admin) だが運転もする」社員がいる。こうした
社員を配車のドライバー候補・ドライバー画面の対象に含めたい。しかし role を driver に変えてしまうと、
事務・管理の権限を失ってしまう。ロールはそのままに「運転できる」能力だけを付与 したい。
決定¶
ロールとは独立した能力フラグ can_drive を導入する。「運転できる」判定は次の単一規則:
driverは can_drive の値に依存せず常に運転可。normal/adminはcan_drive = trueのときだけ運転可 (admin は付与しても管理権限を一切失わない)。
実装:
- DB:
profiles.can_drive boolean not null default falseを追加 (migration 51)。既定 false なので 誰も自動的に運転可にはならない (管理者が明示付与して初めて有効)。既存 driver 行は値をtrueに バックフィル (アプリ判定上は不要だが一覧の整合のため)。 - 判定の権威は JWT (
auth.users.raw_app_meta_data.can_drive)。RoleGuard・ナビ・配車候補が リフェッチ無しで判定できるよう、能力判定はapp_metadata.can_driveを読む。profiles.can_driveは メンバー一覧表示・整合用の 副次コピー で、app_metadataと同時にservice_role経由の API (POST /team/members/:id/can-drive) で更新される。 - 唯一の判定関数:
canActAsDriver(role, canDrive)(AuthContext.tsx)。配車のドライバー候補フィルタ・RoleGuard(/driverアクセス・allowDriverCapable)・ナビ (本日のルート表示) はすべてこれを使う。
export function canActAsDriver(role: UserRole | null, canDrive: boolean): boolean {
return role === 'driver' || ((role === 'normal' || role === 'admin') && canDrive === true);
}
影響 (Consequences)¶
良い点¶
- ロールを変えずに運転能力を付与でき、「事務 + 運転」「管理 + 運転」を権限を失わず表現できる。
- 「運転できる」判定が
canActAsDriver1 関数に集約され、配車候補・ルートガード・ナビが同じ基準で 一致する。全組合せはテーブルテストで網羅されている。 - 能力判定を JWT (
app_metadata.can_drive) から読むため、画面側はメンバー情報を再取得せず判定できる。
トレードオフ / 注意点¶
- 二重保存の同期責務: 権威の
app_metadata.can_driveと副次コピーのprofiles.can_driveを 常に同時更新する必要がある。これはservice_roleAPI (/team/members/:id/can-drive) に閉じている。 can_drive変更も他のクレーム同様 トークン再取得まで反映されない (ADR 0003)。- 「ロール」と「能力」という概念が増えるため、ドライバー対象者を判定するコードは必ず
canActAsDriverを経由する規律が要る (生のrole === 'driver'比較は不可)。
根拠 (典拠)¶
supabase/migrations/00000000000051_profiles_can_drive.sql:1-40— 設計 (運転可 = driver OR (normal AND can_drive))、can_drive boolean not null default false、判定の権威はapp_metadata.can_drive・本列は副次コピー (POST /team/members/:id/can-driveで同時更新)、driver 行のバックフィル。apps/web/src/contexts/AuthContext.tsx:46-60,87,174—canActAsDriver(driver OR ((normal|admin) AND canDrive))、canDriveコンテキスト、readAppMetaBoolean(user, 'can_drive')。apps/web/src/contexts/AuthContext.test.ts:68-88—canActAsDriverの全組合せテーブルテスト (admin+canDrive=true → true 等) と配車候補フィルタ。apps/web/src/components/auth/RoleGuard.tsx:17-43—allowDriverCapableとresolveRoleAccess(canActAsDriver を OR 評価)。