@aiquants/authz-react-router 0.8.1 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/admin/app.ts","../src/admin/server.ts","../src/admin/labels.ts","../src/admin/ui.tsx","../src/admin/views.tsx","../src/MyPermissionGroupStatus.tsx","../src/usePermission.ts","../src/MyPermissionIndicator.tsx","../src/resources.ts","../src/server.ts"],"sourcesContent":["/**\n * Public entry point of the React Router authorization adapter.\n * React Router 向け認可アダプタの公開エントリポイント。\n */\nexport * from \"./admin/app\"\nexport * from \"./admin/labels\"\nexport * from \"./admin/server\"\nexport * from \"./admin/ui\"\nexport * from \"./admin/views\"\nexport * from \"./MyPermissionGroupStatus\"\nexport * from \"./MyPermissionIndicator\"\nexport * from \"./resources\"\nexport * from \"./server\"\nexport * from \"./usePermission\"\n","/**\n * Server wiring that mounts the whole authorization admin UI on **one splat route** (`authz/*`).\n * A host needs a single `routes.ts` entry plus one minimal mount file to serve every tab — no\n * per-tab route directory or file.\n * 認可管理 UI を **スプラット 1 ルート (`authz/*`)** にマウントするためのサーバ結線。\n * `routes.ts` の 1 エントリ + 極小のマウントファイル 1 個で全タブを提供できる形\n * (タブごとのルートディレクトリ / ファイルが不要)。\n *\n * The returned `loader` / `action` are store-dependent (server side); the component is the\n * store-free `AuthzAdminAppView` (`views.tsx`) and the error boundary is `makeAuthzErrorBoundary`.\n * 返り値の `loader` / `action` は store 依存 (サーバ)。Component は store 非依存の\n * `AuthzAdminAppView` (`views.tsx`)、ErrorBoundary は `makeAuthzErrorBoundary` を使う形。\n */\nimport { data, redirect } from \"react-router\"\nimport { type CreateAuthzAdminServerOptions, createAuthzAdminServer } from \"./server\"\n\ntype RouteArgs = { request: Request; params: Record<string, string | undefined> }\n\nexport type CreateAuthzAdminAppOptions = CreateAuthzAdminServerOptions & {\n /** マウントのベースパス(タブリンク/未指定セグメントのリダイレクト先)。既定 `/authz`。 */\n basePath?: string\n}\n\n/**\n * Take the leading segment out of the splat param (`roles/...` → `roles`).\n * スプラット param (\"*\") から先頭セグメントを取り出す処理。\n *\n * @returns The first path segment (empty string when absent). 先頭のパスセグメント (無ければ空文字)。\n */\nfunction segmentOf(params: Record<string, string | undefined>): string {\n return String(params[\"*\"] ?? \"\").split(\"/\")[0]\n}\n\n/**\n * Build the loader/action pair that serves the whole authorization admin UI from one splat route.\n * 認可管理 UI 全体を 1 本のスプラットルートで提供する loader/action 対を生成するファクトリ。\n *\n * @returns The route `loader` / `action` plus the underlying server for direct use. ルートの loader/action と内部サーバ。\n */\nexport function createAuthzAdminApp(opts: CreateAuthzAdminAppOptions) {\n const server = createAuthzAdminServer(opts)\n const basePath = opts.basePath ?? \"/authz\"\n const SEGMENTS = [\"roles\", \"resources\", \"role_permissions\", \"user_roles\", \"group_roles\", \"graph\"] as const\n // データ取得(ガード無し)。ガードはスプラット loader で 1 回だけ行い、ガード済み文脈\n // (テナント + userId) を渡す。全セグメントが同じ引数形なので、テナントの渡し忘れは型で落ちる。\n const segmentData = { roles: server.roles.data, resources: server.resources.data, role_permissions: server.grants.data, user_roles: server.userRoles.data, group_roles: server.groupRoles.data, graph: server.graph.data }\n // Map で持つ。素のオブジェクトだと `POST /authz/toString` が prototype 継承のメソッドを引き当て、\n // ガードを 1 度も通らずに「ハンドラ」が実行されてしまう。\n const actions = new Map<string, (args: RouteArgs) => Promise<unknown>>([\n [\"roles\", server.roles.action],\n [\"resources\", server.resources.action],\n [\"role_permissions\", server.grants.action],\n [\"user_roles\", server.userRoles.action],\n [\"group_roles\", server.groupRoles.action],\n ])\n\n /**\n * Splat loader: validate the segment, guard once, then load that segment's data.\n * スプラット loader。セグメントを検証し、ガードを 1 回だけ通してから該当データを読む処理。\n *\n * @returns The segment's data merged with the layout shell's fields. セグメントのデータとレイアウト用フィールド。\n */\n async function loader(args: RouteArgs) {\n const segment = segmentOf(args.params)\n if (!(SEGMENTS as readonly string[]).includes(segment)) throw redirect(`${basePath}/roles`)\n // segment は上で SEGMENTS に含まれることを確認済み (prototype 継承キーは到達しない)\n // ガードは 1 回だけ(requireUser + requirePermission(read))。userId と自己権限を取り回して再ガード/再取得しない。\n const { userId, tenantId, myPermissions } = await server.layoutGuard(args.request)\n const segData = await segmentData[segment as (typeof SEGMENTS)[number]]({ tenantId, userId })\n // segData は labels と tenantId を含む(各 data 関数が付与)。myPermissions/adminResourceKey はレイアウト殻用。\n return { segment, basePath, myPermissions, adminResourceKey: server.resourceKey, ...segData }\n }\n\n /**\n * Splat action: dispatch to the segment's own handler, or 400 when the segment has none.\n * スプラット action。セグメント固有のハンドラへ振り分け、無ければ 400 を返す処理。\n *\n * @returns The handler's result, or a 400 payload. ハンドラの結果、または 400 のペイロード。\n */\n async function action(args: RouteArgs) {\n const segment = segmentOf(args.params)\n const act = actions.get(segment)\n if (!act) return data({ error: \"unknown action\" }, { status: 400 })\n return act(args)\n }\n\n // server: 個別タブの loader/action を露出(統合テスト等から直接呼ぶ用)。\n return { loader, action, server }\n}\n","/**\n * Server wiring for the authorization admin UI: a factory that binds the store port to the app's own\n * guard / session and emits each route's loader and action.\n * 認可管理 UI のサーバ結線 (loader/action 工場)。store ポート + アプリのガード/セッションを束ね、\n * 各ルートの loader/action を生成する。返り値の loader データには解決済みラベルを含める。\n * store(drizzle)を渡すのはこの工場の呼び出し側(アプリのサーバモジュール)に限る。\n *\n * **Tenant contract**: the screen can only ever act within the single tenant the guard resolved.\n * Every read is passed to the store together with that `tenantId`, and every write re-loads the\n * target row server-side to prove it really belongs to that tenant — having rendered a filtered\n * listing is not an authorization check.\n * **テナント契約**: 画面が扱えるのは、ガードが解決した 1 つのテナントだけ。読み取りは必ず\n * `tenantId` を伴ってストアへ渡し、書き込みは対象行が本当にそのテナントのものかをサーバ側で\n * 引き直して確認する(絞り込まれた一覧を描画したことは認可の証明にならないため)。\n */\nimport { AUTHZ_ACTIONS, type AuthzAction, type AuthzAdminStore, AuthzDeniedError, type AuthzGroupSummary, type AuthzResource, type AuthzRole, assertTenantId, isSameTenant } from \"@aiquants/authz-core\"\nimport { data, redirect } from \"react-router\"\nimport type { PermissionView } from \"../server\"\nimport { type AuthzAdminLabels, type PartialAuthzAdminLabels, resolveLabels } from \"./labels\"\nimport type { AuthzGrantsData, AuthzGraphData, AuthzGroupRolesData, AuthzLayoutData, AuthzResourcesData, AuthzRolesData, AuthzUserRolesData } from \"./views\"\n\ntype Req = { request: Request }\ntype PermissionQuery = { resourceKey: string; action: AuthzAction }\n\n/**\n * Everything a guarded admin request resolved to: the tenant it acts within and the acting user.\n * ガード済みの管理リクエストが解決した文脈 (操作対象テナントと実行ユーザー)。\n *\n * Both fields are required. The tenant is what every store call is scoped by, and the user id is what\n * the audit columns and the \"last administrator\" warning are computed from; neither has a meaningful\n * \"absent\" value on this route.\n * 双方必須 (テナントは全ストア呼び出しの絞り込み、ユーザー ID は監査列と自己管理者判定の基準)。\n */\nexport type AuthzAdminContext = { tenantId: string; userId: string | number }\n\nexport type CreateAuthzAdminServerOptions = {\n store: AuthzAdminStore\n /** 管理画面のガード資源キー(既定 `authz_admin`)。 */\n resourceKey?: string\n /** 未ログインをログインへ誘導する(例: throw redirect(...))。 */\n requireUser: (request: Request) => Promise<unknown>\n /**\n * Authorization guard supplied by the app (from `createAuthz`); allow → `{ userId, tenantId, permission }`.\n * 認可ガード (アプリの createAuthz 由来)。allow で { userId, tenantId, permission } を返すポート。\n * `userId` は nullable(未解決/万一の public 付与)であり、createAuthzAdminServer 内で deny する\n * (管理経路は監査・自己権限計算に実 id が必須のため非 null を保証)。\n * `tenantId` は **ガードが判定に使ったテナントそのもの**。ここで捨てて画面側が別経路で引き直すと、\n * 「A テナントの権限で B テナントを編集できる」という越境が構造的に開く。\n * `permission` は **同一判定から導かれた** 自己権限ビューであり、表示用に再取得しない\n * (再取得は「ガードは通るのにバッジは権限なし」という乖離の温床)。\n */\n requirePermission: (request: Request, query: PermissionQuery) => Promise<{ userId: string | number | null; tenantId: string; permission: PermissionView }>\n /** 文言の部分上書き(既定は日本語)。 */\n labels?: PartialAuthzAdminLabels\n}\n\n/** Whether a caught value is a `Response` to be rethrown as-is. 捕捉値がそのまま再送出すべき `Response` かの判定。 */\nconst isResponse = (e: unknown): e is Response => e instanceof Response\n\n/**\n * Convert an exception raised inside an action into `403` (authorization denials only) or `400` (validation etc.).\n * action 内の例外を 403(認可拒否のみ)/ 400(検証等)へ変換する処理。\n *\n * @param e The caught value. 捕捉した値。\n * @returns A `400` data payload for a non-authorization failure. 認可以外の失敗に対する 400 応答。\n * @throws {Response} The original `Response`, or a `403` for an {@link AuthzDeniedError}. 元の `Response`、または拒否時の 403。\n */\nfunction toActionError(e: unknown) {\n if (isResponse(e)) throw e\n if (e instanceof AuthzDeniedError) throw new Response(e.message, { status: 403 })\n return data({ error: e instanceof Error ? e.message : \"エラーが発生しました\" }, { status: 400 })\n}\n\n/**\n * Build the authorization admin UI's server side: one guard seam, per-tab data loaders, and the\n * loader / action pair of every route.\n * 認可管理 UI のサーバ側 (単一のガードシーム、タブ別データ取得、各ルートの loader/action) を構築する処理。\n *\n * Every route entry is guarded through the app-supplied `requirePermission`, whose result — the\n * tenant, the acting user, and the self-permission view — comes from **one** evaluation and is\n * carried onward as an {@link AuthzAdminContext}. Nothing here re-derives the tenant from the request\n * or re-loads the permission for display: a second lookup can disagree with the first, which is\n * exactly the \"guard allows but the badge says denied\" failure, and re-deriving the tenant is how a\n * screen ends up editing a tenant the guard never authorized.\n * 各ルートはアプリ由来の `requirePermission` を通し、その 1 回の判定から得たテナント・実行ユーザー・\n * 自己権限を {@link AuthzAdminContext} として持ち回る形 (テナントの引き直しも表示用の再取得も行わない)。\n *\n * Writes never trust the submitted ids: each one is re-loaded from a tenant-scoped listing and, where\n * the row carries its own tenant, compared against it. A missing row and a foreign row are answered\n * identically, so the screen cannot be used as a cross-tenant existence oracle.\n * 書き込みは送信された id を信用せず、テナント絞り込みの一覧から引き直して帰属を確認する形\n * (「存在しない」と「別テナント」は同じ応答にし、他テナントの存在確認手段にしない)。\n *\n * @param opts Store port, guard seams, admin resource key and label overrides.\n * store ポート・ガードシーム・管理リソースキー・文言上書き。\n * @returns The labels, the guarded resource key, `layoutGuard`, and every route's loader / action.\n * 文言・ガード対象リソースキー・`layoutGuard`・各ルートの loader/action。\n */\nexport function createAuthzAdminServer(opts: CreateAuthzAdminServerOptions) {\n const store = opts.store\n const RES = opts.resourceKey ?? \"authz_admin\"\n const labels: AuthzAdminLabels = resolveLabels(opts.labels)\n\n /**\n * Refuse an operation whose target does not belong to the request's tenant (fail-close, 403).\n * 要求テナントに属さない対象への操作を拒否する処理 (fail-close、403)。\n *\n * A missing row and a foreign row are deliberately indistinguishable: answering them differently\n * would turn this screen into a cross-tenant existence oracle for role / resource / grant ids.\n * 「存在しない」と「別テナント」を区別しない (区別自体が他テナントの存在確認手段になるため)。\n *\n * @param ctx The guarded tenant and acting user. ガード済みのテナントと実行ユーザー。\n * @param action The admin action being attempted. 試みられた管理操作。\n * @returns Never returns; always throws. 常に送出 (正常復帰しない)。\n * @throws {AuthzDeniedError} Always, with reason `tenant-mismatch`. 常に `tenant-mismatch` で拒否。\n */\n function denyCrossTenant(ctx: AuthzAdminContext, action: AuthzAction): never {\n throw new AuthzDeniedError({ appKey: \"\", resourceKey: RES, action, reason: \"tenant-mismatch\", tenantId: ctx.tenantId, userId: ctx.userId })\n }\n\n /**\n * Take the row with `id` out of a tenant-scoped listing, denying when it is not present.\n * テナントで絞り込んだ一覧から id の行を取り出し、無ければ拒否する処理。\n *\n * Used for the join rows (grants / assignments) whose DTOs carry no tenant of their own: for them\n * membership in the tenant-scoped listing is the ownership proof available.\n * テナント列を持たない結合行 (付与・割当) 用。絞り込み一覧への所属が唯一の所有証明となる形。\n *\n * @param rows Rows loaded for `ctx.tenantId`. `ctx.tenantId` で読み込んだ行群。\n * @param id The id the form asked to act on. フォームが指定した対象 id。\n * @param ctx The guarded tenant and acting user. ガード済みのテナントと実行ユーザー。\n * @param action The admin action being attempted. 試みられた管理操作。\n * @returns The matching row. 一致した行。\n * @throws {AuthzDeniedError} When no row matches. 一致する行が無い場合。\n */\n function pickOwned<T extends { id: number }>(rows: T[], id: number, ctx: AuthzAdminContext, action: AuthzAction): T {\n const row = rows.find((r) => r.id === id)\n if (!row) denyCrossTenant(ctx, action)\n return row\n }\n\n /**\n * Same as {@link pickOwned}, additionally comparing the row's **own** projected tenant.\n * {@link pickOwned} に加えて、行が自ら持つ (DB から射影された) テナントまで突き合わせる処理。\n *\n * The listing was already filtered, but a filtered listing is not an authorization check: if the\n * store ever forgot its predicate, every id in the database would look owned. Comparing the value\n * the row itself carries is what actually proves ownership.\n * 一覧の絞り込みは認可ではない (述語が抜ければ全 id が自テナントに見える)。行が名乗る値との突合が所有の証明。\n *\n * @param rows Rows loaded for `ctx.tenantId`. `ctx.tenantId` で読み込んだ行群。\n * @param id The id the form asked to act on. フォームが指定した対象 id。\n * @param ctx The guarded tenant and acting user. ガード済みのテナントと実行ユーザー。\n * @param action The admin action being attempted. 試みられた管理操作。\n * @returns The matching row, proven to belong to the tenant. 帰属を確認した行。\n * @throws {AuthzDeniedError} When absent or stamped with another tenant. 不在または別テナントの場合。\n */\n function pickOwnedTenanted<T extends { id: number; tenantId: string }>(rows: T[], id: number, ctx: AuthzAdminContext, action: AuthzAction): T {\n const row = pickOwned(rows, id, ctx, action)\n if (!isSameTenant(ctx.tenantId, row.tenantId)) denyCrossTenant(ctx, action)\n return row\n }\n\n /**\n * Reject a submitted payload that names a tenant other than the request's own.\n * 送信されたペイロードが要求テナント以外を名乗る場合に拒否する処理。\n *\n * The form field and the guard both name a tenant; neither side is preferred. They must agree, or\n * the write is refused — accepting the form's value would let any admin write into any tenant.\n * フォーム値とガード値のどちらも優先せず、一致しなければ書き込みを拒否する形 (フォーム採用は越境そのもの)。\n *\n * @param ctx The guarded tenant and acting user. ガード済みのテナントと実行ユーザー。\n * @param action The admin action being attempted. 試みられた管理操作。\n * @param payloadTenantId The tenant the submitted payload targets. ペイロードが対象とするテナント。\n * @returns Nothing; returns normally only when the two agree. 一致時のみ正常復帰。\n * @throws {AuthzDeniedError} When the two tenants differ. 不一致の場合。\n */\n function assertPayloadTenant(ctx: AuthzAdminContext, action: AuthzAction, payloadTenantId: string): void {\n if (!isSameTenant(ctx.tenantId, payloadTenantId)) denyCrossTenant(ctx, action)\n }\n\n /**\n * Load the tenant's roles and prove `roleId` is one of them.\n * 当該テナントのロールを読み、`roleId` がその中の 1 件であることを確認する処理。\n *\n * @param ctx The guarded tenant and acting user. ガード済みのテナントと実行ユーザー。\n * @param action The admin action being attempted. 試みられた管理操作。\n * @param roleId The role the form asked to act on. フォームが指定したロール id。\n * @returns The owned role row. 帰属を確認したロール行。\n * @throws {AuthzDeniedError} When the role is absent or foreign. 不在または別テナントの場合。\n */\n async function ownedRole(ctx: AuthzAdminContext, action: AuthzAction, roleId: number): Promise<AuthzRole> {\n return pickOwnedTenanted(await store.listRoles(ctx.tenantId), roleId, ctx, action)\n }\n\n /**\n * Load the tenant's resources and prove `resourceId` is one of them.\n * 当該テナントのリソースを読み、`resourceId` がその中の 1 件であることを確認する処理。\n *\n * @param ctx The guarded tenant and acting user. ガード済みのテナントと実行ユーザー。\n * @param action The admin action being attempted. 試みられた管理操作。\n * @param resourceId The resource the form asked to act on. フォームが指定したリソース id。\n * @returns The owned resource row. 帰属を確認したリソース行。\n * @throws {AuthzDeniedError} When the resource is absent or foreign. 不在または別テナントの場合。\n */\n async function ownedResource(ctx: AuthzAdminContext, action: AuthzAction, resourceId: number): Promise<AuthzResource> {\n return pickOwnedTenanted(await store.listResources(ctx.tenantId), resourceId, ctx, action)\n }\n\n /**\n * Load the tenant's groups and prove `groupId` is one of them.\n * 当該テナントのグループを読み、`groupId` がその中の 1 件であることを確認する処理。\n *\n * @param ctx The guarded tenant and acting user. ガード済みのテナントと実行ユーザー。\n * @param action The admin action being attempted. 試みられた管理操作。\n * @param groupId The group the form asked to act on. フォームが指定したグループ id。\n * @returns The owned group row. 帰属を確認したグループ行。\n * @throws {AuthzDeniedError} When the group is absent or foreign. 不在または別テナントの場合。\n */\n async function ownedGroup(ctx: AuthzAdminContext, action: AuthzAction, groupId: number): Promise<AuthzGroupSummary> {\n return pickOwnedTenanted(await store.listGroups(ctx.tenantId), groupId, ctx, action)\n }\n\n /**\n * Prove `userId` is an assignable user of the tenant (TDUserRole itself has no tenant column).\n * `userId` が当該テナントの割当候補ユーザーであることを確認する処理 (TDUserRole 自体はテナント列を持たない)。\n *\n * @param ctx The guarded tenant and acting user. ガード済みのテナントと実行ユーザー。\n * @param action The admin action being attempted. 試みられた管理操作。\n * @param userId The user the form asked to act on. フォームが指定したユーザー id。\n * @returns Nothing; returns normally only when the user belongs. 帰属時のみ正常復帰。\n * @throws {AuthzDeniedError} When the user is not in the tenant. 当該テナントに居ない場合。\n */\n async function assertOwnedUser(ctx: AuthzAdminContext, action: AuthzAction, userId: number): Promise<void> {\n pickOwned(await store.listUsers(ctx.tenantId), userId, ctx, action)\n }\n\n /**\n * Prove `grantId` is one of the tenant's grants (TDRolePermission has no tenant column of its own).\n * `grantId` が当該テナントの付与行であることを確認する処理 (TDRolePermission 自体はテナント列を持たない)。\n *\n * @param ctx The guarded tenant and acting user. ガード済みのテナントと実行ユーザー。\n * @param action The admin action being attempted. 試みられた管理操作。\n * @param grantId The grant the form asked to act on. フォームが指定した付与 id。\n * @returns Nothing; returns normally only when the grant belongs. 帰属時のみ正常復帰。\n * @throws {AuthzDeniedError} When the grant is absent or foreign. 不在または別テナントの場合。\n */\n async function assertOwnedGrant(ctx: AuthzAdminContext, action: AuthzAction, grantId: number): Promise<void> {\n pickOwned(await store.listGrants(ctx.tenantId), grantId, ctx, action)\n }\n\n /**\n * Boundary guard: resolve the request into a complete {@link AuthzAdminContext} exactly once.\n * 境界ガード。リクエストを完全な {@link AuthzAdminContext} へ 1 度だけ解決する処理。\n *\n * 未解決ユーザー(userId==null)と空テナントをここで 1 回だけ弾く。これ以降 `ctx.userId` は非 null、\n * `ctx.tenantId` は非空が保証される。管理リソースは public を付与しない前提だが、防御的に拒否する\n * (監査・自己権限計算に実 id が必須のため)。\n *\n * @param request The incoming request. 受信したリクエスト。\n * @param query The admin resource and action to guard. ガードする管理リソースとアクション。\n * @returns The guarded context plus the self-permission view from that same evaluation. ガード済み文脈と同一判定由来の自己権限。\n * @throws {AuthzTenantError} When the guard resolved a blank tenant. テナントが空の場合。\n * @throws {AuthzDeniedError} When the acting user is unresolved. 実行ユーザーが未解決の場合。\n */\n const requirePermission = async (request: Request, query: PermissionQuery): Promise<AuthzAdminContext & { permission: PermissionView }> => {\n const { userId, tenantId, permission } = await opts.requirePermission(request, query)\n // テナント未解決は「全テナント」ではなく契約違反。ユーザー判定より前に落とす\n assertTenantId(tenantId, \"createAuthzAdminServer(requirePermission.tenantId)\")\n if (userId == null) {\n throw new AuthzDeniedError({ appKey: \"\", resourceKey: query.resourceKey, action: query.action, reason: \"unresolved-user\", tenantId, userId: null })\n }\n return { userId, tenantId, permission }\n }\n\n /**\n * Layout guard: `requireUser` (302) → `requirePermission(read)` (403), run exactly once, returning the\n * guarded context together with the self-permission map derived from that **same** evaluation.\n * レイアウトガード。1 回だけ実行し、ガード済み文脈と同じ判定から導かれた自己権限マップを返す処理\n * (表示用の追加問い合わせは行わない)。\n *\n * @param request The incoming request. 受信したリクエスト。\n * @returns The guarded context and the admin resource's permission view. ガード済み文脈と管理リソースの権限ビュー。\n */\n async function layoutGuard(request: Request): Promise<AuthzAdminContext & { myPermissions: Record<string, PermissionView | undefined> }> {\n await opts.requireUser(request)\n try {\n const { userId, tenantId, permission } = await requirePermission(request, { resourceKey: RES, action: \"read\" })\n return { userId, tenantId, myPermissions: { [RES]: permission } }\n } catch (e) {\n if (e instanceof AuthzDeniedError) throw new Response(`Forbidden: ${RES}`, { status: 403 })\n throw e\n }\n }\n\n // ---- データ取得(ガード無し)。ガード済みの文脈(テナント + userId)を受け取る。 ----\n // 全タブが同じ引数形になっているのは、スプラット結線 (app.ts) がセグメントで引いた関数を\n // そのまま呼べるようにするため。ここが関数ごとに違う形だと、渡し忘れが型で見えなくなる。\n /**\n * Roles tab data: the tenant's roles plus the actor's own admin-backing role ids.\n * ロールタブのデータ (当該テナントのロールと、実行者の管理権限を支えるロール ID)。\n *\n * @param ctx The guarded tenant and acting user. ガード済みのテナントと実行ユーザー。\n * @returns The roles tab payload. ロールタブの表示データ。\n */\n const rolesData = async (ctx: AuthzAdminContext): Promise<AuthzRolesData> => {\n const [roles, self] = await Promise.all([store.listRoles(ctx.tenantId), store.getSelfAdminContext(ctx.tenantId, Number(ctx.userId))])\n return { roles, selfAdminRoleIds: self.selfAdminRoleIds, tenantId: ctx.tenantId, labels }\n }\n /**\n * Resources tab data: the tenant's resources.\n * リソースタブのデータ (当該テナントのリソース)。\n *\n * @param ctx The guarded tenant and acting user. ガード済みのテナントと実行ユーザー。\n * @returns The resources tab payload. リソースタブの表示データ。\n */\n const resourcesData = async (ctx: AuthzAdminContext): Promise<AuthzResourcesData> => ({ resources: await store.listResources(ctx.tenantId), tenantId: ctx.tenantId, labels })\n /**\n * Grants tab data: the tenant's grants, plus its roles and resources as form choices.\n * 権限付与タブのデータ (当該テナントの付与と、フォームの選択肢となるロール・リソース)。\n *\n * @param ctx The guarded tenant and acting user. ガード済みのテナントと実行ユーザー。\n * @returns The grants tab payload. 権限付与タブの表示データ。\n */\n const grantsData = async (ctx: AuthzAdminContext): Promise<AuthzGrantsData> => {\n const [grants, roles, resources] = await Promise.all([store.listGrants(ctx.tenantId), store.listRoles(ctx.tenantId), store.listResources(ctx.tenantId)])\n return { grants, roles, resources, tenantId: ctx.tenantId, labels }\n }\n /**\n * User-roles tab data: the tenant's assignments, its roles and assignable users, and the actor's\n * own admin-backing role ids.\n * ユーザー割当タブのデータ (当該テナントの割当・ロール・割当候補ユーザーと、実行者の管理権限を支えるロール ID)。\n *\n * @param ctx The guarded tenant and acting user. ガード済みのテナントと実行ユーザー。\n * @returns The user-roles tab payload. ユーザー割当タブの表示データ。\n */\n const userRolesData = async (ctx: AuthzAdminContext): Promise<AuthzUserRolesData> => {\n const [assignments, roles, users, self] = await Promise.all([store.listUserRoles(ctx.tenantId), store.listRoles(ctx.tenantId), store.listUsers(ctx.tenantId), store.getSelfAdminContext(ctx.tenantId, Number(ctx.userId))])\n return { assignments, roles, users, actorUserId: self.actorUserId, selfAdminRoleIds: self.selfAdminRoleIds, tenantId: ctx.tenantId, labels }\n }\n /**\n * Group-roles tab data: the tenant's group assignments, plus its roles and groups as form choices.\n * グループ割当タブのデータ (当該テナントのグループ割当と、フォームの選択肢となるロール・グループ)。\n *\n * @param ctx The guarded tenant and acting user. ガード済みのテナントと実行ユーザー。\n * @returns The group-roles tab payload. グループ割当タブの表示データ。\n */\n const groupRolesData = async (ctx: AuthzAdminContext): Promise<AuthzGroupRolesData> => {\n const [groupRoles, roles, groups] = await Promise.all([store.listGroupRoles(ctx.tenantId), store.listRoles(ctx.tenantId), store.listGroups(ctx.tenantId)])\n return { groupRoles, roles, groups, tenantId: ctx.tenantId, labels }\n }\n /**\n * Graph tab data: every listing of the tenant, loaded in parallel for the relationship view.\n * グラフタブのデータ (関係図のために当該テナントの全一覧を並列取得)。\n *\n * @param ctx The guarded tenant and acting user. ガード済みのテナントと実行ユーザー。\n * @returns The graph tab payload. グラフタブの表示データ。\n */\n const graphData = async (ctx: AuthzAdminContext): Promise<AuthzGraphData> => {\n const [users, roles, resources, grants, assignments, groupRoles, groups] = await Promise.all([\n store.listUsers(ctx.tenantId),\n store.listRoles(ctx.tenantId),\n store.listResources(ctx.tenantId),\n store.listGrants(ctx.tenantId),\n store.listUserRoles(ctx.tenantId),\n store.listGroupRoles(ctx.tenantId),\n store.listGroups(ctx.tenantId),\n ])\n return { users, roles, resources, grants, assignments, groupRoles, groups, tenantId: ctx.tenantId, labels }\n }\n\n return {\n /** 露出: スプラットマウント(app.ts)がガードを 1 回だけ行うために使う。 */\n labels,\n resourceKey: RES,\n layoutGuard,\n\n /** /authz レイアウト: requireUser(302) → requirePermission(read, 403)。自己権限はガードの戻り値から得る。 */\n layout: {\n /**\n * Layout loader: guard once, then hand the shell the tenant and the self-permission map.\n * レイアウトの loader。ガードを 1 回だけ行い、テナントと自己権限マップを外枠へ渡す処理。\n *\n * @param args The route arguments (request only). ルート引数 (request のみ)。\n * @returns The layout payload. レイアウトの表示データ。\n * @throws {Response} `302` when unauthenticated, `403` when the read is denied. 未認証は 302、拒否は 403。\n */\n async loader({ request }: Req): Promise<AuthzLayoutData> {\n const { tenantId, myPermissions } = await layoutGuard(request)\n return { myPermissions, tenantId, labels, adminResourceKey: RES }\n },\n },\n\n /** /authz index → roles へ。 */\n index: {\n /**\n * Index loader: the admin root has no view of its own, so it redirects to the roles tab.\n * インデックスの loader。管理ルートは固有の表示を持たないためロールタブへ転送する処理。\n *\n * @returns Never returns; always throws the redirect. 常に転送を送出 (正常復帰しない)。\n * @throws {Response} The `/authz/roles` redirect. `/authz/roles` への転送。\n */\n loader() {\n throw redirect(\"/authz/roles\")\n },\n },\n\n roles: {\n data: rolesData,\n /**\n * Roles tab loader: guard for `read`, then load the tab within the guarded tenant.\n * ロールタブの loader。`read` をガードし、ガード済みテナントの範囲で読み込む処理。\n *\n * @param args The route arguments (request only). ルート引数 (request のみ)。\n * @returns The roles tab payload. ロールタブの表示データ。\n * @throws {AuthzDeniedError} When the read is denied. 読み取りが拒否された場合。\n */\n async loader({ request }: Req): Promise<AuthzRolesData> {\n const ctx = await requirePermission(request, { resourceKey: RES, action: \"read\" })\n return rolesData(ctx)\n },\n /**\n * Roles tab action: create / save / toggle / delete, each guarded for its own verb.\n * ロールタブの action。作成・保存・有効切替・削除を、それぞれの動詞でガードして実行する処理。\n *\n * `create` and `save` additionally reconcile the submitted `tenantId` with the guarded one\n * before the store is called, and every id is re-loaded through `ownedRole`.\n * `create`/`save` は送信された `tenantId` をガード側と突合し、id は必ず `ownedRole` で引き直す形。\n *\n * @param args The route arguments (request only). ルート引数 (request のみ)。\n * @returns `{ ok: true }`, or a `400` payload on a validation failure. 成功、または検証失敗時の 400 応答。\n * @throws {Response} `403` when the guard denies the verb. 動詞のガードが拒否した場合。\n */\n async action({ request }: Req) {\n const form = await request.formData()\n const intent = String(form.get(\"_action\"))\n try {\n switch (intent) {\n case \"create\": {\n const ctx = await requirePermission(request, { resourceKey: RES, action: \"create\" })\n const tenantId = String(form.get(\"tenantId\") ?? \"\").trim()\n const roleKey = String(form.get(\"roleKey\") ?? \"\").trim()\n const name = String(form.get(\"name\") ?? \"\").trim()\n if (!(tenantId && roleKey && name)) return data({ error: \"tenant_id と role_key と name は必須です\" }, { status: 400 })\n assertPayloadTenant(ctx, \"create\", tenantId)\n await store.createRole(ctx.tenantId, { tenantId, roleKey, name, description: String(form.get(\"description\") ?? \"\").trim() || null }, String(ctx.userId))\n return { ok: true }\n }\n case \"save\": {\n const ctx = await requirePermission(request, { resourceKey: RES, action: \"update\" })\n const tenantId = String(form.get(\"tenantId\") ?? \"\").trim()\n const roleKey = String(form.get(\"roleKey\") ?? \"\").trim()\n const name = String(form.get(\"name\") ?? \"\").trim()\n if (!(tenantId && roleKey && name)) return data({ error: \"tenantId と roleKey と name は必須です\" }, { status: 400 })\n assertPayloadTenant(ctx, \"update\", tenantId)\n const role = await ownedRole(ctx, \"update\", Number(form.get(\"id\")))\n await store.updateRole(ctx.tenantId, role.id, { tenantId, roleKey, name, description: String(form.get(\"description\") ?? \"\").trim() || null }, String(ctx.userId))\n return { ok: true }\n }\n case \"toggle\": {\n const ctx = await requirePermission(request, { resourceKey: RES, action: \"update\" })\n const role = await ownedRole(ctx, \"update\", Number(form.get(\"id\")))\n await store.setRoleActive(ctx.tenantId, role.id, form.get(\"isActive\") === \"true\", String(ctx.userId))\n return { ok: true }\n }\n case \"delete\": {\n const ctx = await requirePermission(request, { resourceKey: RES, action: \"delete\" })\n const role = await ownedRole(ctx, \"delete\", Number(form.get(\"id\")))\n await store.deleteRole(ctx.tenantId, role.id)\n return { ok: true }\n }\n default:\n return data({ error: \"unknown action\" }, { status: 400 })\n }\n } catch (e) {\n return toActionError(e)\n }\n },\n },\n\n resources: {\n data: resourcesData,\n /**\n * Resources tab loader: guard for `read`, then load the tab within the guarded tenant.\n * リソースタブの loader。`read` をガードし、ガード済みテナントの範囲で読み込む処理。\n *\n * @param args The route arguments (request only). ルート引数 (request のみ)。\n * @returns The resources tab payload. リソースタブの表示データ。\n * @throws {AuthzDeniedError} When the read is denied. 読み取りが拒否された場合。\n */\n async loader({ request }: Req): Promise<AuthzResourcesData> {\n const ctx = await requirePermission(request, { resourceKey: RES, action: \"read\" })\n return resourcesData(ctx)\n },\n /**\n * Resources tab action: create / save / delete, each guarded for its own verb.\n * リソースタブの action。作成・保存・削除を、それぞれの動詞でガードして実行する処理。\n *\n * `create` reconciles the submitted `tenantId` with the guarded one; `save` and `delete`\n * re-load the id through `ownedResource` instead of trusting the form.\n * `create` は送信 `tenantId` を突合し、`save`/`delete` は `ownedResource` で id を引き直す形。\n *\n * @param args The route arguments (request only). ルート引数 (request のみ)。\n * @returns `{ ok: true }`, or a `400` payload on a validation failure. 成功、または検証失敗時の 400 応答。\n * @throws {Response} `403` when the guard denies the verb. 動詞のガードが拒否した場合。\n */\n async action({ request }: Req) {\n const form = await request.formData()\n const intent = String(form.get(\"_action\"))\n try {\n switch (intent) {\n case \"create\": {\n const ctx = await requirePermission(request, { resourceKey: RES, action: \"create\" })\n const tenantId = String(form.get(\"tenantId\") ?? \"\").trim()\n const appKey = String(form.get(\"appKey\") ?? \"\").trim()\n const resourceKey = String(form.get(\"resourceKey\") ?? \"\").trim()\n const name = String(form.get(\"name\") ?? \"\").trim()\n if (!(tenantId && appKey && resourceKey && name)) return data({ error: \"tenant_id / app_key / resource_key / name は必須です\" }, { status: 400 })\n assertPayloadTenant(ctx, \"create\", tenantId)\n await store.createResource(ctx.tenantId, { tenantId, appKey, resourceKey, name, description: String(form.get(\"description\") ?? \"\").trim() || null }, String(ctx.userId))\n return { ok: true }\n }\n case \"save\": {\n const ctx = await requirePermission(request, { resourceKey: RES, action: \"update\" })\n const name = String(form.get(\"name\") ?? \"\").trim()\n if (!name) return data({ error: \"name は必須です\" }, { status: 400 })\n const resource = await ownedResource(ctx, \"update\", Number(form.get(\"id\")))\n await store.updateResource(ctx.tenantId, resource.id, { name, description: String(form.get(\"description\") ?? \"\").trim() || null }, String(ctx.userId))\n return { ok: true }\n }\n case \"delete\": {\n const ctx = await requirePermission(request, { resourceKey: RES, action: \"delete\" })\n const resource = await ownedResource(ctx, \"delete\", Number(form.get(\"id\")))\n await store.deleteResource(ctx.tenantId, resource.id)\n return { ok: true }\n }\n default:\n return data({ error: \"unknown action\" }, { status: 400 })\n }\n } catch (e) {\n return toActionError(e)\n }\n },\n },\n\n grants: {\n data: grantsData,\n /**\n * Grants tab loader: guard for `read`, then load the tab within the guarded tenant.\n * 権限付与タブの loader。`read` をガードし、ガード済みテナントの範囲で読み込む処理。\n *\n * @param args The route arguments (request only). ルート引数 (request のみ)。\n * @returns The grants tab payload. 権限付与タブの表示データ。\n * @throws {AuthzDeniedError} When the read is denied. 読み取りが拒否された場合。\n */\n async loader({ request }: Req): Promise<AuthzGrantsData> {\n const ctx = await requirePermission(request, { resourceKey: RES, action: \"read\" })\n return grantsData(ctx)\n },\n /**\n * Grants tab action: create / save / delete, each guarded for its own verb.\n * 権限付与タブの action。作成・保存・削除を、それぞれの動詞でガードして実行する処理。\n *\n * A grant links two rows, so `create` proves **both** the role and the resource belong to\n * the guarded tenant; `save` and `delete` prove the grant itself through `assertOwnedGrant`.\n * 付与は 2 行を結ぶため `create` はロール・リソース双方の帰属を証明し、`save`/`delete` は\n * `assertOwnedGrant` で付与自体の帰属を証明する形。\n *\n * @param args The route arguments (request only). ルート引数 (request のみ)。\n * @returns `{ ok: true }`, or a `400` payload on a validation failure. 成功、または検証失敗時の 400 応答。\n * @throws {Response} `403` when the guard denies the verb. 動詞のガードが拒否した場合。\n */\n async action({ request }: Req) {\n const form = await request.formData()\n const intent = String(form.get(\"_action\"))\n try {\n switch (intent) {\n case \"create\": {\n const ctx = await requirePermission(request, { resourceKey: RES, action: \"create\" })\n const tenantId = String(form.get(\"tenantId\") ?? \"\").trim()\n const roleId = Number(form.get(\"roleId\"))\n const resourceId = Number(form.get(\"resourceId\"))\n if (!(tenantId && roleId && resourceId)) return data({ error: \"tenant_id とロールとリソースを選択してください\" }, { status: 400 })\n const rawAction = String(form.get(\"action\") ?? \"\")\n if (!(AUTHZ_ACTIONS as readonly string[]).includes(rawAction)) return data({ error: \"無効な action です\" }, { status: 400 })\n const action = rawAction as AuthzAction\n assertPayloadTenant(ctx, \"create\", tenantId)\n // 付与は 2 つの行を結び付ける。どちらか片方でも他テナントなら越境の橋になる\n const role = await ownedRole(ctx, \"create\", roleId)\n const resource = await ownedResource(ctx, \"create\", resourceId)\n await store.createGrant(ctx.tenantId, { tenantId, roleId: role.id, resourceId: resource.id, action, rowScope: String(form.get(\"rowScope\") ?? \"\"), columnScope: String(form.get(\"columnScope\") ?? \"\") }, String(ctx.userId))\n return { ok: true }\n }\n case \"save\": {\n const ctx = await requirePermission(request, { resourceKey: RES, action: \"update\" })\n const rawAction = String(form.get(\"action\") ?? \"\")\n if (!(AUTHZ_ACTIONS as readonly string[]).includes(rawAction)) return data({ error: \"無効な action です\" }, { status: 400 })\n const action = rawAction as AuthzAction\n const grantId = Number(form.get(\"id\"))\n await assertOwnedGrant(ctx, \"update\", grantId)\n await store.updateGrant(ctx.tenantId, grantId, { action, rowScope: String(form.get(\"rowScope\") ?? \"\"), columnScope: String(form.get(\"columnScope\") ?? \"\") }, String(ctx.userId))\n return { ok: true }\n }\n case \"delete\": {\n const ctx = await requirePermission(request, { resourceKey: RES, action: \"delete\" })\n const grantId = Number(form.get(\"id\"))\n await assertOwnedGrant(ctx, \"delete\", grantId)\n await store.deleteGrant(ctx.tenantId, grantId)\n return { ok: true }\n }\n default:\n return data({ error: \"unknown action\" }, { status: 400 })\n }\n } catch (e) {\n return toActionError(e)\n }\n },\n },\n\n userRoles: {\n data: userRolesData,\n /**\n * User-roles tab loader: guard for `read`, then load the tab within the guarded tenant.\n * ユーザー割当タブの loader。`read` をガードし、ガード済みテナントの範囲で読み込む処理。\n *\n * @param args The route arguments (request only). ルート引数 (request のみ)。\n * @returns The user-roles tab payload. ユーザー割当タブの表示データ。\n * @throws {AuthzDeniedError} When the read is denied. 読み取りが拒否された場合。\n */\n async loader({ request }: Req): Promise<AuthzUserRolesData> {\n const ctx = await requirePermission(request, { resourceKey: RES, action: \"read\" })\n return userRolesData(ctx)\n },\n /**\n * User-roles tab action: assign / remove, each guarded for its own verb.\n * ユーザー割当タブの action。割当・解除を、それぞれの動詞でガードして実行する処理。\n *\n * The assignment row carries no tenant of its own, so both ends are proven — the user\n * through `assertOwnedUser` and the role through `ownedRole` — before the store is called.\n * 割当行はテナントを持たないため、ユーザー側 (`assertOwnedUser`) とロール側 (`ownedRole`) の\n * 両端の帰属を証明してからストアを呼ぶ形。\n *\n * @param args The route arguments (request only). ルート引数 (request のみ)。\n * @returns `{ ok: true }`, or a `400` payload on a validation failure. 成功、または検証失敗時の 400 応答。\n * @throws {Response} `403` when the guard denies the verb. 動詞のガードが拒否した場合。\n */\n async action({ request }: Req) {\n const form = await request.formData()\n const intent = String(form.get(\"_action\"))\n try {\n switch (intent) {\n case \"assign\": {\n const ctx = await requirePermission(request, { resourceKey: RES, action: \"create\" })\n const userId = Number(form.get(\"userId\"))\n const roleId = Number(form.get(\"roleId\"))\n if (!(userId && roleId)) return data({ error: \"ユーザーとロールを選択してください\" }, { status: 400 })\n // 割当行 (TDUserRole) はテナント列を持たない。両端の帰属を確かめて初めてテナント内に閉じる\n await assertOwnedUser(ctx, \"create\", userId)\n const role = await ownedRole(ctx, \"create\", roleId)\n await store.assignUserRole(ctx.tenantId, userId, role.id, String(ctx.userId))\n return { ok: true }\n }\n case \"remove\": {\n const ctx = await requirePermission(request, { resourceKey: RES, action: \"delete\" })\n const userId = Number(form.get(\"userId\"))\n await assertOwnedUser(ctx, \"delete\", userId)\n const role = await ownedRole(ctx, \"delete\", Number(form.get(\"roleId\")))\n await store.removeUserRole(ctx.tenantId, userId, role.id)\n return { ok: true }\n }\n default:\n return data({ error: \"unknown action\" }, { status: 400 })\n }\n } catch (e) {\n return toActionError(e)\n }\n },\n },\n\n groupRoles: {\n data: groupRolesData,\n /**\n * Group-roles tab loader: guard for `read`, then load the tab within the guarded tenant.\n * グループ割当タブの loader。`read` をガードし、ガード済みテナントの範囲で読み込む処理。\n *\n * @param args The route arguments (request only). ルート引数 (request のみ)。\n * @returns The group-roles tab payload. グループ割当タブの表示データ。\n * @throws {AuthzDeniedError} When the read is denied. 読み取りが拒否された場合。\n */\n async loader({ request }: Req): Promise<AuthzGroupRolesData> {\n const ctx = await requirePermission(request, { resourceKey: RES, action: \"read\" })\n return groupRolesData(ctx)\n },\n /**\n * Group-roles tab action: assign / remove, each guarded for its own verb.\n * グループ割当タブの action。割当・解除を、それぞれの動詞でガードして実行する処理。\n *\n * As with user roles the link row carries no tenant, so both the group (`ownedGroup`) and\n * the role (`ownedRole`) are proven to belong to the guarded tenant first.\n * ユーザー割当と同様に割当行はテナントを持たないため、グループ側・ロール側双方の帰属を先に証明する形。\n *\n * @param args The route arguments (request only). ルート引数 (request のみ)。\n * @returns `{ ok: true }`, or a `400` payload on a validation failure. 成功、または検証失敗時の 400 応答。\n * @throws {Response} `403` when the guard denies the verb. 動詞のガードが拒否した場合。\n */\n async action({ request }: Req) {\n const form = await request.formData()\n const intent = String(form.get(\"_action\"))\n try {\n switch (intent) {\n case \"assign\": {\n const ctx = await requirePermission(request, { resourceKey: RES, action: \"create\" })\n const groupId = Number(form.get(\"groupId\"))\n const roleId = Number(form.get(\"roleId\"))\n if (!(groupId && roleId)) return data({ error: \"グループとロールを選択してください\" }, { status: 400 })\n const group = await ownedGroup(ctx, \"create\", groupId)\n const role = await ownedRole(ctx, \"create\", roleId)\n await store.assignGroupRole(ctx.tenantId, group.id, role.id, String(ctx.userId))\n return { ok: true }\n }\n case \"remove\": {\n const ctx = await requirePermission(request, { resourceKey: RES, action: \"delete\" })\n const group = await ownedGroup(ctx, \"delete\", Number(form.get(\"groupId\")))\n const role = await ownedRole(ctx, \"delete\", Number(form.get(\"roleId\")))\n await store.removeGroupRole(ctx.tenantId, group.id, role.id)\n return { ok: true }\n }\n default:\n return data({ error: \"unknown action\" }, { status: 400 })\n }\n } catch (e) {\n return toActionError(e)\n }\n },\n },\n\n graph: {\n data: graphData,\n /**\n * Graph tab loader: guard for `read`, then load every listing of the guarded tenant.\n * グラフタブの loader。`read` をガードし、ガード済みテナントの全一覧を読み込む処理。\n *\n * @param args The route arguments (request only). ルート引数 (request のみ)。\n * @returns The graph tab payload. グラフタブの表示データ。\n * @throws {AuthzDeniedError} When the read is denied. 読み取りが拒否された場合。\n */\n async loader({ request }: Req): Promise<AuthzGraphData> {\n const ctx = await requirePermission(request, { resourceKey: RES, action: \"read\" })\n return graphData(ctx)\n },\n },\n }\n}\n","/**\n * The label contract of the authorization admin UI: its string shape, the English defaults, and the\n * per-section merge that resolves a host's partial overrides against them.\n * 認可管理 UI のラベル契約 (文言の型・英語既定値・部分上書きを解決するセクション単位マージ)。\n *\n * A host overrides whole sections through `createAuthzAdminRoutes(..., { labels })`; sections it\n * leaves out keep the defaults, so a partial translation never blanks a screen.\n * アプリ側は `createAuthzAdminRoutes(..., { labels })` でセクション単位に上書きする形\n * (未指定セクションは既定値のまま残るため、部分翻訳でも画面が空にならない)。\n */\n\n/**\n * Authz admin UI strings (i18n). Defaults are English; override per-section via `createAuthzAdminRoutes(..., { labels })`.\n * 認可管理 UI の文言 (i18n)。既定は英語で、セクション単位に上書きできる形。\n */\nexport type AuthzAdminLabels = {\n title: string\n myPermission: string\n tabs: { roles: string; resources: string; role_permissions: string; user_roles: string; group_roles: string; graph: string }\n /**\n * Strings shared by every tab, including the tenant column / field every tenant-scoped table shows.\n * 全タブ共通の文言 (テナント列・テナント入力欄を含む)。\n */\n common: { add: string; save: string; delete: string; remove: string; assign: string; grant: string; select: string; active: string; inactive: string; optional: string; tenant: string; tenantHint: string; unknownTenant: string }\n roles: { heading: string; note: string; roleKey: string; name: string; description: string; activeHeader: string; empty: string; newHeading: string; hintRoleKey: string }\n resources: { heading: string; note: string; appKey: string; resourceKey: string; name: string; description: string; empty: string; newHeading: string; hintAppKey: string; hintResourceKey: string; hintDailyReport: string }\n grants: {\n heading: string\n note: string\n role: string\n resource: string\n action: string\n rowScope: string\n columnScope: string\n empty: string\n newHeading: string\n rowScopeHint: string\n columnScopeHint: string\n rowScopePlaceholder: string\n columnScopePlaceholder: string\n nullRows: string\n nullCols: string\n selectPlaceholder: string\n }\n userRoles: { heading: string; note: string; user: string; role: string; empty: string; newHeading: string; selectPlaceholder: string }\n groupRoles: { heading: string; note: string; group: string; role: string; empty: string; newHeading: string; selectPlaceholder: string }\n error: { forbiddenTitle: string; forbiddenBody: string; genericTitlePrefix: string; genericTitle: string; genericBody: string }\n warn: { selfRoleDisable: string; selfRoleDelete: string; selfAssignmentRemove: string }\n}\n\nexport const defaultAuthzAdminLabels: AuthzAdminLabels = {\n title: \"Authorization (RBAC)\",\n myPermission: \"Your permissions\",\n tabs: { roles: \"Roles\", resources: \"Resources\", role_permissions: \"Permissions\", user_roles: \"User assignments\", group_roles: \"Group assignments\", graph: \"Graph\" },\n common: {\n add: \"Add\",\n save: \"Save\",\n delete: \"Delete\",\n remove: \"Remove\",\n assign: \"Assign\",\n grant: \"Grant\",\n select: \"Select…\",\n active: \"○ Enabled\",\n inactive: \"× Disabled\",\n optional: \"optional\",\n tenant: \"tenant\",\n tenantHint: \"must match the tenant you are signed in to\",\n unknownTenant: \"—\",\n },\n roles: {\n heading: \"Roles (TMRole)\",\n note: \"Edit name / description inline and click Save (role_key is an immutable identifier).\",\n roleKey: \"role_key\",\n name: \"name\",\n description: \"description\",\n activeHeader: \"Active\",\n empty: \"No roles\",\n newHeading: \"New role\",\n hintRoleKey: \"e.g. admin\",\n },\n resources: {\n heading: \"Resources (TMResource)\",\n note: \"Edit name / description inline and click Save (app_key / resource_key are immutable identifiers).\",\n appKey: \"app_key\",\n resourceKey: \"resource_key\",\n name: \"name\",\n description: \"description\",\n empty: \"No resources\",\n newHeading: \"New resource\",\n hintAppKey: \"e.g. quants\",\n hintResourceKey: \"e.g. daily_report\",\n hintDailyReport: \"optional\",\n },\n grants: {\n heading: \"Permissions (TDRolePermission)\",\n note: \"Empty row_scope / column_scope = all rows / all columns (NULL). JSON must satisfy the row_scope / column_scope contract.\",\n role: \"role\",\n resource: \"resource\",\n action: \"action\",\n rowScope: \"row_scope\",\n columnScope: \"column_scope\",\n empty: \"No permissions\",\n newHeading: \"Grant a permission\",\n rowScopeHint: \"JSON / empty = all rows\",\n columnScopeHint: \"JSON / empty = all columns\",\n rowScopePlaceholder: \"empty = all rows (NULL)\",\n columnScopePlaceholder: \"empty = all columns (NULL)\",\n nullRows: \"NULL (all rows)\",\n nullCols: \"NULL (all columns)\",\n selectPlaceholder: \"Select…\",\n },\n userRoles: { heading: \"User assignments (TDUserRole)\", note: \"Assign active roles to specific users.\", user: \"user\", role: \"role\", empty: \"No assignments\", newHeading: \"Assign a role\", selectPlaceholder: \"Select…\" },\n groupRoles: { heading: \"Group assignments (TDGroupRole)\", note: \"Assign active roles to user groups.\", group: \"group\", role: \"role\", empty: \"No group assignments\", newHeading: \"Assign a role to group\", selectPlaceholder: \"Select…\" },\n error: {\n forbiddenTitle: \"Access denied\",\n forbiddenBody: \"You don't have permission to view this screen. Ask an administrator to grant access.\",\n genericTitlePrefix: \"Error\",\n genericTitle: \"An error occurred\",\n genericBody: \"An unexpected error occurred. Please try again later.\",\n },\n warn: {\n selfRoleDisable: \"This role backs your own authorization-admin access. Disabling it may lock you out of this screen. Continue?\",\n selfRoleDelete: \"This role backs your own authorization-admin access. Deleting it may lock you out of this screen. Continue?\",\n selfAssignmentRemove: \"This assignment backs your own authorization-admin access. Removing it may lock you out of this screen. Continue?\",\n },\n}\n\nexport type PartialAuthzAdminLabels = {\n title?: string\n myPermission?: string\n tabs?: Partial<AuthzAdminLabels[\"tabs\"]>\n common?: Partial<AuthzAdminLabels[\"common\"]>\n roles?: Partial<AuthzAdminLabels[\"roles\"]>\n resources?: Partial<AuthzAdminLabels[\"resources\"]>\n grants?: Partial<AuthzAdminLabels[\"grants\"]>\n userRoles?: Partial<AuthzAdminLabels[\"userRoles\"]>\n groupRoles?: Partial<AuthzAdminLabels[\"groupRoles\"]>\n error?: Partial<AuthzAdminLabels[\"error\"]>\n warn?: Partial<AuthzAdminLabels[\"warn\"]>\n}\n\n/**\n * Merge partial overrides onto the English defaults, one section at a time.\n * 部分的な上書きを英語の既定値へセクション単位で統合する処理。\n *\n * The merge is shallow **per section**, not a whole-object replace: a host that overrides only\n * `grants.note` keeps every other `grants` string instead of losing them.\n * 統合はセクション単位の浅いマージであり全体置換ではない形\n * (`grants.note` だけを上書きしても、同セクションの他文言は失われない)。\n *\n * @param over Partial overrides, or `undefined` to keep the defaults. 部分的な上書き (`undefined` なら既定値のまま)。\n * @returns The fully resolved label set. 解決済みの全文言。\n */\nexport function resolveLabels(over?: PartialAuthzAdminLabels): AuthzAdminLabels {\n const d = defaultAuthzAdminLabels\n if (!over) return d\n return {\n title: over.title ?? d.title,\n myPermission: over.myPermission ?? d.myPermission,\n tabs: { ...d.tabs, ...over.tabs },\n common: { ...d.common, ...over.common },\n roles: { ...d.roles, ...over.roles },\n resources: { ...d.resources, ...over.resources },\n grants: { ...d.grants, ...over.grants },\n userRoles: { ...d.userRoles, ...over.userRoles },\n groupRoles: { ...d.groupRoles, ...over.groupRoles },\n error: { ...d.error, ...over.error },\n warn: { ...d.warn, ...over.warn },\n }\n}\n","/**\n * Presentation primitives and the inline style map shared by every authorization admin UI tab.\n * 認可管理 UI 共有のプレゼンテーション部品・スタイル。\n */\nimport type { CSSProperties, ReactNode } from \"react\"\n\n/**\n * Inline styles shared by the admin tabs, keyed by role.\n * 管理タブ共通のインラインスタイル (役割名をキーとする形)。\n *\n * `satisfies` is used rather than a `Record<string, CSSProperties>` annotation: the annotation\n * erases the key set, so a typo such as `ui.dangerBtn` type-checks and silently renders unstyled.\n * `Record<string, CSSProperties>` の注釈ではなく `satisfies` を用いる形 (注釈はキー集合を消し、\n * `ui.dangerBtn` のような綴り誤りが型検査を素通りして無装飾描画になるため)。\n */\nexport const ui = {\n page: { padding: \"1.25rem\", maxWidth: 1280, margin: \"0 auto\", color: \"#0f172a\" },\n tableContainer: { width: \"100%\", overflowX: \"auto\", marginBottom: \"1rem\", WebkitOverflowScrolling: \"touch\" },\n table: { borderCollapse: \"collapse\", width: \"100%\", fontSize: \"0.9rem\", tableLayout: \"fixed\" },\n th: { textAlign: \"left\", borderBottom: \"2px solid #e2e8f0\", padding: \"0.45rem 0.65rem\", fontSize: \"0.78rem\", color: \"#475569\", fontWeight: 600, textTransform: \"uppercase\", letterSpacing: \"0.03em\" },\n td: { borderBottom: \"1px solid #f1f5f9\", padding: \"0.5rem 0.65rem\", verticalAlign: \"middle\", wordBreak: \"break-all\" },\n input: { padding: \"0.4rem 0.55rem\", border: \"1px solid #cbd5e1\", borderRadius: 6, fontSize: \"0.9rem\", background: \"#fff\", width: \"100%\", boxSizing: \"border-box\" },\n select: { padding: \"0.4rem 0.55rem\", border: \"1px solid #cbd5e1\", borderRadius: 6, fontSize: \"0.9rem\", background: \"#fff\", width: \"100%\", boxSizing: \"border-box\" },\n textarea: { padding: \"0.4rem 0.55rem\", border: \"1px solid #cbd5e1\", borderRadius: 6, fontSize: \"0.82rem\", fontFamily: \"ui-monospace, SFMono-Regular, Menlo, monospace\", minHeight: \"3.25rem\", resize: \"vertical\", width: \"100%\", boxSizing: \"border-box\" },\n button: { padding: \"0.3rem 0.7rem\", border: \"1px solid #cbd5e1\", borderRadius: 6, background: \"#fff\", cursor: \"pointer\", fontSize: \"0.82rem\", color: \"#334155\" },\n save: { padding: \"0.3rem 0.7rem\", border: \"1px solid #2563eb\", borderRadius: 6, background: \"#eff6ff\", cursor: \"pointer\", fontSize: \"0.82rem\", color: \"#2563eb\", fontWeight: 600 },\n danger: { padding: \"0.3rem 0.7rem\", border: \"1px solid #fecaca\", borderRadius: 6, background: \"#fff\", cursor: \"pointer\", fontSize: \"0.82rem\", color: \"#dc2626\" },\n primary: { padding: \"0.5rem 1.25rem\", border: \"1px solid #2563eb\", borderRadius: 6, background: \"#2563eb\", color: \"#fff\", cursor: \"pointer\", fontSize: \"0.9rem\", fontWeight: 600, alignSelf: \"flex-start\" },\n card: { display: \"flex\", flexDirection: \"column\", gap: \"0.85rem\", maxWidth: 600, marginTop: \"0.75rem\", padding: \"1rem 1.1rem\", border: \"1px solid #e2e8f0\", borderRadius: 10, background: \"#f8fafc\" },\n code: { fontFamily: \"ui-monospace, SFMono-Regular, Menlo, monospace\", fontSize: \"0.78rem\", color: \"#334155\", wordBreak: \"break-all\" },\n muted: { color: \"#94a3b8\" },\n h2: { fontSize: \"1.05rem\", margin: \"0 0 0.35rem\", fontWeight: 700 },\n h3: { fontSize: \"0.92rem\", margin: \"1.25rem 0 0\", color: \"#334155\", fontWeight: 600 },\n note: { color: \"#64748b\", fontSize: \"0.82rem\", margin: \"0 0 0.75rem\" },\n empty: { color: \"#94a3b8\", padding: \"0.75rem 0.65rem\" },\n} satisfies Record<string, CSSProperties>\n\n/**\n * Stack a label above its control, keeping the two from overlapping and the tabs consistent.\n * ラベルを上、コントロールを下に縦積みするフィールド (重なり防止・レイアウト統一)。\n *\n * A `<div>` rather than a `<label>`: the control is expected to carry its own `aria-label`.\n * `<label>` ではなく `<div>` で構成する形 (コントロール側が `aria-label` を持つ前提のため)。\n *\n * @param props The label text, an optional hint, and the control to wrap. ラベル文言・任意の補足・包むコントロール。\n * @returns The stacked field. 縦積みされたフィールド。\n */\nexport function Field({ label, hint, children }: { label: string; hint?: string; children: ReactNode }) {\n return (\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: \"0.3rem\" }}>\n <span style={{ fontSize: \"0.8rem\", color: \"#475569\", fontWeight: 600 }}>\n {label}\n {hint ? <span style={{ fontWeight: 400, color: \"#94a3b8\" }}> — {hint}</span> : null}\n </span>\n {children}\n </div>\n )\n}\n\n/**\n * Render an error message as an assertive alert, or nothing when there is no message.\n * エラーメッセージを `role=\"alert\"` で表示するコンポーネント (メッセージが無ければ何も描画しない形)。\n *\n * @param props The message to show, if any. 表示するメッセージ (省略可)。\n * @returns The alert element, or `null`. アラート要素、または `null`。\n */\nexport function ErrorAlert({ message }: { message?: string }) {\n if (!message) return null\n return (\n <p role=\"alert\" style={{ color: \"#dc2626\", background: \"#fef2f2\", border: \"1px solid #fecaca\", borderRadius: 8, padding: \"0.55rem 0.8rem\", fontSize: \"0.85rem\", margin: \"0.25rem 0\" }}>\n {message}\n </p>\n )\n}\n","/**\n * Client-safe presentation components of the authorization admin UI.\n * Every view reads its rows, labels and self-admin hints from the loader payload through\n * `useLoaderData`, and depends on no database or store — the server wiring lives in `admin/server.ts`.\n * 認可管理 UI のプレゼンテーション部品 (client-safe)。\n * loader データ (行 + ラベル + 自己管理者ヒント) を `useLoaderData` で受け取り描画する形。\n * DB / ストアには一切依存しない (サーバ結線は `admin/server.ts`)。\n *\n * **Known deviations of {@link AuthzGroupRolesView}** from the conventions the other tabs follow:\n * its `group` / `role` columns carry no percentage width, its action column is sized in `px`, its\n * table is not wrapped in `ui.tableContainer`, and its removal confirmation text is hard-coded in\n * Japanese instead of coming from `labels.warn`. They are recorded in the design specification\n * rather than silently changed here, because the spec compares the tabs against each other.\n * **{@link AuthzGroupRolesView} の既知の逸脱**: `group` / `role` 列の幅指定なし・操作列の `px` 指定・\n * `ui.tableContainer` 非適用・削除確認文言のハードコード (`labels.warn` 由来ではない) の 4 点\n * (仕様書に記録のうえ据え置く形)。\n */\nimport { AUTHZ_ACTIONS, type AuthzAssignment, type AuthzGrant, type AuthzGroupRoleAssignment, type AuthzGroupSummary, type AuthzResource, type AuthzRole, type AuthzUser } from \"@aiquants/authz-core\"\nimport { SelectBox } from \"@aiquants/select-box\"\nimport { useState } from \"react\"\nimport { Form, isRouteErrorResponse, NavLink, Outlet, useActionData, useLoaderData, useRouteError } from \"react-router\"\nimport { type MyPermissionGroupItem, MyPermissionGroupStatus } from \"../MyPermissionGroupStatus\"\nimport type { PermissionView } from \"../server\"\nimport { type AuthzAdminLabels, type PartialAuthzAdminLabels, resolveLabels } from \"./labels\"\nimport { ErrorAlert, Field, ui } from \"./ui\"\n\ntype WithLabels = { labels: AuthzAdminLabels }\n/**\n * Loader payload of any tenant-scoped tab: the tenant every row on screen was loaded for.\n * テナント単位タブの loader ペイロード共通部 (画面上の全行を読み込んだテナント)。\n */\ntype WithTenant = { tenantId: string }\ntype ActionError = { error?: string } | undefined\n/**\n * Read the error message out of an action payload, if the action reported one.\n * action の戻り値からエラーメッセージを取り出す処理。\n *\n * @param d The `useActionData()` payload of the tab. 当該タブの `useActionData()` の戻り値。\n * @returns The message, or `undefined` when the action did not fail. メッセージ (失敗していなければ `undefined`)。\n */\nconst actionError = (d: ActionError) => d?.error\n\n/**\n * Render one table cell showing which tenant a row belongs to.\n * 行の所属テナントを表示するテーブルセルを描画するコンポーネント。\n *\n * @param props The tenant to show and the `data-testid` to expose it under. 表示するテナントと露出する `data-testid`。\n * @returns The tenant cell element. テナントセル要素。\n */\nfunction TenantCell({ tenantId, testId }: { tenantId: string; testId: string }) {\n return (\n <td style={ui.td}>\n <span style={ui.code} data-testid={testId}>\n {tenantId}\n </span>\n </td>\n )\n}\n\n/**\n * Resolve the tenant a join row (grant / assignment) inherits from the role it references.\n * 結合行 (付与・割当) が参照先ロールから受け継ぐテナントを解決する処理。\n *\n * `TDRolePermission` / `TDUserRole` / `TDGroupRole` carry no tenant column of their own, so the only\n * real tenant available is the one projected onto the referenced role row. An unresolvable role means\n * the two listings disagree — that is shown as \"unknown\" rather than assumed to be the current tenant,\n * because assuming would paint a foreign row as local.\n * これらの結合表はテナント列を持たないため、参照先ロール行の実テナントが唯一の出所。解決できない場合は\n * 「現在のテナント」と仮定せず不明として表示する (仮定は越境行を自テナントに見せるため)。\n *\n * @param roles Roles loaded for the same tenant. 同一テナントで読み込んだロール群。\n * @param roleId The role the join row references. 結合行が参照するロール id。\n * @param labels Resolved UI strings. 解決済みの UI 文言。\n * @returns The role's tenant, or the \"unknown\" marker. ロールのテナント、または不明マーカー。\n */\nfunction tenantOfRole(roles: AuthzRole[], roleId: number, labels: AuthzAdminLabels): string {\n const role = roles.find((r) => r.id === roleId)\n return role ? role.tenantId : labels.common.unknownTenant\n}\n\n// ============================ Layout ============================\n\nexport type AuthzLayoutData = WithLabels & WithTenant & { myPermissions: Record<string, PermissionView | undefined>; adminResourceKey: string; basePath?: string }\n\nexport type AuthzLayoutShellProps = {\n children: React.ReactNode\n renderHeader?: (props: { title: string; annotation: React.ReactNode }) => React.ReactNode\n}\n\n/**\n * Render the shared chrome of the admin UI: the header (title + \"your permissions\") and the tabs.\n * 認可管理 UI 共通の外枠 (ヘッダ = タイトル + あなたの権限、およびタブ) を描画するコンポーネント。\n *\n * @param props The tab body to render and an optional header renderer. 描画する中身 (splat なら children、ネストなら Outlet) と任意のヘッダ描画関数。\n * @returns The framed admin page. 外枠つきの管理画面。\n */\nfunction AuthzLayoutShell({ children, renderHeader }: AuthzLayoutShellProps) {\n const { myPermissions, labels, adminResourceKey, basePath, tenantId } = useLoaderData() as AuthzLayoutData\n const base = basePath ?? \"/authz\"\n const [variant, setVariant] = useState<\"pills\" | \"popover\">(\"pills\")\n\n const tabs: ReadonlyArray<readonly [string, string]> = [\n [\"roles\", labels.tabs.roles],\n [\"resources\", labels.tabs.resources],\n [\"role_permissions\", labels.tabs.role_permissions],\n [\"user_roles\", labels.tabs.user_roles],\n [\"group_roles\", labels.tabs.group_roles],\n [\"graph\", labels.tabs.graph],\n ]\n\n const permissionItems: MyPermissionGroupItem[] = Object.entries(myPermissions || {}).map(([key, perm]) => ({\n resourceKey: key,\n resourceName: perm?.resourceKey || key,\n permission: perm,\n expectedResourceKey: key,\n }))\n\n const items =\n permissionItems.length > 0\n ? permissionItems\n : [\n {\n resourceKey: adminResourceKey,\n resourceName: adminResourceKey,\n permission: myPermissions?.[adminResourceKey],\n expectedResourceKey: adminResourceKey,\n },\n ]\n\n const annotation = (\n <div style={{ display: \"flex\", alignItems: \"center\", gap: \"0.4rem\" }}>\n <span data-testid=\"authz-tenant-badge\" style={{ ...ui.code, border: \"1px solid #cbd5e1\", borderRadius: 4, padding: \"1px 6px\", background: \"#f8fafc\" }}>\n {labels.common.tenant}: {tenantId}\n </span>\n <MyPermissionGroupStatus prefixLabel={labels.myPermission} items={items} variant={variant} size=\"xs\" />\n <div style={{ display: \"inline-flex\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", padding: \"1px\", backgroundColor: \"#f8fafc\", fontSize: \"0.7rem\" }}>\n <button\n type=\"button\"\n onClick={() => setVariant(\"pills\")}\n data-testid=\"switch-variant-pills\"\n style={{\n padding: \"1px 5px\",\n borderRadius: \"3px\",\n border: \"none\",\n cursor: \"pointer\",\n fontSize: \"0.7rem\",\n lineHeight: \"1.2\",\n fontWeight: variant === \"pills\" ? 600 : 400,\n backgroundColor: variant === \"pills\" ? \"#ffffff\" : \"transparent\",\n color: variant === \"pills\" ? \"#1e293b\" : \"#64748b\",\n boxShadow: variant === \"pills\" ? \"0 1px 2px rgba(0,0,0,0.05)\" : \"none\",\n }}>\n Pills\n </button>\n <button\n type=\"button\"\n onClick={() => setVariant(\"popover\")}\n data-testid=\"switch-variant-popover\"\n style={{\n padding: \"1px 5px\",\n borderRadius: \"3px\",\n border: \"none\",\n cursor: \"pointer\",\n fontSize: \"0.7rem\",\n lineHeight: \"1.2\",\n fontWeight: variant === \"popover\" ? 600 : 400,\n backgroundColor: variant === \"popover\" ? \"#ffffff\" : \"transparent\",\n color: variant === \"popover\" ? \"#1e293b\" : \"#64748b\",\n boxShadow: variant === \"popover\" ? \"0 1px 2px rgba(0,0,0,0.05)\" : \"none\",\n }}>\n Popover\n </button>\n </div>\n </div>\n )\n\n return (\n <div className=\"authz-admin\" style={ui.page}>\n {renderHeader ? (\n renderHeader({ title: labels.title, annotation })\n ) : (\n <header style={{ display: \"flex\", alignItems: \"center\", justifyContent: \"space-between\", gap: \"0.75rem\", flexWrap: \"wrap\", margin: \"0 0 0.4rem\" }}>\n <h1 style={{ fontSize: \"1.15rem\", margin: 0, fontWeight: 700, color: \"#1e293b\" }}>{labels.title}</h1>\n {annotation}\n </header>\n )}\n <nav style={{ display: \"flex\", gap: \"0.25rem\", borderBottom: \"1px solid #e2e8f0\", margin: \"0.6rem 0 1rem\" }}>\n {tabs.map(([key, label]) => (\n <NavLink\n key={key}\n to={`${base}/${key}`}\n style={({ isActive }) => ({\n padding: \"0.5rem 0.9rem\",\n textDecoration: \"none\",\n color: isActive ? \"#2563eb\" : \"#475569\",\n borderBottom: isActive ? \"2px solid #2563eb\" : \"2px solid transparent\",\n marginBottom: -1,\n fontWeight: isActive ? 700 : 500,\n fontSize: \"0.92rem\",\n })}>\n {label}\n </NavLink>\n ))}\n </nav>\n {children}\n </div>\n )\n}\n\nexport type AuthzLayoutProps = {\n renderHeader?: (props: { title: string; annotation: React.ReactNode }) => React.ReactNode\n}\n\n/**\n * Layout route for a nested route configuration; the active tab is rendered through `Outlet`.\n * ネストルート構成用のレイアウト (子タブは `Outlet` で描画)。`routes.ts` をネスト構成にする場合の入口。\n *\n * @param props Optional header renderer. 任意のヘッダ描画関数。\n * @returns The framed admin page hosting the nested route. ネストされた子ルートを収めた管理画面。\n */\nexport function AuthzLayout({ renderHeader }: AuthzLayoutProps = {}) {\n return (\n <AuthzLayoutShell renderHeader={renderHeader}>\n <Outlet />\n </AuthzLayoutShell>\n )\n}\n\n/**\n * Build the admin UI's error boundary with labels baked in.\n * ラベルを埋め込んだ認可管理 UI のエラー境界を生成する処理。\n *\n * An error boundary cannot read loader data, so its wording cannot come from the loader the way\n * every tab's does; the host injects its own locale here instead.\n * エラー境界は loader データを読めないため、他タブのようにラベルを loader から得られない形\n * (アプリ側が自分のロケールをここで注入する)。\n *\n * @param over Partial label overrides; omitted sections keep the English defaults. 部分的なラベル上書き (未指定は英語の既定値)。\n * @returns An error boundary component bound to those labels. そのラベルを束縛したエラー境界コンポーネント。\n */\nexport function makeAuthzErrorBoundary(over?: PartialAuthzAdminLabels): () => React.ReactElement {\n const L = resolveLabels(over).error\n /**\n * Render the caught routing error, distinguishing a 403 from any other failure.\n * 捕捉したルーティングエラーを描画するコンポーネント (403 とその他を区別する形)。\n *\n * @returns The error panel. エラー表示パネル。\n */\n return function AuthzErrorBoundary() {\n const error = useRouteError()\n const routeError = isRouteErrorResponse(error) ? error : null\n // 認可拒否は Response ではなく `status` を持つ素の例外として届く (ガードが送出する\n // `AuthzDeniedError`)。Response だけを見ていると、拒否された利用者に「エラーが発生\n // しました」という汎用文言が出て、権限の話だと判らない\n const is403 = routeError?.status === 403 || (typeof error === \"object\" && error !== null && (error as { status?: unknown }).status === 403)\n const title = is403 ? L.forbiddenTitle : routeError ? `${L.genericTitlePrefix} (${routeError.status})` : L.genericTitle\n const message = is403 ? L.forbiddenBody : routeError ? (typeof routeError.data === \"string\" && routeError.data ? routeError.data : routeError.statusText) : L.genericBody\n return (\n <div style={ui.page}>\n <div role=\"alert\" style={{ maxWidth: 480, margin: \"3rem auto\", padding: \"1.5rem\", border: \"1px solid #fecaca\", borderRadius: 12, background: \"#fef2f2\" }}>\n <h1 style={{ fontSize: \"1.1rem\", fontWeight: 700, color: \"#b91c1c\", margin: \"0 0 0.5rem\" }}>{title}</h1>\n <p style={{ color: \"#7f1d1d\", fontSize: \"0.9rem\", margin: 0, lineHeight: 1.7 }}>{message}</p>\n </div>\n </div>\n )\n }\n}\n\n/**\n * The error boundary bound to the package's English default labels.\n * 英語 (既定ラベル) のエラー境界。i18n する場合は {@link makeAuthzErrorBoundary} を使う形。\n */\nexport const AuthzErrorBoundary = makeAuthzErrorBoundary()\n\n/**\n * Warn before a destructive click that would remove one of the actor's own administrator paths.\n * 実行者自身の管理権限を支える対象への破壊操作について、実行前に警告する処理。\n *\n * @param active Whether the target really supports the actor's admin status. 対象が実行者の管理権限を支えているかどうか。\n * @param message Confirmation text to show. 表示する確認文言。\n * @returns A click handler that cancels the submit when the user declines. キャンセル時に送信を中止するクリックハンドラ。\n */\nconst confirmGuard = (active: boolean, message: string) => (e: React.MouseEvent) => {\n if (active && !window.confirm(message)) e.preventDefault()\n}\n\n// ============================ Roles ============================\n\nexport type AuthzRolesData = WithLabels & WithTenant & { roles: AuthzRole[]; selfAdminRoleIds?: number[] }\n\n/**\n * Roles tab: lists the tenant's roles and offers create / rename / enable / delete.\n * ロールタブ。当該テナントのロール一覧と、作成・改名・有効切替・削除の操作を提供するコンポーネント。\n *\n * Reads {@link AuthzRolesData}; the tenant shown in every row is the one the guard resolved, which\n * the loader also echoes as `tenantId`.\n * {@link AuthzRolesData} を読む形。表示するテナントはガードが解決したもの (loader が `tenantId` として同梱)。\n *\n * @returns The roles tab. ロールタブ。\n */\nexport function AuthzRolesView() {\n const { roles, selfAdminRoleIds, labels, tenantId } = useLoaderData() as AuthzRolesData\n const L = labels.roles\n const C = labels.common\n const selfAdmin = new Set(selfAdminRoleIds ?? [])\n return (\n <section>\n <h2 style={ui.h2}>{L.heading}</h2>\n <ErrorAlert message={actionError(useActionData() as ActionError)} />\n <p style={ui.note}>{L.note}</p>\n <div style={ui.tableContainer}>\n <table style={ui.table}>\n <thead>\n <tr>\n <th style={{ ...ui.th, width: \"12%\" }}>{C.tenant}</th>\n <th style={{ ...ui.th, width: \"18%\" }}>{L.roleKey}</th>\n <th style={{ ...ui.th, width: \"22%\" }}>{L.name}</th>\n <th style={{ ...ui.th, width: \"28%\" }}>{L.description}</th>\n <th style={{ ...ui.th, width: \"10%\" }}>{L.activeHeader}</th>\n <th style={{ ...ui.th, width: \"10%\" }} />\n </tr>\n </thead>\n <tbody>\n {roles.map((r) => (\n <tr key={r.id}>\n <TenantCell tenantId={r.tenantId} testId={`tenant-role-${r.id}`} />\n <td style={ui.td}>\n {/* 保存フォームにも所属テナントを載せる (サーバはガード側テナントと突き合わせ、不一致は 403) */}\n <input type=\"hidden\" name=\"tenantId\" value={r.tenantId} form={`save-role-${r.id}`} />\n {r.roleKey === \"@authenticated\" || r.roleKey === \"@anonymous\" ? <span style={ui.code}>{r.roleKey}</span> : <input name=\"roleKey\" defaultValue={r.roleKey} form={`save-role-${r.id}`} aria-label={`roleKey-${r.id}`} required style={ui.input} />}\n </td>\n <td style={ui.td}>\n <input name=\"name\" defaultValue={r.name} form={`save-role-${r.id}`} aria-label={`name-${r.id}`} required style={ui.input} />\n </td>\n <td style={ui.td}>\n <input name=\"description\" defaultValue={r.description ?? \"\"} form={`save-role-${r.id}`} aria-label={`description-${r.id}`} style={ui.input} />\n </td>\n <td style={ui.td}>\n <Form method=\"post\" style={{ display: \"inline\" }}>\n <input type=\"hidden\" name=\"_action\" value=\"toggle\" />\n <input type=\"hidden\" name=\"id\" value={r.id} />\n <input type=\"hidden\" name=\"isActive\" value={String(!r.isActive)} />\n <button type=\"submit\" style={ui.button} onClick={r.isActive ? confirmGuard(selfAdmin.has(r.id), labels.warn.selfRoleDisable) : undefined}>\n {r.isActive ? C.active : C.inactive}\n </button>\n </Form>\n </td>\n <td style={{ ...ui.td, whiteSpace: \"nowrap\" }}>\n <Form method=\"post\" id={`save-role-${r.id}`} style={{ display: \"inline\" }}>\n <input type=\"hidden\" name=\"_action\" value=\"save\" />\n <input type=\"hidden\" name=\"id\" value={r.id} />\n <button type=\"submit\" style={ui.save}>\n {C.save}\n </button>\n </Form>\n <Form method=\"post\" style={{ display: \"inline\", marginLeft: \"0.4rem\" }}>\n <input type=\"hidden\" name=\"_action\" value=\"delete\" />\n <input type=\"hidden\" name=\"id\" value={r.id} />\n <button type=\"submit\" style={ui.danger} onClick={confirmGuard(selfAdmin.has(r.id), labels.warn.selfRoleDelete)}>\n {C.delete}\n </button>\n </Form>\n </td>\n </tr>\n ))}\n {roles.length === 0 ? (\n <tr>\n <td style={ui.empty} colSpan={6}>\n {L.empty}\n </td>\n </tr>\n ) : null}\n </tbody>\n </table>\n </div>\n\n <h3 style={ui.h3}>{L.newHeading}</h3>\n <Form method=\"post\" style={ui.card}>\n <input type=\"hidden\" name=\"_action\" value=\"create\" />\n <Field label={C.tenant} hint={C.tenantHint}>\n <input name=\"tenantId\" aria-label=\"tenant_id\" defaultValue={tenantId} required style={ui.input} />\n </Field>\n <Field label={L.roleKey} hint={L.hintRoleKey}>\n <input name=\"roleKey\" aria-label=\"role_key\" required style={ui.input} />\n </Field>\n <Field label={L.name}>\n <input name=\"name\" aria-label=\"name\" required style={ui.input} />\n </Field>\n <Field label={L.description} hint={C.optional}>\n <input name=\"description\" aria-label=\"description\" style={ui.input} />\n </Field>\n <button type=\"submit\" style={ui.primary}>\n {C.add}\n </button>\n </Form>\n </section>\n )\n}\n\n// ============================ Resources ============================\n\nexport type AuthzResourcesData = WithLabels & WithTenant & { resources: AuthzResource[] }\n\n/**\n * Resources tab: lists the tenant's resources and offers create / rename / delete.\n * リソースタブ。当該テナントのリソース一覧と、作成・改名・削除の操作を提供するコンポーネント。\n *\n * Reads {@link AuthzResourcesData}; the tenant shown is the guard-resolved one.\n * {@link AuthzResourcesData} を読む形 (表示するテナントはガードが解決したもの)。\n *\n * @returns The resources tab. リソースタブ。\n */\nexport function AuthzResourcesView() {\n const { resources, labels, tenantId } = useLoaderData() as AuthzResourcesData\n const L = labels.resources\n const C = labels.common\n return (\n <section>\n <h2 style={ui.h2}>{L.heading}</h2>\n <ErrorAlert message={actionError(useActionData() as ActionError)} />\n <p style={ui.note}>{L.note}</p>\n <div style={ui.tableContainer}>\n <table style={ui.table}>\n <thead>\n <tr>\n <th style={{ ...ui.th, width: \"12%\" }}>{C.tenant}</th>\n <th style={{ ...ui.th, width: \"13%\" }}>{L.appKey}</th>\n <th style={{ ...ui.th, width: \"18%\" }}>{L.resourceKey}</th>\n <th style={{ ...ui.th, width: \"22%\" }}>{L.name}</th>\n <th style={{ ...ui.th, width: \"25%\" }}>{L.description}</th>\n <th style={{ ...ui.th, width: \"10%\" }} />\n </tr>\n </thead>\n <tbody>\n {resources.map((r) => (\n <tr key={r.id}>\n <TenantCell tenantId={r.tenantId} testId={`tenant-resource-${r.id}`} />\n <td style={ui.td}>\n <span style={ui.code}>{r.appKey}</span>\n </td>\n <td style={ui.td}>\n <span style={ui.code}>{r.resourceKey}</span>\n </td>\n <td style={ui.td}>\n <input name=\"name\" defaultValue={r.name} form={`save-res-${r.id}`} aria-label={`name-${r.id}`} required style={ui.input} />\n </td>\n <td style={ui.td}>\n <input name=\"description\" defaultValue={r.description ?? \"\"} form={`save-res-${r.id}`} aria-label={`description-${r.id}`} style={ui.input} />\n </td>\n <td style={{ ...ui.td, whiteSpace: \"nowrap\" }}>\n <Form method=\"post\" id={`save-res-${r.id}`} style={{ display: \"inline\" }}>\n <input type=\"hidden\" name=\"_action\" value=\"save\" />\n <input type=\"hidden\" name=\"id\" value={r.id} />\n <button type=\"submit\" style={ui.save}>\n {C.save}\n </button>\n </Form>\n <Form method=\"post\" style={{ display: \"inline\", marginLeft: \"0.4rem\" }}>\n <input type=\"hidden\" name=\"_action\" value=\"delete\" />\n <input type=\"hidden\" name=\"id\" value={r.id} />\n <button type=\"submit\" style={ui.danger}>\n {C.delete}\n </button>\n </Form>\n </td>\n </tr>\n ))}\n {resources.length === 0 ? (\n <tr>\n <td style={ui.empty} colSpan={6}>\n {L.empty}\n </td>\n </tr>\n ) : null}\n </tbody>\n </table>\n </div>\n\n <h3 style={ui.h3}>{L.newHeading}</h3>\n <Form method=\"post\" style={ui.card}>\n <input type=\"hidden\" name=\"_action\" value=\"create\" />\n <Field label={C.tenant} hint={C.tenantHint}>\n <input name=\"tenantId\" aria-label=\"tenant_id\" defaultValue={tenantId} required style={ui.input} />\n </Field>\n <Field label={L.appKey} hint={L.hintAppKey}>\n <input name=\"appKey\" aria-label=\"app_key\" required style={ui.input} />\n </Field>\n <Field label={L.resourceKey} hint={L.hintResourceKey}>\n <input name=\"resourceKey\" aria-label=\"resource_key\" required style={ui.input} />\n </Field>\n <Field label={L.name}>\n <input name=\"name\" aria-label=\"name\" required style={ui.input} />\n </Field>\n <Field label={L.description} hint={C.optional}>\n <input name=\"description\" aria-label=\"description\" style={ui.input} />\n </Field>\n <button type=\"submit\" style={ui.primary}>\n {C.add}\n </button>\n </Form>\n </section>\n )\n}\n\n// ============================ Grants (role_permissions) ============================\n\n/**\n * Grants tab payload. `roles` / `resources` are the **full** rows, not just the id + key pairs the\n * select boxes need: a grant row carries no tenant of its own, so the referenced role's projected\n * tenant is what the tenant column shows.\n * 付与タブのペイロード。`roles` / `resources` は全項目を持つ行 (付与行にテナント列が無く、参照先ロールの実テナントを表示するため)。\n */\nexport type AuthzGrantsData = WithLabels & WithTenant & { grants: AuthzGrant[]; roles: AuthzRole[]; resources: AuthzResource[] }\n\n/**\n * One editable grant row (its own component so the action select can hold local state).\n * 編集可能な付与行 1 件 (action の選択状態を持つため独立コンポーネント)。\n *\n * @param props The grant row, the tenant it belongs to, and the resolved labels. 付与行・所属テナント・解決済み文言。\n * @returns The table row element. テーブル行要素。\n */\nfunction GrantRow({ g, tenantId, labels }: { g: AuthzGrant; tenantId: string; labels: AuthzAdminLabels }) {\n const C = labels.common\n const [action, setAction] = useState<{ label: string; value: string }>({ label: g.action, value: g.action })\n\n return (\n <tr>\n <TenantCell tenantId={tenantId} testId={`tenant-grant-${g.id}`} />\n <td style={ui.td}>\n <span style={ui.code}>{g.roleKey}</span>\n </td>\n <td style={ui.td}>\n <span style={ui.code}>\n {g.appKey}:{g.resourceKey}\n </span>\n </td>\n <td style={ui.td}>\n {g.roleKey === \"@anonymous\" ? (\n <span style={ui.code}>{g.action}</span>\n ) : (\n <>\n <input type=\"hidden\" name=\"action\" value={action.value} form={`save-grant-${g.id}`} />\n <SelectBox\n options={AUTHZ_ACTIONS.map((a) => ({ label: a, value: a }))}\n value={action}\n onChange={(val) => {\n if (val) setAction(val as { label: string; value: string })\n }}\n />\n </>\n )}\n </td>\n <td style={ui.td}>\n <textarea name=\"rowScope\" defaultValue={g.rowScope ?? \"\"} form={`save-grant-${g.id}`} aria-label={`row_scope-${g.id}`} rows={2} placeholder={labels.grants.rowScopePlaceholder} style={ui.textarea} />\n </td>\n <td style={ui.td}>\n <textarea name=\"columnScope\" defaultValue={g.columnScope ?? \"\"} form={`save-grant-${g.id}`} aria-label={`column_scope-${g.id}`} rows={2} placeholder={labels.grants.columnScopePlaceholder} style={ui.textarea} />\n </td>\n <td style={{ ...ui.td, whiteSpace: \"nowrap\" }}>\n <Form method=\"post\" id={`save-grant-${g.id}`} style={{ display: \"inline\" }}>\n <input type=\"hidden\" name=\"_action\" value=\"save\" />\n <input type=\"hidden\" name=\"id\" value={g.id} />\n <button type=\"submit\" style={ui.save}>\n {C.save}\n </button>\n </Form>\n <Form method=\"post\" style={{ display: \"inline\", marginLeft: \"0.4rem\" }}>\n <input type=\"hidden\" name=\"_action\" value=\"delete\" />\n <input type=\"hidden\" name=\"id\" value={g.id} />\n <button type=\"submit\" style={ui.danger}>\n {C.delete}\n </button>\n </Form>\n </td>\n </tr>\n )\n}\n\n/**\n * Permissions tab: lists the tenant's grants and offers grant / re-scope / revoke.\n * 権限付与タブ。当該テナントの付与一覧と、付与・スコープ変更・削除の操作を提供するコンポーネント。\n *\n * Reads {@link AuthzGrantsData}; its role and resource pickers are already narrowed to the\n * guard-resolved tenant by the loader, so a cross-tenant pair cannot be selected here.\n * {@link AuthzGrantsData} を読む形。ロール・リソースの選択肢は loader が解決済みテナントへ絞ってあるため、\n * 越境した組み合わせは選べない。\n *\n * @returns The permissions tab. 権限付与タブ。\n */\nexport function AuthzGrantsView() {\n const { grants, roles, resources, labels, tenantId } = useLoaderData() as AuthzGrantsData\n const L = labels.grants\n const C = labels.common\n\n const roleOptions = roles.map((r) => ({ label: r.roleKey, value: r.id }))\n const resourceOptions = resources.map((r) => ({ label: `${r.appKey}:${r.resourceKey}`, value: r.id }))\n const actionOptions = AUTHZ_ACTIONS.map((a) => ({ label: a, value: a }))\n\n const [selectedRole, setSelectedRole] = useState<{ label: string; value: number } | undefined>(undefined)\n const [selectedResource, setSelectedResource] = useState<{ label: string; value: number } | undefined>(undefined)\n const [selectedAction, setSelectedAction] = useState<{ label: string; value: string } | undefined>({ label: \"read\", value: \"read\" })\n\n return (\n <section>\n <h2 style={ui.h2}>{L.heading}</h2>\n <p style={ui.note}>{L.note}</p>\n <ErrorAlert message={actionError(useActionData() as ActionError)} />\n <div style={ui.tableContainer}>\n <table style={ui.table}>\n <thead>\n <tr>\n <th style={{ ...ui.th, width: \"10%\" }}>{C.tenant}</th>\n <th style={{ ...ui.th, width: \"13%\" }}>{L.role}</th>\n <th style={{ ...ui.th, width: \"17%\" }}>{L.resource}</th>\n <th style={{ ...ui.th, width: \"13%\" }}>{L.action}</th>\n <th style={{ ...ui.th, width: \"19%\" }}>{L.rowScope}</th>\n <th style={{ ...ui.th, width: \"18%\" }}>{L.columnScope}</th>\n <th style={{ ...ui.th, width: \"10%\" }} />\n </tr>\n </thead>\n <tbody>\n {grants.map((g) => (\n <GrantRow key={g.id} g={g} tenantId={tenantOfRole(roles, g.roleId, labels)} labels={labels} />\n ))}\n {grants.length === 0 ? (\n <tr>\n <td style={ui.empty} colSpan={7}>\n {L.empty}\n </td>\n </tr>\n ) : null}\n </tbody>\n </table>\n </div>\n\n <h3 style={ui.h3}>{L.newHeading}</h3>\n <Form method=\"post\" style={ui.card}>\n <input type=\"hidden\" name=\"_action\" value=\"create\" />\n <input type=\"hidden\" name=\"roleId\" value={selectedRole?.value ?? \"\"} />\n <input type=\"hidden\" name=\"resourceId\" value={selectedResource?.value ?? \"\"} />\n <input type=\"hidden\" name=\"action\" value={selectedAction?.value ?? \"\"} />\n\n <Field label={C.tenant} hint={C.tenantHint}>\n <input name=\"tenantId\" aria-label=\"tenant_id\" defaultValue={tenantId} required style={ui.input} />\n </Field>\n <Field label={L.role}>\n <SelectBox options={roleOptions} value={selectedRole} onChange={(val) => setSelectedRole((val || undefined) as { label: string; value: number } | undefined)} placeholder={L.selectPlaceholder} />\n </Field>\n <Field label={L.resource}>\n <SelectBox options={resourceOptions} value={selectedResource} onChange={(val) => setSelectedResource((val || undefined) as { label: string; value: number } | undefined)} placeholder={L.selectPlaceholder} />\n </Field>\n <Field label={L.action}>\n <SelectBox options={actionOptions} value={selectedAction} onChange={(val) => setSelectedAction((val || undefined) as { label: string; value: string } | undefined)} />\n </Field>\n <Field label={L.rowScope} hint={L.rowScopeHint}>\n <textarea name=\"rowScope\" aria-label=\"row_scope\" rows={2} style={ui.textarea} placeholder='{\"filters\":[{\"field\":\"部門CD\",\"op\":\"in\",\"values\":[\"A\"]}]}' />\n </Field>\n <Field label={L.columnScope} hint={L.columnScopeHint}>\n <textarea name=\"columnScope\" aria-label=\"column_scope\" rows={2} style={ui.textarea} placeholder='{\"mode\":\"deny\",\"columns\":[\"金額\"]}' />\n </Field>\n <button type=\"submit\" style={ui.primary}>\n {C.grant}\n </button>\n </Form>\n </section>\n )\n}\n\n// ============================ User roles ============================\n\n/**\n * User-assignment tab payload. `roles` holds the **full** role rows because `TDUserRole` has no tenant\n * column: the assignment's tenant is the one projected onto the role it points at.\n * ユーザー割当タブのペイロード。`TDUserRole` にテナント列が無いため、`roles` は参照先ロールの実テナントを含む全項目行。\n */\nexport type AuthzUserRolesData = WithLabels & WithTenant & { assignments: AuthzAssignment[]; roles: AuthzRole[]; users: AuthzUser[]; actorUserId?: number; selfAdminRoleIds?: number[] }\n\n/**\n * User-roles tab: lists the tenant's user assignments and offers assign / unassign.\n * ユーザー割当タブ。当該テナントのユーザー×ロール割当一覧と、割当・解除の操作を提供するコンポーネント。\n *\n * Reads {@link AuthzUserRolesData}; removing an assignment that supports the actor's own admin\n * status is confirmed first through {@link confirmGuard}.\n * {@link AuthzUserRolesData} を読む形 (実行者自身の管理権限を支える割当の解除は {@link confirmGuard} で確認)。\n *\n * @returns The user-roles tab. ユーザー割当タブ。\n */\nexport function AuthzUserRolesView() {\n const { assignments, roles, users, actorUserId, selfAdminRoleIds, labels } = useLoaderData() as AuthzUserRolesData\n const L = labels.userRoles\n const C = labels.common\n const selfAdmin = new Set(selfAdminRoleIds ?? [])\n\n const userOptions = users.map((u) => ({ label: `${u.email}(${u.displayName})`, value: u.id }))\n const roleOptions = roles.filter((r) => r.roleKey !== \"@authenticated\" && r.roleKey !== \"@anonymous\").map((r) => ({ label: r.roleKey, value: r.id }))\n\n const [selectedUser, setSelectedUser] = useState<{ label: string; value: number } | undefined>(undefined)\n const [selectedRole, setSelectedRole] = useState<{ label: string; value: number } | undefined>(undefined)\n\n return (\n <section>\n <h2 style={ui.h2}>{L.heading}</h2>\n <ErrorAlert message={actionError(useActionData() as ActionError)} />\n <p style={ui.note}>{L.note}</p>\n <div style={ui.tableContainer}>\n <table style={ui.table}>\n <thead>\n <tr>\n <th style={{ ...ui.th, width: \"12%\" }}>{C.tenant}</th>\n <th style={{ ...ui.th, width: \"40%\" }}>{L.user}</th>\n <th style={{ ...ui.th, width: \"38%\" }}>{L.role}</th>\n <th style={{ ...ui.th, width: \"10%\" }} />\n </tr>\n </thead>\n <tbody>\n {assignments.map((a) => (\n <tr key={`${a.userId}-${a.roleId}`}>\n <TenantCell tenantId={tenantOfRole(roles, a.roleId, labels)} testId={`tenant-assignment-${a.userId}-${a.roleId}`} />\n <td style={ui.td}>\n {a.email}\n <span style={ui.muted}>({a.displayName})</span>\n </td>\n <td style={ui.td}>{a.roleKey}</td>\n <td style={ui.td}>\n <Form method=\"post\" style={{ display: \"inline\" }}>\n <input type=\"hidden\" name=\"_action\" value=\"remove\" />\n <input type=\"hidden\" name=\"userId\" value={a.userId} />\n <input type=\"hidden\" name=\"roleId\" value={a.roleId} />\n <button type=\"submit\" style={ui.danger} onClick={confirmGuard(a.userId === actorUserId && selfAdmin.has(a.roleId), labels.warn.selfAssignmentRemove)}>\n {C.remove}\n </button>\n </Form>\n </td>\n </tr>\n ))}\n {assignments.length === 0 ? (\n <tr>\n <td style={ui.empty} colSpan={4}>\n {L.empty}\n </td>\n </tr>\n ) : null}\n </tbody>\n </table>\n </div>\n\n <h3 style={ui.h3}>{L.newHeading}</h3>\n <Form method=\"post\" style={ui.card}>\n <input type=\"hidden\" name=\"_action\" value=\"assign\" />\n <input type=\"hidden\" name=\"userId\" value={selectedUser?.value ?? \"\"} />\n <input type=\"hidden\" name=\"roleId\" value={selectedRole?.value ?? \"\"} />\n <Field label={L.user}>\n <SelectBox options={userOptions} value={selectedUser} onChange={(val) => setSelectedUser((val || undefined) as { label: string; value: number } | undefined)} placeholder={L.selectPlaceholder} />\n </Field>\n <Field label={L.role}>\n <SelectBox options={roleOptions} value={selectedRole} onChange={(val) => setSelectedRole((val || undefined) as { label: string; value: number } | undefined)} placeholder={L.selectPlaceholder} />\n </Field>\n <button type=\"submit\" style={ui.primary}>\n {C.assign}\n </button>\n </Form>\n </section>\n )\n}\n\n// ============================ Group roles ============================\n\nexport type AuthzGroupRolesData = WithLabels &\n WithTenant & {\n groupRoles: AuthzGroupRoleAssignment[]\n roles: AuthzRole[]\n groups: AuthzGroupSummary[]\n }\n\n/**\n * Group-roles tab: lists the tenant's group assignments and offers assign / unassign.\n * グループ割当タブ。当該テナントのグループ×ロール割当一覧と、割当・解除の操作を提供するコンポーネント。\n *\n * Reads {@link AuthzGroupRolesData}. See the module docstring for its known layout deviations from\n * the other tabs (hard-coded confirmation text, no tenant column, error alert placed differently).\n * {@link AuthzGroupRolesData} を読む形。他タブとの既知の逸脱 (確認文言のハードコード・テナント列の非表示・\n * エラー表示位置の相違) はモジュール冒頭の記載を参照。\n *\n * @returns The group-roles tab. グループ割当タブ。\n */\nexport function AuthzGroupRolesView() {\n const { groupRoles, roles, groups, labels } = useLoaderData() as AuthzGroupRolesData\n const actionData = useActionData() as ActionError\n const err = actionError(actionData)\n const L = labels.groupRoles\n const C = labels.common\n\n const activeRoles = roles.filter((r) => r.isActive && r.roleKey !== \"@authenticated\" && r.roleKey !== \"@anonymous\")\n\n const groupOptions = groups.map((g) => ({ label: `${g.name} (${g.groupKey})`, value: g.id }))\n const roleOptions = activeRoles.map((r) => ({ label: `${r.name} (${r.roleKey})`, value: r.id }))\n\n const [selectedGroup, setSelectedGroup] = useState<{ label: string; value: number } | undefined>(groupOptions[0])\n const [selectedRole, setSelectedRole] = useState<{ label: string; value: number } | undefined>(roleOptions[0])\n\n return (\n <section style={{ display: \"flex\", flexDirection: \"column\", gap: \"1.25rem\" }}>\n <div>\n <h2 style={ui.h2}>{L.heading}</h2>\n <p style={ui.note}>{L.note}</p>\n </div>\n {err && <ErrorAlert message={err} />}\n\n <table style={ui.table}>\n <thead>\n <tr>\n <th style={{ ...ui.th, width: \"12%\" }}>{C.tenant}</th>\n <th style={ui.th}>{L.group}</th>\n <th style={ui.th}>{L.role}</th>\n <th style={{ ...ui.th, width: \"100px\" }} />\n </tr>\n </thead>\n <tbody>\n {groupRoles.length === 0 ? (\n <tr>\n <td colSpan={4} style={{ ...ui.td, textAlign: \"center\", color: \"#64748b\" }}>\n {L.empty}\n </td>\n </tr>\n ) : (\n groupRoles.map((a) => (\n <tr key={`${a.groupId}-${a.roleId}`}>\n <TenantCell tenantId={tenantOfRole(roles, a.roleId, labels)} testId={`tenant-group-role-${a.groupId}-${a.roleId}`} />\n <td style={ui.td}>\n <strong>{a.groupName}</strong> <span style={{ color: \"#64748b\", fontSize: \"0.85rem\" }}>({a.groupKey})</span>\n </td>\n <td style={ui.td}>\n <code>{a.roleKey}</code>\n </td>\n <td style={{ ...ui.td, textAlign: \"right\" }}>\n <Form method=\"post\" onSubmit={(e) => !confirm(\"このグループロール割当を解除しますか?\") && e.preventDefault()}>\n <input type=\"hidden\" name=\"_action\" value=\"remove\" />\n <input type=\"hidden\" name=\"groupId\" value={a.groupId} />\n <input type=\"hidden\" name=\"roleId\" value={a.roleId} />\n <button type=\"submit\" style={ui.danger}>\n {C.remove}\n </button>\n </Form>\n </td>\n </tr>\n ))\n )}\n </tbody>\n </table>\n\n <Form method=\"post\" style={ui.card}>\n <h3 style={ui.h3}>{L.newHeading}</h3>\n <input type=\"hidden\" name=\"_action\" value=\"assign\" />\n <input type=\"hidden\" name=\"groupId\" value={selectedGroup?.value ?? \"\"} />\n <input type=\"hidden\" name=\"roleId\" value={selectedRole?.value ?? \"\"} />\n <Field label={L.group}>\n <SelectBox options={groupOptions} value={selectedGroup} onChange={(val) => setSelectedGroup((val || undefined) as { label: string; value: number } | undefined)} placeholder={L.selectPlaceholder} />\n </Field>\n <Field label={L.role}>\n <SelectBox options={roleOptions} value={selectedRole} onChange={(val) => setSelectedRole((val || undefined) as { label: string; value: number } | undefined)} placeholder={L.selectPlaceholder} />\n </Field>\n <button type=\"submit\" style={ui.primary}>\n {C.assign}\n </button>\n </Form>\n </section>\n )\n}\n\n// ============================ Graph view ============================\n\nexport type AuthzGraphData = WithLabels &\n WithTenant & {\n users: AuthzUser[]\n roles: AuthzRole[]\n resources: AuthzResource[]\n grants: AuthzGrant[]\n assignments: AuthzAssignment[]\n groupRoles?: AuthzGroupRoleAssignment[]\n groups?: AuthzGroupSummary[]\n }\n\nconst ACTION_COLORS: Record<string, { bg: string; text: string; border: string }> = {\n read: { bg: \"#ecfdf5\", text: \"#047857\", border: \"#a7f3d0\" },\n create: { bg: \"#eff6ff\", text: \"#1d4ed8\", border: \"#bfdbfe\" },\n update: { bg: \"#fffbeb\", text: \"#b45309\", border: \"#fde68a\" },\n delete: { bg: \"#fef2f2\", text: \"#b91c1c\", border: \"#fecaca\" },\n}\n\nexport type AuthzNodeType = \"user\" | \"group\" | \"role\" | \"resource\"\n\n/**\n * Draw the user → group → role → resource authorization graph as an SVG topology map.\n * ユーザー → グループ → ロール → リソースの認可関係を SVG のトポロジ図として描画するコンポーネント。\n *\n * Hovering or selecting a node dims everything not reachable from it, so a single grant path can be\n * followed end to end.\n * ノードのホバー・選択で、到達しない要素を減光する形 (1 本の付与経路を端から端まで追える)。\n *\n * @param props The graph rows to draw, plus the selected node and its click handler. 描画対象の行と、選択ノード・そのクリックハンドラ。\n * @returns The topology map. トポロジ図。\n */\nfunction AuthzTopologyMap({\n users,\n roles,\n resources,\n grants,\n assignments,\n groupRoles = [],\n groups = [],\n selectedNode,\n onNodeClick,\n}: {\n users: AuthzUser[]\n roles: AuthzRole[]\n resources: AuthzResource[]\n grants: AuthzGrant[]\n assignments: AuthzAssignment[]\n groupRoles?: AuthzGroupRoleAssignment[]\n groups?: AuthzGroupSummary[]\n selectedNode: { type: AuthzNodeType; id: number } | null\n onNodeClick: (type: AuthzNodeType, id: number) => void\n}) {\n const [hoveredNode, setHoveredNode] = useState<{ type: AuthzNodeType; id: number } | null>(null)\n\n const connectedUserIds = new Set(assignments.map((a) => a.userId))\n const connectedGroupIds = new Set(groupRoles.map((g) => g.groupId))\n const connectedRoleIds = new Set([...assignments.map((a) => a.roleId), ...groupRoles.map((g) => g.roleId), ...grants.map((g) => g.roleId)])\n const connectedResourceIds = new Set(grants.map((g) => g.resourceId))\n\n const activeUsers = users.filter((u) => connectedUserIds.has(u.id))\n const activeGroups = groups.filter((g) => connectedGroupIds.has(g.id))\n const activeRoles = roles.filter((r) => connectedRoleIds.has(r.id))\n const activeResources = resources.filter((res) => connectedResourceIds.has(res.id))\n\n /**\n * Actions actually granted on a resource, or `[\"none\"]` when it carries no grant at all.\n * 当該リソースに実際に付与されているアクション (付与が皆無なら `[\"none\"]`)。\n *\n * @param resId Resource id. リソース ID。\n * @returns The distinct granted actions. 重複を除いた付与済みアクション。\n */\n const getResourceActions = (resId: number) => {\n const resGrants = grants.filter((g) => g.resourceId === resId)\n const uniqueActions = Array.from(new Set(resGrants.map((g) => g.action)))\n return uniqueActions.length > 0 ? uniqueActions : [\"none\"]\n }\n\n /**\n * Pixel height of a resource card, which grows with the number of actions it lists.\n * リソースカードの高さ (px)。列挙するアクション数に応じて伸びる形。\n *\n * @param resId Resource id. リソース ID。\n * @returns The card height in pixels. カードの高さ (px)。\n */\n const getResourceCardHeight = (resId: number) => {\n const actions = getResourceActions(resId)\n return 8 + 24 + actions.length * 24 + 6\n }\n\n const resourceCardGap = 16\n let cumulativeResY = 0\n const resourceYMap = new Map<number, { top: number; height: number; actionYMap: Map<string, number> }>()\n\n activeResources.forEach((res) => {\n const actions = getResourceActions(res.id)\n const height = getResourceCardHeight(res.id)\n const top = cumulativeResY\n const actionYMap = new Map<string, number>()\n\n actions.forEach((act, actIdx) => {\n const actY = top + 8 + 24 + actIdx * 24 + 12\n actionYMap.set(act, actY)\n })\n\n resourceYMap.set(res.id, { top, height, actionYMap })\n cumulativeResY += height + resourceCardGap\n })\n\n const totalResourceHeight = cumulativeResY > 0 ? cumulativeResY - resourceCardGap : 0\n\n const stdCardHeight = 52\n const stdCardGap = 18\n const stdItemHeight = stdCardHeight + stdCardGap\n\n const totalRoleHeight = activeRoles.length > 0 ? activeRoles.length * stdItemHeight - stdCardGap : 0\n const totalGroupHeight = activeGroups.length > 0 ? activeGroups.length * stdItemHeight - stdCardGap : 0\n const totalUserHeight = activeUsers.length > 0 ? activeUsers.length * stdItemHeight - stdCardGap : 0\n\n const maxColumnHeight = Math.max(totalResourceHeight, totalRoleHeight, totalGroupHeight, totalUserHeight, 320)\n const svgHeight = maxColumnHeight + 60\n\n const resourceStartTop = (svgHeight - totalResourceHeight) / 2\n const roleStartTop = (svgHeight - totalRoleHeight) / 2\n const groupStartTop = (svgHeight - totalGroupHeight) / 2\n const userStartTop = (svgHeight - totalUserHeight) / 2\n\n /**\n * Absolute y of an action row inside a resource card, used as a link endpoint.\n * リソースカード内のアクション行の絶対 y 座標 (リンクの端点)。\n *\n * @param resId Resource id. リソース ID。\n * @param action Action row to locate. 位置を求めるアクション。\n * @returns The y coordinate in SVG units. SVG 座標系の y 値。\n */\n const getResourceActionCenterY = (resId: number, action: string) => {\n const info = resourceYMap.get(resId)\n if (!info) return svgHeight / 2\n const relativeActY = info.actionYMap.get(action) ?? info.height / 2\n return resourceStartTop + relativeActY\n }\n\n /**\n * Absolute y of a role card's centre in the role column.\n * ロール列における当該ロールカード中心の絶対 y 座標。\n *\n * @param roleIndex Index within the visible roles. 表示中ロール内での位置。\n * @returns The y coordinate in SVG units. SVG 座標系の y 値。\n */\n const getRoleCenterY = (roleIndex: number) => {\n return roleStartTop + roleIndex * stdItemHeight + stdCardHeight / 2\n }\n\n /**\n * Absolute y of a group card's centre in the group column.\n * グループ列における当該グループカード中心の絶対 y 座標。\n *\n * @param groupIndex Index within the visible groups. 表示中グループ内での位置。\n * @returns The y coordinate in SVG units. SVG 座標系の y 値。\n */\n const getGroupCenterY = (groupIndex: number) => {\n return groupStartTop + groupIndex * stdItemHeight + stdCardHeight / 2\n }\n\n /**\n * Absolute y of a user card's centre in the user column.\n * ユーザー列における当該ユーザーカード中心の絶対 y 座標。\n *\n * @param userIndex Index within the visible users. 表示中ユーザー内での位置。\n * @returns The y coordinate in SVG units. SVG 座標系の y 値。\n */\n const getUserCenterY = (userIndex: number) => {\n return userStartTop + userIndex * stdItemHeight + stdCardHeight / 2\n }\n\n const resourceX = 140\n const roleX = 420\n const groupX = 700\n const userX = 980\n\n const activeNode = hoveredNode || selectedNode\n\n /**\n * Whether a user stays highlighted under the hovered / selected node.\n * ホバー・選択中のノードのもとで当該ユーザーを強調表示し続けるかどうかの判定。\n *\n * @param uId User id. ユーザー ID。\n * @returns `true` when the user is reachable from the active node. 到達可能なら `true`。\n */\n const isUserActive = (uId: number) => {\n if (!activeNode) return true\n if (activeNode.type === \"user\") return activeNode.id === uId\n if (activeNode.type === \"role\") return assignments.some((a) => a.userId === uId && a.roleId === activeNode.id)\n if (activeNode.type === \"group\") return false\n if (activeNode.type === \"resource\") {\n const roleIds = grants.filter((g) => g.resourceId === activeNode.id).map((g) => g.roleId)\n return assignments.some((a) => a.userId === uId && roleIds.includes(a.roleId))\n }\n return true\n }\n\n /**\n * Whether a group stays highlighted under the hovered / selected node.\n * ホバー・選択中のノードのもとで当該グループを強調表示し続けるかどうかの判定。\n *\n * @param gId Group id. グループ ID。\n * @returns `true` when the group is reachable from the active node. 到達可能なら `true`。\n */\n const isGroupActive = (gId: number) => {\n if (!activeNode) return true\n if (activeNode.type === \"group\") return activeNode.id === gId\n if (activeNode.type === \"role\") return groupRoles.some((gr) => gr.groupId === gId && gr.roleId === activeNode.id)\n if (activeNode.type === \"user\") return false\n if (activeNode.type === \"resource\") {\n const roleIds = grants.filter((g) => g.resourceId === activeNode.id).map((g) => g.roleId)\n return groupRoles.some((gr) => gr.groupId === gId && roleIds.includes(gr.roleId))\n }\n return true\n }\n\n /**\n * Whether a role stays highlighted under the hovered / selected node.\n * ホバー・選択中のノードのもとで当該ロールを強調表示し続けるかどうかの判定。\n *\n * @param rId Role id. ロール ID。\n * @returns `true` when the role is reachable from the active node. 到達可能なら `true`。\n */\n const isRoleActive = (rId: number) => {\n if (!activeNode) return true\n if (activeNode.type === \"role\") return activeNode.id === rId\n if (activeNode.type === \"user\") return assignments.some((a) => a.userId === activeNode.id && a.roleId === rId)\n if (activeNode.type === \"group\") return groupRoles.some((gr) => gr.groupId === activeNode.id && gr.roleId === rId)\n if (activeNode.type === \"resource\") return grants.some((g) => g.resourceId === activeNode.id && g.roleId === rId)\n return true\n }\n\n /**\n * Whether a resource stays highlighted under the hovered / selected node.\n * ホバー・選択中のノードのもとで当該リソースを強調表示し続けるかどうかの判定。\n *\n * @param resId Resource id. リソース ID。\n * @returns `true` when the resource is reachable from the active node. 到達可能なら `true`。\n */\n const isResourceActive = (resId: number) => {\n if (!activeNode) return true\n if (activeNode.type === \"resource\") return activeNode.id === resId\n if (activeNode.type === \"role\") return grants.some((g) => g.roleId === activeNode.id && g.resourceId === resId)\n if (activeNode.type === \"group\") {\n const groupRoleIds = groupRoles.filter((gr) => gr.groupId === activeNode.id).map((gr) => gr.roleId)\n return grants.some((g) => groupRoleIds.includes(g.roleId) && g.resourceId === resId)\n }\n if (activeNode.type === \"user\") {\n const userRoleIds = assignments.filter((a) => a.userId === activeNode.id).map((a) => a.roleId)\n return grants.some((g) => userRoleIds.includes(g.roleId) && g.resourceId === resId)\n }\n return true\n }\n\n /**\n * Whether the user → role edge stays highlighted under the hovered / selected node.\n * ホバー・選択中のノードのもとで、ユーザー → ロールの辺を強調表示し続けるかどうかの判定。\n *\n * @param uId User id. ユーザー ID。\n * @param rId Role id. ロール ID。\n * @returns `true` when the edge lies on a path from the active node. 経路上にあれば `true`。\n */\n const isUserRoleLinkActive = (uId: number, rId: number) => {\n if (!activeNode) return true\n if (activeNode.type === \"user\") return activeNode.id === uId && assignments.some((a) => a.userId === uId && a.roleId === rId)\n if (activeNode.type === \"role\") return activeNode.id === rId && assignments.some((a) => a.userId === uId && a.roleId === rId)\n if (activeNode.type === \"resource\") {\n const roleIds = grants.filter((g) => g.resourceId === activeNode.id).map((g) => g.roleId)\n return roleIds.includes(rId) && assignments.some((a) => a.userId === uId && a.roleId === rId)\n }\n if (activeNode.type === \"group\") return false\n return true\n }\n\n /**\n * Whether the group → role edge stays highlighted under the hovered / selected node.\n * ホバー・選択中のノードのもとで、グループ → ロールの辺を強調表示し続けるかどうかの判定。\n *\n * @param gId Group id. グループ ID。\n * @param rId Role id. ロール ID。\n * @returns `true` when the edge lies on a path from the active node. 経路上にあれば `true`。\n */\n const isGroupRoleLinkActive = (gId: number, rId: number) => {\n if (!activeNode) return true\n if (activeNode.type === \"group\") return activeNode.id === gId && groupRoles.some((gr) => gr.groupId === gId && gr.roleId === rId)\n if (activeNode.type === \"role\") return activeNode.id === rId && groupRoles.some((gr) => gr.groupId === gId && gr.roleId === rId)\n if (activeNode.type === \"resource\") {\n const roleIds = grants.filter((g) => g.resourceId === activeNode.id).map((g) => g.roleId)\n return roleIds.includes(rId) && groupRoles.some((gr) => gr.groupId === gId && gr.roleId === rId)\n }\n if (activeNode.type === \"user\") return false\n return true\n }\n\n /**\n * Whether the role → resource edge stays highlighted under the hovered / selected node.\n * ホバー・選択中のノードのもとで、ロール → リソースの辺を強調表示し続けるかどうかの判定。\n *\n * @param rId Role id. ロール ID。\n * @param resId Resource id. リソース ID。\n * @returns `true` when the edge lies on a path from the active node. 経路上にあれば `true`。\n */\n const isRoleResourceLinkActive = (rId: number, resId: number) => {\n if (!activeNode) return true\n if (activeNode.type === \"role\") return activeNode.id === rId && grants.some((g) => g.roleId === rId && g.resourceId === resId)\n if (activeNode.type === \"resource\") return activeNode.id === resId && grants.some((g) => g.roleId === rId && g.resourceId === resId)\n if (activeNode.type === \"group\") {\n const groupRoleIds = groupRoles.filter((gr) => gr.groupId === activeNode.id).map((gr) => gr.roleId)\n return groupRoleIds.includes(rId) && grants.some((g) => g.roleId === rId && g.resourceId === resId)\n }\n if (activeNode.type === \"user\") {\n const userRoleIds = assignments.filter((a) => a.userId === activeNode.id).map((a) => a.roleId)\n return userRoleIds.includes(rId) && grants.some((g) => g.roleId === rId && g.resourceId === resId)\n }\n return true\n }\n\n const userRoleLinks: Array<{ uIdx: number; rIdx: number; active: boolean; key: string; offset: number }> = []\n assignments.forEach((a) => {\n const uIdx = activeUsers.findIndex((u) => u.id === a.userId)\n const rIdx = activeRoles.findIndex((r) => r.id === a.roleId)\n if (uIdx !== -1 && rIdx !== -1) {\n const roleAssignments = assignments.filter((item) => item.roleId === a.roleId)\n const assignIdx = roleAssignments.findIndex((item) => item.userId === a.userId && item.roleId === a.roleId)\n const totalAssigns = roleAssignments.length\n const offset = totalAssigns > 1 ? Math.min(Math.max((assignIdx - (totalAssigns - 1) / 2) * 4, -16), 16) : 0\n\n userRoleLinks.push({\n uIdx,\n rIdx,\n active: isUserRoleLinkActive(a.userId, a.roleId),\n key: `ur-${a.userId}-${a.roleId}`,\n offset,\n })\n }\n })\n\n const groupRoleLinks: Array<{ gIdx: number; rIdx: number; active: boolean; key: string; offset: number }> = []\n groupRoles.forEach((gr) => {\n const gIdx = activeGroups.findIndex((g) => g.id === gr.groupId)\n const rIdx = activeRoles.findIndex((r) => r.id === gr.roleId)\n if (gIdx !== -1 && rIdx !== -1) {\n const gRoleAssigns = groupRoles.filter((item) => item.roleId === gr.roleId)\n const assignIdx = gRoleAssigns.findIndex((item) => item.groupId === gr.groupId && item.roleId === gr.roleId)\n const totalAssigns = gRoleAssigns.length\n const offset = totalAssigns > 1 ? Math.min(Math.max((assignIdx - (totalAssigns - 1) / 2) * 4, -16), 16) : 0\n\n groupRoleLinks.push({\n gIdx,\n rIdx,\n active: isGroupRoleLinkActive(gr.groupId, gr.roleId),\n key: `gr-${gr.groupId}-${gr.roleId}`,\n offset,\n })\n }\n })\n\n const roleResourceLinks: Array<{ rIdx: number; resIdx: number; active: boolean; key: string; action: string; color: string; offset: number }> = []\n grants.forEach((g) => {\n const rIdx = activeRoles.findIndex((r) => r.id === g.roleId)\n const resIdx = activeResources.findIndex((res) => res.id === g.resourceId)\n if (rIdx !== -1 && resIdx !== -1) {\n const sameResourceActionGrants = grants.filter((item) => item.resourceId === g.resourceId && item.action === g.action)\n const actionRoleIdx = sameResourceActionGrants.findIndex((item) => item.id === g.id)\n const totalSameGrants = sameResourceActionGrants.length\n const offset = totalSameGrants > 1 ? Math.min(Math.max((actionRoleIdx - (totalSameGrants - 1) / 2) * 4, -12), 12) : 0\n\n const colorConf = ACTION_COLORS[g.action]\n const color = colorConf ? colorConf.text : \"#6366f1\"\n\n roleResourceLinks.push({\n rIdx,\n resIdx,\n active: isRoleResourceLinkActive(g.roleId, g.resourceId),\n key: `rr-grant-${g.id}`,\n action: g.action,\n color,\n offset,\n })\n }\n })\n\n return (\n <div data-testid=\"authz-topology-map\" style={{ width: \"100%\", overflowX: \"auto\", backgroundColor: \"#ffffff\", borderRadius: \"10px\", border: \"1px solid #e2e8f0\", padding: \"1rem\", boxSizing: \"border-box\" }}>\n <div style={{ position: \"relative\", minWidth: \"1160px\", height: `${svgHeight}px` }}>\n <svg style={{ position: \"absolute\", top: 0, left: 0, width: \"100%\", height: \"100%\", pointerEvents: \"none\", zIndex: 1 }}>\n <title>Topology Connections</title>\n\n {roleResourceLinks.map((link) => {\n const grantItem = grants.find((g) => `rr-grant-${g.id}` === link.key)\n if (!grantItem) return null\n\n const y1 = getResourceActionCenterY(grantItem.resourceId, grantItem.action) + link.offset\n const y2 = getRoleCenterY(link.rIdx)\n const cX1 = resourceX + 135\n const cX2 = roleX - 135\n return (\n <path\n key={link.key}\n d={`M ${resourceX + 90} ${y1} C ${cX1} ${y1}, ${cX2} ${y2}, ${roleX - 90} ${y2}`}\n fill=\"none\"\n stroke={link.active ? link.color : \"#cbd5e1\"}\n strokeWidth={link.active && activeNode ? 3 : 2}\n strokeOpacity={link.active ? (activeNode ? 1.0 : 0.75) : 0.12}\n style={{ transition: \"all 0.2s ease\" }}\n />\n )\n })}\n\n {groupRoleLinks.map((link) => {\n const y1 = getRoleCenterY(link.rIdx) + link.offset\n const y2 = getGroupCenterY(link.gIdx)\n const cX1 = roleX + 135\n const cX2 = groupX - 135\n return (\n <path\n key={link.key}\n data-testid={`topo-link-group-role-${link.key}`}\n data-active={link.active ? \"true\" : \"false\"}\n d={`M ${roleX + 90} ${y1} C ${cX1} ${y1}, ${cX2} ${y2}, ${groupX - 90} ${y2}`}\n fill=\"none\"\n stroke={link.active ? \"#8b5cf6\" : \"#cbd5e1\"}\n strokeWidth={link.active && activeNode ? 3 : 2}\n strokeDasharray=\"4 2\"\n strokeOpacity={link.active ? (activeNode ? 1.0 : 0.75) : 0.12}\n style={{ transition: \"all 0.2s ease\" }}\n />\n )\n })}\n\n {userRoleLinks.map((link) => {\n const y1 = getRoleCenterY(link.rIdx) + link.offset\n const y2 = getUserCenterY(link.uIdx)\n const cX1 = roleX + 135\n const cX2 = userX - 135\n return (\n <path\n key={link.key}\n data-testid={`topo-link-user-role-${link.key}`}\n data-active={link.active ? \"true\" : \"false\"}\n d={`M ${roleX + 90} ${y1} C ${cX1} ${y1}, ${cX2} ${y2}, ${userX - 90} ${y2}`}\n fill=\"none\"\n stroke={link.active ? \"#4f46e5\" : \"#cbd5e1\"}\n strokeWidth={link.active && activeNode ? 3 : 2}\n strokeOpacity={link.active ? (activeNode ? 1.0 : 0.75) : 0.12}\n style={{ transition: \"all 0.2s ease\" }}\n />\n )\n })}\n </svg>\n\n <div style={{ position: \"relative\", zIndex: 2, width: \"100%\", height: \"100%\" }}>\n {activeResources.map((res) => {\n const isSelected = selectedNode?.type === \"resource\" && selectedNode.id === res.id\n const active = isResourceActive(res.id)\n const cardInfo = resourceYMap.get(res.id)\n if (!cardInfo) return null\n\n const cardTop = resourceStartTop + cardInfo.top\n const actions = getResourceActions(res.id)\n\n return (\n <div\n key={res.id}\n style={{\n position: \"absolute\",\n left: `${resourceX - 90}px`,\n top: `${cardTop}px`,\n width: \"180px\",\n height: `${cardInfo.height}px`,\n borderRadius: \"8px\",\n border: isSelected ? \"2px solid #10b981\" : \"1px solid #a7f3d0\",\n backgroundColor: isSelected ? \"#d1fae5\" : active ? \"#ffffff\" : \"#f8fafc\",\n opacity: active ? 1 : 0.35,\n boxShadow: isSelected ? \"0 4px 12px rgba(16, 185, 129, 0.2)\" : \"0 1px 3px rgba(0,0,0,0.05)\",\n transition: \"all 0.15s ease\",\n padding: \"8px\",\n boxSizing: \"border-box\",\n display: \"flex\",\n flexDirection: \"column\",\n }}>\n <button\n type=\"button\"\n onClick={() => onNodeClick(\"resource\", res.id)}\n onMouseEnter={() => setHoveredNode({ type: \"resource\", id: res.id })}\n onMouseLeave={() => setHoveredNode(null)}\n data-testid={`topo-node-resource-${res.id}`}\n aria-label={`リソース: ${res.name}`}\n aria-pressed={isSelected}\n style={{\n background: \"none\",\n border: \"none\",\n padding: 0,\n margin: 0,\n cursor: \"pointer\",\n textAlign: \"left\",\n width: \"100%\",\n display: \"flex\",\n alignItems: \"center\",\n gap: \"6px\",\n marginBottom: \"6px\",\n height: \"18px\",\n }}>\n <span style={{ fontSize: \"1rem\", flexShrink: 0 }}>🔑</span>\n <span style={{ fontWeight: 600, fontSize: \"0.78rem\", color: \"#047857\", overflow: \"hidden\", textOverflow: \"ellipsis\", whiteSpace: \"nowrap\" }}>{res.name}</span>\n </button>\n\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: \"2px\" }}>\n {actions.map((action) => {\n if (action === \"none\") {\n return (\n <div key=\"none\" style={{ fontSize: \"0.68rem\", color: \"#94a3b8\", height: \"22px\", display: \"flex\", alignItems: \"center\", padding: \"0 6px\" }}>\n 権限設定なし\n </div>\n )\n }\n\n const colorConf = ACTION_COLORS[action] ?? { bg: \"#ecfdf5\", text: \"#047857\", border: \"#a7f3d0\" }\n return (\n <div\n key={action}\n style={{\n height: \"22px\",\n boxSizing: \"border-box\",\n padding: \"0 8px\",\n borderRadius: \"4px\",\n backgroundColor: colorConf.bg,\n color: colorConf.text,\n border: `1px solid ${colorConf.border}`,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"space-between\",\n fontSize: \"0.68rem\",\n fontWeight: 600,\n }}>\n <span>{action}</span>\n <span style={{ fontSize: \"0.6rem\", opacity: 0.6 }}>●</span>\n </div>\n )\n })}\n </div>\n </div>\n )\n })}\n\n {activeRoles.map((r, i) => {\n const isSelected = selectedNode?.type === \"role\" && selectedNode.id === r.id\n const active = isRoleActive(r.id)\n const y = getRoleCenterY(i)\n return (\n <button\n type=\"button\"\n key={r.id}\n onClick={() => onNodeClick(\"role\", r.id)}\n onMouseEnter={() => setHoveredNode({ type: \"role\", id: r.id })}\n onMouseLeave={() => setHoveredNode(null)}\n data-testid={`topo-node-role-${r.id}`}\n aria-label={`ロール: ${r.name}`}\n aria-pressed={isSelected}\n style={{\n position: \"absolute\",\n left: `${roleX - 90}px`,\n top: `${y - 26}px`,\n width: \"180px\",\n height: \"52px\",\n borderRadius: \"8px\",\n border: isSelected ? \"2px solid #6366f1\" : \"1px solid #c7d2fe\",\n backgroundColor: isSelected ? \"#e0e7ff\" : active ? \"#ffffff\" : \"#f8fafc\",\n opacity: active ? 1 : 0.35,\n cursor: \"pointer\",\n display: \"flex\",\n alignItems: \"center\",\n padding: \"0 10px\",\n gap: \"8px\",\n boxShadow: isSelected ? \"0 4px 12px rgba(99, 102, 241, 0.2)\" : \"0 1px 3px rgba(0,0,0,0.05)\",\n transition: \"all 0.15s ease\",\n textAlign: \"left\",\n boxSizing: \"border-box\",\n }}>\n <span style={{ fontSize: \"1.1rem\", flexShrink: 0 }}>🛡️</span>\n <div style={{ overflow: \"hidden\", textOverflow: \"ellipsis\", whiteSpace: \"nowrap\", flex: 1, minWidth: 0 }}>\n <div style={{ fontWeight: 600, fontSize: \"0.78rem\", color: \"#4338ca\", textOverflow: \"ellipsis\", overflow: \"hidden\" }}>{r.name}</div>\n <div style={{ fontSize: \"0.68rem\", color: \"#64748b\", fontFamily: \"monospace\", textOverflow: \"ellipsis\", overflow: \"hidden\" }}>{r.roleKey}</div>\n </div>\n </button>\n )\n })}\n\n {activeGroups.map((g, i) => {\n const isSelected = selectedNode?.type === \"group\" && selectedNode.id === g.id\n const active = isGroupActive(g.id)\n const y = getGroupCenterY(i)\n return (\n <button\n type=\"button\"\n key={g.id}\n onClick={() => onNodeClick(\"group\", g.id)}\n onMouseEnter={() => setHoveredNode({ type: \"group\", id: g.id })}\n onMouseLeave={() => setHoveredNode(null)}\n data-testid={`topo-node-group-${g.id}`}\n aria-label={`グループ: ${g.name}`}\n aria-pressed={isSelected}\n style={{\n position: \"absolute\",\n left: `${groupX - 90}px`,\n top: `${y - 26}px`,\n width: \"180px\",\n height: \"52px\",\n borderRadius: \"8px\",\n border: isSelected ? \"2px solid #8b5cf6\" : \"1px solid #ddd6fe\",\n backgroundColor: isSelected ? \"#f3e8ff\" : active ? \"#ffffff\" : \"#f8fafc\",\n opacity: active ? 1 : 0.35,\n cursor: \"pointer\",\n display: \"flex\",\n alignItems: \"center\",\n padding: \"0 10px\",\n gap: \"8px\",\n boxShadow: isSelected ? \"0 4px 12px rgba(139, 92, 246, 0.2)\" : \"0 1px 3px rgba(0,0,0,0.05)\",\n transition: \"all 0.15s ease\",\n textAlign: \"left\",\n boxSizing: \"border-box\",\n }}>\n <span style={{ fontSize: \"1.1rem\", flexShrink: 0 }}>👥</span>\n <div style={{ overflow: \"hidden\", textOverflow: \"ellipsis\", whiteSpace: \"nowrap\", flex: 1, minWidth: 0 }}>\n <div style={{ fontWeight: 600, fontSize: \"0.78rem\", color: \"#6d28d9\", textOverflow: \"ellipsis\", overflow: \"hidden\" }}>{g.name}</div>\n <div style={{ fontSize: \"0.68rem\", color: \"#64748b\", fontFamily: \"monospace\", textOverflow: \"ellipsis\", overflow: \"hidden\" }}>{g.groupKey}</div>\n </div>\n </button>\n )\n })}\n\n {activeUsers.map((u, i) => {\n const isSelected = selectedNode?.type === \"user\" && selectedNode.id === u.id\n const active = isUserActive(u.id)\n const y = getUserCenterY(i)\n return (\n <button\n type=\"button\"\n key={u.id}\n onClick={() => onNodeClick(\"user\", u.id)}\n onMouseEnter={() => setHoveredNode({ type: \"user\", id: u.id })}\n onMouseLeave={() => setHoveredNode(null)}\n data-testid={`topo-node-user-${u.id}`}\n aria-label={`ユーザー: ${u.displayName}`}\n aria-pressed={isSelected}\n style={{\n position: \"absolute\",\n left: `${userX - 90}px`,\n top: `${y - 26}px`,\n width: \"180px\",\n height: \"52px\",\n borderRadius: \"8px\",\n border: isSelected ? \"2px solid #0284c7\" : \"1px solid #bae6fd\",\n backgroundColor: isSelected ? \"#e0f2fe\" : active ? \"#ffffff\" : \"#f8fafc\",\n opacity: active ? 1 : 0.35,\n cursor: \"pointer\",\n display: \"flex\",\n alignItems: \"center\",\n padding: \"0 10px\",\n gap: \"8px\",\n boxShadow: isSelected ? \"0 4px 12px rgba(2, 132, 199, 0.2)\" : \"0 1px 3px rgba(0,0,0,0.05)\",\n transition: \"all 0.15s ease\",\n textAlign: \"left\",\n boxSizing: \"border-box\",\n }}>\n <span style={{ fontSize: \"1.1rem\", flexShrink: 0 }}>👤</span>\n <div style={{ overflow: \"hidden\", textOverflow: \"ellipsis\", whiteSpace: \"nowrap\", flex: 1, minWidth: 0 }}>\n <div style={{ fontWeight: 600, fontSize: \"0.78rem\", color: \"#0369a1\", textOverflow: \"ellipsis\", overflow: \"hidden\" }}>{u.displayName}</div>\n <div style={{ fontSize: \"0.68rem\", color: \"#64748b\", textOverflow: \"ellipsis\", overflow: \"hidden\" }}>{u.email}</div>\n </div>\n </button>\n )\n })}\n </div>\n </div>\n </div>\n )\n}\n\n/**\n * Graph tab: the authorization relations of the tenant, as a topology map or as linked columns.\n * グラフタブ。当該テナントの認可関係を、トポロジ図または連動カラム表示で俯瞰するコンポーネント。\n *\n * Reads {@link AuthzGraphData}. Selecting a node filters the other three columns to what that node\n * actually reaches, and the search box narrows every column at once.\n * {@link AuthzGraphData} を読む形 (ノード選択で他 3 カラムを到達範囲に絞り、検索は全カラムへ同時適用)。\n *\n * @returns The graph tab. グラフタブ。\n */\nexport function AuthzGraphView() {\n const { users, roles, resources, grants, assignments, groupRoles = [], groups = [] } = useLoaderData() as AuthzGraphData\n const [selectedNode, setSelectedNode] = useState<{ type: AuthzNodeType; id: number } | null>(null)\n const [searchQuery, setSearchQuery] = useState(\"\")\n const [viewMode, setViewMode] = useState<\"topology\" | \"columns\">(\"topology\")\n\n const roleMap = new Map(roles.map((r) => [r.id, r]))\n const resourceMap = new Map(resources.map((r) => [r.id, r]))\n const groupMap = new Map(groups.map((g) => [g.id, g]))\n const userMap = new Map(users.map((u) => [u.id, u]))\n\n /**\n * Whether a user is related to the selected node, in the graph tab's column view.\n * グラフタブのカラム表示において、当該ユーザーが選択ノードと関係するかどうかの判定。\n *\n * @param userId User id. ユーザー ID。\n * @returns `true` when nothing is selected, or the user is reachable. 未選択、または到達可能なら `true`。\n */\n const isUserRelated = (userId: number): boolean => {\n if (!selectedNode) return true\n if (selectedNode.type === \"user\") return selectedNode.id === userId\n if (selectedNode.type === \"role\") return assignments.some((a) => a.userId === userId && a.roleId === selectedNode.id)\n if (selectedNode.type === \"group\") return false\n if (selectedNode.type === \"resource\") {\n const roleIdsForResource = grants.filter((g) => g.resourceId === selectedNode.id).map((g) => g.roleId)\n return assignments.some((a) => a.userId === userId && roleIdsForResource.includes(a.roleId))\n }\n return true\n }\n\n /**\n * Whether a group is related to the selected node, in the graph tab's column view.\n * グラフタブのカラム表示において、当該グループが選択ノードと関係するかどうかの判定。\n *\n * @param groupId Group id. グループ ID。\n * @returns `true` when nothing is selected, or the group is reachable. 未選択、または到達可能なら `true`。\n */\n const isGroupRelated = (groupId: number): boolean => {\n if (!selectedNode) return true\n if (selectedNode.type === \"group\") return selectedNode.id === groupId\n if (selectedNode.type === \"role\") return groupRoles.some((gr) => gr.groupId === groupId && gr.roleId === selectedNode.id)\n if (selectedNode.type === \"user\") return false\n if (selectedNode.type === \"resource\") {\n const roleIdsForResource = grants.filter((g) => g.resourceId === selectedNode.id).map((g) => g.roleId)\n return groupRoles.some((gr) => gr.groupId === groupId && roleIdsForResource.includes(gr.roleId))\n }\n return true\n }\n\n /**\n * Whether a role is related to the selected node, in the graph tab's column view.\n * グラフタブのカラム表示において、当該ロールが選択ノードと関係するかどうかの判定。\n *\n * @param roleId Role id. ロール ID。\n * @returns `true` when nothing is selected, or the role is reachable. 未選択、または到達可能なら `true`。\n */\n const isRoleRelated = (roleId: number): boolean => {\n if (!selectedNode) return true\n if (selectedNode.type === \"role\") return selectedNode.id === roleId\n if (selectedNode.type === \"user\") return assignments.some((a) => a.userId === selectedNode.id && a.roleId === roleId)\n if (selectedNode.type === \"group\") return groupRoles.some((gr) => gr.groupId === selectedNode.id && gr.roleId === roleId)\n if (selectedNode.type === \"resource\") return grants.some((g) => g.resourceId === selectedNode.id && g.roleId === roleId)\n return true\n }\n\n /**\n * Whether a resource is related to the selected node, in the graph tab's column view.\n * グラフタブのカラム表示において、当該リソースが選択ノードと関係するかどうかの判定。\n *\n * @param resourceId Resource id. リソース ID。\n * @returns `true` when nothing is selected, or the resource is reachable. 未選択、または到達可能なら `true`。\n */\n const isResourceRelated = (resourceId: number): boolean => {\n if (!selectedNode) return true\n if (selectedNode.type === \"resource\") return selectedNode.id === resourceId\n if (selectedNode.type === \"role\") return grants.some((g) => g.roleId === selectedNode.id && g.resourceId === resourceId)\n if (selectedNode.type === \"group\") {\n const groupRoleIds = groupRoles.filter((gr) => gr.groupId === selectedNode.id).map((gr) => gr.roleId)\n return grants.some((g) => groupRoleIds.includes(g.roleId) && g.resourceId === resourceId)\n }\n if (selectedNode.type === \"user\") {\n const userRoleIds = assignments.filter((a) => a.userId === selectedNode.id).map((a) => a.roleId)\n return grants.some((g) => userRoleIds.includes(g.roleId) && g.resourceId === resourceId)\n }\n return true\n }\n\n const query = searchQuery.trim().toLowerCase()\n const filteredUsers = users.filter((u) => !query || u.displayName.toLowerCase().includes(query) || u.email?.toLowerCase().includes(query))\n const filteredGroups = groups.filter((g) => !query || g.name.toLowerCase().includes(query) || g.groupKey.toLowerCase().includes(query))\n const filteredRoles = roles.filter((r) => !query || r.name.toLowerCase().includes(query) || r.roleKey.toLowerCase().includes(query))\n const filteredResources = resources.filter((r) => !query || r.name.toLowerCase().includes(query) || r.resourceKey.toLowerCase().includes(query) || r.appKey.toLowerCase().includes(query))\n\n const selectedUserObj = selectedNode?.type === \"user\" ? userMap.get(selectedNode.id) : null\n const selectedGroupObj = selectedNode?.type === \"group\" ? groupMap.get(selectedNode.id) : null\n const selectedRoleObj = selectedNode?.type === \"role\" ? roleMap.get(selectedNode.id) : null\n const selectedResourceObj = selectedNode?.type === \"resource\" ? resourceMap.get(selectedNode.id) : null\n\n /**\n * Toggle the graph selection: clicking the selected node again clears the selection.\n * グラフの選択を切り替える処理 (選択中のノードを再クリックすると選択解除)。\n *\n * @param type Node kind that was clicked. クリックされたノード種別。\n * @param id Node id that was clicked. クリックされたノード ID。\n * @returns Nothing. 戻り値なし。\n */\n const handleNodeClick = (type: AuthzNodeType, id: number) => {\n if (selectedNode && selectedNode.type === type && selectedNode.id === id) {\n setSelectedNode(null)\n } else {\n setSelectedNode({ type, id })\n }\n }\n\n return (\n <section data-testid=\"authz-graph-view\" style={{ display: \"flex\", flexDirection: \"column\", gap: \"1rem\", width: \"100%\" }}>\n <div style={{ display: \"flex\", justifyContent: \"space-between\", alignItems: \"center\", flexWrap: \"wrap\", gap: \"0.75rem\", backgroundColor: \"#f8fafc\", padding: \"0.75rem 1rem\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\", width: \"100%\", boxSizing: \"border-box\" }}>\n <div style={{ display: \"flex\", alignItems: \"center\", gap: \"0.75rem\", flexWrap: \"wrap\" }}>\n <div style={{ display: \"inline-flex\", borderRadius: \"6px\", border: \"1px solid #cbd5e1\", padding: \"2px\", backgroundColor: \"#ffffff\" }}>\n <button\n type=\"button\"\n onClick={() => setViewMode(\"topology\")}\n data-testid=\"switch-mode-topology\"\n style={{\n padding: \"3px 10px\",\n borderRadius: \"4px\",\n border: \"none\",\n cursor: \"pointer\",\n fontSize: \"0.75rem\",\n fontWeight: viewMode === \"topology\" ? 600 : 400,\n backgroundColor: viewMode === \"topology\" ? \"#eff6ff\" : \"transparent\",\n color: viewMode === \"topology\" ? \"#0284c7\" : \"#64748b\",\n boxShadow: viewMode === \"topology\" ? \"0 1px 2px rgba(0,0,0,0.08)\" : \"none\",\n }}>\n 🌐 トポロジマップ (Topology)\n </button>\n <button\n type=\"button\"\n onClick={() => setViewMode(\"columns\")}\n data-testid=\"switch-mode-columns\"\n style={{\n padding: \"3px 10px\",\n borderRadius: \"4px\",\n border: \"none\",\n cursor: \"pointer\",\n fontSize: \"0.75rem\",\n fontWeight: viewMode === \"columns\" ? 600 : 400,\n backgroundColor: viewMode === \"columns\" ? \"#eff6ff\" : \"transparent\",\n color: viewMode === \"columns\" ? \"#0284c7\" : \"#64748b\",\n boxShadow: viewMode === \"columns\" ? \"0 1px 2px rgba(0,0,0,0.08)\" : \"none\",\n }}>\n 📋 カラム一覧 (Columns)\n </button>\n </div>\n <div style={{ display: \"flex\", gap: \"0.4rem\", fontSize: \"0.75rem\", flexWrap: \"wrap\" }}>\n <span style={{ backgroundColor: \"#e0f2fe\", color: \"#0369a1\", padding: \"2px 8px\", borderRadius: \"12px\", fontWeight: 600 }}>👤 ユーザー: {users.length}</span>\n <span style={{ backgroundColor: \"#f3e8ff\", color: \"#6d28d9\", padding: \"2px 8px\", borderRadius: \"12px\", fontWeight: 600 }}>👥 グループ: {groups.length}</span>\n <span style={{ backgroundColor: \"#e0e7ff\", color: \"#4338ca\", padding: \"2px 8px\", borderRadius: \"12px\", fontWeight: 600 }}>🛡️ ロール: {roles.length}</span>\n <span style={{ backgroundColor: \"#d1fae5\", color: \"#047857\", padding: \"2px 8px\", borderRadius: \"12px\", fontWeight: 600 }}>🔑 リソース: {resources.length}</span>\n <span style={{ backgroundColor: \"#fef3c7\", color: \"#b45309\", padding: \"2px 8px\", borderRadius: \"12px\", fontWeight: 600 }}>⚡️ 権限設定: {grants.length}</span>\n </div>\n </div>\n <div style={{ display: \"flex\", alignItems: \"center\", gap: \"0.5rem\" }}>\n <input\n type=\"text\"\n placeholder=\"キーワードで絞り込み...\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n data-testid=\"graph-search-input\"\n style={{ padding: \"5px 10px\", borderRadius: \"6px\", border: \"1px solid #cbd5e1\", fontSize: \"0.8rem\", width: \"200px\" }}\n />\n </div>\n </div>\n\n {/* 常時高さ固定のステータスバー (高さ38px固定でレイアウトシフトを防止) */}\n <div\n style={{\n minHeight: \"38px\",\n height: \"38px\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"space-between\",\n backgroundColor: selectedNode ? \"#eff6ff\" : \"#f8fafc\",\n border: `1px solid ${selectedNode ? \"#bfdbfe\" : \"#e2e8f0\"}`,\n padding: \"0 0.8rem\",\n borderRadius: \"6px\",\n fontSize: \"0.8rem\",\n color: selectedNode ? \"#1e40af\" : \"#64748b\",\n width: \"100%\",\n boxSizing: \"border-box\",\n transition: \"all 0.15s ease\",\n }}>\n <div style={{ display: \"flex\", alignItems: \"center\", gap: \"0.4rem\", overflow: \"hidden\", textOverflow: \"ellipsis\", whiteSpace: \"nowrap\" }}>\n {selectedNode ? (\n <>\n <span>🎯</span>\n <strong>選択中のノード:</strong>\n <span>\n {selectedUserObj && `ユーザー「${selectedUserObj.displayName} (${selectedUserObj.email})」に関連するパスを表示中`}\n {selectedGroupObj && `グループ「${selectedGroupObj.name} (${selectedGroupObj.groupKey})」に関連するパスを表示中`}\n {selectedRoleObj && `ロール「${selectedRoleObj.name} (${selectedRoleObj.roleKey})」に関連するパスを表示中`}\n {selectedResourceObj && `リソース「${selectedResourceObj.name} (${selectedResourceObj.appKey}:${selectedResourceObj.resourceKey})」に関連するパスを表示中`}\n </span>\n </>\n ) : (\n <>\n <span>💡</span>\n <span>\n <strong>ヒント:</strong> ノード(リソース・ロール・グループ・ユーザー)をクリックすると、関連する全パスがハイライト表示されます\n </span>\n </>\n )}\n </div>\n {selectedNode && (\n <button\n type=\"button\"\n onClick={() => setSelectedNode(null)}\n data-testid=\"graph-reset-selection\"\n style={{\n padding: \"3px 8px\",\n borderRadius: \"4px\",\n border: \"1px solid #93c5fd\",\n backgroundColor: \"#ffffff\",\n fontSize: \"0.72rem\",\n cursor: \"pointer\",\n color: \"#1d4ed8\",\n fontWeight: 600,\n whiteSpace: \"nowrap\",\n boxShadow: \"0 1px 2px rgba(0,0,0,0.05)\",\n }}>\n ✕ 選択解除\n </button>\n )}\n </div>\n\n {viewMode === \"topology\" ? (\n <AuthzTopologyMap users={filteredUsers} roles={filteredRoles} resources={filteredResources} grants={grants} assignments={assignments} groupRoles={groupRoles} groups={filteredGroups} selectedNode={selectedNode} onNodeClick={handleNodeClick} />\n ) : (\n <div style={{ display: \"grid\", gridTemplateColumns: \"repeat(4, minmax(0, 1fr))\", gap: \"1rem\", alignItems: \"start\", width: \"100%\", boxSizing: \"border-box\" }}>\n {/* Column 1: Resources */}\n <div style={{ backgroundColor: \"#ffffff\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\", padding: \"0.75rem\" }}>\n <div style={{ fontWeight: 700, fontSize: \"0.85rem\", color: \"#047857\", marginBottom: \"0.6rem\", display: \"flex\", alignItems: \"center\", gap: \"0.3rem\", borderBottom: \"2px solid #a7f3d0\", paddingBottom: \"0.4rem\" }}>\n <span>🔑</span> リソース ({filteredResources.length})\n </div>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: \"0.5rem\", maxHeight: \"480px\", overflowY: \"auto\", paddingRight: \"4px\" }}>\n {filteredResources.length === 0 && <div style={{ fontSize: \"0.8rem\", color: \"#94a3b8\", textAlign: \"center\", padding: \"1rem\" }}>対象のリソースがありません</div>}\n {filteredResources.map((res) => {\n const isSelected = selectedNode?.type === \"resource\" && selectedNode.id === res.id\n const active = isResourceRelated(res.id)\n const resourceGrants = grants.filter((g) => g.resourceId === res.id)\n\n return (\n <button\n type=\"button\"\n key={res.id}\n onClick={() => handleNodeClick(\"resource\", res.id)}\n data-testid={`graph-node-resource-${res.id}`}\n style={{\n textAlign: \"left\",\n width: \"100%\",\n padding: \"0.6rem\",\n borderRadius: \"6px\",\n border: isSelected ? \"2px solid #10b981\" : \"1px solid #e2e8f0\",\n backgroundColor: isSelected ? \"#d1fae5\" : active ? \"#ffffff\" : \"#f8fafc\",\n opacity: active ? 1 : 0.4,\n cursor: \"pointer\",\n transition: \"all 0.15s ease\",\n boxShadow: isSelected ? \"0 2px 4px rgba(16, 185, 129, 0.15)\" : \"none\",\n }}>\n <div style={{ fontWeight: 600, fontSize: \"0.82rem\", color: \"#0f172a\" }}>{res.name}</div>\n <div style={{ fontSize: \"0.72rem\", color: \"#64748b\", fontFamily: \"monospace\" }}>\n {res.appKey}:{res.resourceKey}\n </div>\n <div style={{ display: \"flex\", flexWrap: \"wrap\", gap: \"0.25rem\", marginTop: \"0.4rem\" }}>\n {resourceGrants.map((g) => {\n const role = roleMap.get(g.roleId)\n const actStyle = ACTION_COLORS[g.action] ?? { bg: \"#f1f5f9\", text: \"#475569\", border: \"#cbd5e1\" }\n return (\n <span\n key={g.id}\n style={{\n fontSize: \"0.68rem\",\n padding: \"1px 6px\",\n borderRadius: \"4px\",\n backgroundColor: actStyle.bg,\n color: actStyle.text,\n border: `1px solid ${actStyle.border}`,\n }}>\n {role?.roleKey ?? `Role#${g.roleId}`}: <strong>{g.action}</strong>\n </span>\n )\n })}\n {resourceGrants.length === 0 && <span style={{ fontSize: \"0.68rem\", color: \"#94a3b8\" }}>権限設定なし</span>}\n </div>\n </button>\n )\n })}\n </div>\n </div>\n\n {/* Column 2: Roles */}\n <div style={{ backgroundColor: \"#ffffff\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\", padding: \"0.75rem\" }}>\n <div style={{ fontWeight: 700, fontSize: \"0.85rem\", color: \"#4338ca\", marginBottom: \"0.6rem\", display: \"flex\", alignItems: \"center\", gap: \"0.3rem\", borderBottom: \"2px solid #c7d2fe\", paddingBottom: \"0.4rem\" }}>\n <span>🛡️</span> ロール ({filteredRoles.length})\n </div>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: \"0.5rem\", maxHeight: \"480px\", overflowY: \"auto\", paddingRight: \"4px\" }}>\n {filteredRoles.length === 0 && <div style={{ fontSize: \"0.8rem\", color: \"#94a3b8\", textAlign: \"center\", padding: \"1rem\" }}>対象のロールがありません</div>}\n {filteredRoles.map((r) => {\n const isSelected = selectedNode?.type === \"role\" && selectedNode.id === r.id\n const active = isRoleRelated(r.id)\n const roleGrants = grants.filter((g) => g.roleId === r.id)\n const roleAssignments = assignments.filter((a) => a.roleId === r.id)\n const roleGroupAssignments = groupRoles.filter((gr) => gr.roleId === r.id)\n\n return (\n <button\n type=\"button\"\n key={r.id}\n onClick={() => handleNodeClick(\"role\", r.id)}\n data-testid={`graph-node-role-${r.id}`}\n style={{\n textAlign: \"left\",\n width: \"100%\",\n padding: \"0.6rem\",\n borderRadius: \"6px\",\n border: isSelected ? \"2px solid #6366f1\" : \"1px solid #e2e8f0\",\n backgroundColor: isSelected ? \"#e0e7ff\" : active ? \"#ffffff\" : \"#f8fafc\",\n opacity: active ? 1 : 0.4,\n cursor: \"pointer\",\n transition: \"all 0.15s ease\",\n boxShadow: isSelected ? \"0 2px 4px rgba(99, 102, 241, 0.15)\" : \"none\",\n }}>\n <div style={{ fontWeight: 600, fontSize: \"0.82rem\", color: \"#0f172a\" }}>{r.name}</div>\n <div style={{ fontSize: \"0.72rem\", color: \"#64748b\", fontFamily: \"monospace\" }}>{r.roleKey}</div>\n <div style={{ fontSize: \"0.7rem\", color: \"#475569\", marginTop: \"0.3rem\", display: \"flex\", gap: \"0.5rem\" }}>\n <span>権限: {roleGrants.length}</span>\n <span>グループ: {roleGroupAssignments.length}</span>\n <span>ユーザー: {roleAssignments.length}</span>\n </div>\n </button>\n )\n })}\n </div>\n </div>\n\n {/* Column 3: Groups */}\n <div style={{ backgroundColor: \"#ffffff\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\", padding: \"0.75rem\" }}>\n <div style={{ fontWeight: 700, fontSize: \"0.85rem\", color: \"#6d28d9\", marginBottom: \"0.6rem\", display: \"flex\", alignItems: \"center\", gap: \"0.3rem\", borderBottom: \"2px solid #ddd6fe\", paddingBottom: \"0.4rem\" }}>\n <span>👥</span> グループ ({filteredGroups.length})\n </div>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: \"0.5rem\", maxHeight: \"480px\", overflowY: \"auto\", paddingRight: \"4px\" }}>\n {filteredGroups.length === 0 && <div style={{ fontSize: \"0.8rem\", color: \"#94a3b8\", textAlign: \"center\", padding: \"1rem\" }}>対象のグループがありません</div>}\n {filteredGroups.map((g) => {\n const isSelected = selectedNode?.type === \"group\" && selectedNode.id === g.id\n const active = isGroupRelated(g.id)\n const assignedRoles = groupRoles.filter((gr) => gr.groupId === g.id)\n\n return (\n <button\n type=\"button\"\n key={g.id}\n onClick={() => handleNodeClick(\"group\", g.id)}\n data-testid={`graph-node-group-${g.id}`}\n style={{\n textAlign: \"left\",\n width: \"100%\",\n padding: \"0.6rem\",\n borderRadius: \"6px\",\n border: isSelected ? \"2px solid #8b5cf6\" : \"1px solid #e2e8f0\",\n backgroundColor: isSelected ? \"#f3e8ff\" : active ? \"#ffffff\" : \"#f8fafc\",\n opacity: active ? 1 : 0.4,\n cursor: \"pointer\",\n transition: \"all 0.15s ease\",\n boxShadow: isSelected ? \"0 2px 4px rgba(139, 92, 246, 0.15)\" : \"none\",\n }}>\n <div style={{ fontWeight: 600, fontSize: \"0.82rem\", color: \"#0f172a\" }}>{g.name}</div>\n <div style={{ fontSize: \"0.72rem\", color: \"#64748b\", fontFamily: \"monospace\" }}>{g.groupKey}</div>\n <div style={{ display: \"flex\", flexWrap: \"wrap\", gap: \"0.25rem\", marginTop: \"0.4rem\" }}>\n {assignedRoles.map((gr) => {\n const role = roleMap.get(gr.roleId)\n return (\n <span\n key={`${gr.groupId}-${gr.roleId}`}\n style={{\n fontSize: \"0.68rem\",\n padding: \"1px 6px\",\n borderRadius: \"4px\",\n backgroundColor: \"#f3e8ff\",\n color: \"#6d28d9\",\n border: \"1px solid #ddd6fe\",\n fontWeight: 600,\n }}>\n 🛡️ {role?.name ?? gr.roleKey}\n </span>\n )\n })}\n {assignedRoles.length === 0 && <span style={{ fontSize: \"0.68rem\", color: \"#94a3b8\" }}>ロール未割り当て</span>}\n </div>\n </button>\n )\n })}\n </div>\n </div>\n\n {/* Column 4: Users */}\n <div style={{ backgroundColor: \"#ffffff\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\", padding: \"0.75rem\" }}>\n <div style={{ fontWeight: 700, fontSize: \"0.85rem\", color: \"#0369a1\", marginBottom: \"0.6rem\", display: \"flex\", alignItems: \"center\", gap: \"0.3rem\", borderBottom: \"2px solid #bae6fd\", paddingBottom: \"0.4rem\" }}>\n <span>👤</span> ユーザー ({filteredUsers.length})\n </div>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: \"0.5rem\", maxHeight: \"480px\", overflowY: \"auto\", paddingRight: \"4px\" }}>\n {filteredUsers.length === 0 && <div style={{ fontSize: \"0.8rem\", color: \"#94a3b8\", textAlign: \"center\", padding: \"1rem\" }}>対象のユーザーがありません</div>}\n {filteredUsers.map((u) => {\n const isSelected = selectedNode?.type === \"user\" && selectedNode.id === u.id\n const active = isUserRelated(u.id)\n const userAssignments = assignments.filter((a) => a.userId === u.id)\n\n return (\n <button\n type=\"button\"\n key={u.id}\n onClick={() => handleNodeClick(\"user\", u.id)}\n data-testid={`graph-node-user-${u.id}`}\n style={{\n textAlign: \"left\",\n width: \"100%\",\n padding: \"0.6rem\",\n borderRadius: \"6px\",\n border: isSelected ? \"2px solid #0284c7\" : \"1px solid #e2e8f0\",\n backgroundColor: isSelected ? \"#e0f2fe\" : active ? \"#ffffff\" : \"#f8fafc\",\n opacity: active ? 1 : 0.4,\n cursor: \"pointer\",\n transition: \"all 0.15s ease\",\n boxShadow: isSelected ? \"0 2px 4px rgba(2, 132, 199, 0.15)\" : \"none\",\n }}>\n <div style={{ fontWeight: 600, fontSize: \"0.82rem\", color: \"#0f172a\" }}>{u.displayName}</div>\n <div style={{ fontSize: \"0.72rem\", color: \"#64748b\" }}>{u.email}</div>\n <div style={{ display: \"flex\", flexWrap: \"wrap\", gap: \"0.25rem\", marginTop: \"0.4rem\" }}>\n {userAssignments.map((a) => {\n const role = roleMap.get(a.roleId)\n return (\n <span\n key={`${a.userId}-${a.roleId}`}\n style={{\n fontSize: \"0.68rem\",\n padding: \"1px 6px\",\n borderRadius: \"4px\",\n backgroundColor: \"#e0f2fe\",\n color: \"#0369a1\",\n border: \"1px solid #bae6fd\",\n fontWeight: 600,\n }}>\n 🛡️ {role?.name ?? a.roleKey}\n </span>\n )\n })}\n {userAssignments.length === 0 && <span style={{ fontSize: \"0.68rem\", color: \"#94a3b8\" }}>直接ロールなし</span>}\n </div>\n </button>\n )\n })}\n </div>\n </div>\n </div>\n )}\n </section>\n )\n}\n\n// ============================ Single-file (splat) app view ============================\n\nconst SEGMENT_VIEWS: Record<string, () => React.ReactElement> = {\n roles: AuthzRolesView,\n resources: AuthzResourcesView,\n role_permissions: AuthzGrantsView,\n user_roles: AuthzUserRolesView,\n group_roles: AuthzGroupRolesView,\n graph: AuthzGraphView,\n}\n\nexport type AuthzAdminAppViewProps = {\n renderHeader?: (props: { title: string; annotation: React.ReactNode }) => React.ReactNode\n}\n\n/**\n * Render the whole admin UI from a single splat route (`authz/*`), without depending on any store.\n * スプラット (`authz/*`) 1 ルートで認可管理 UI 全体を描画するコンポーネント (store 非依存)。\n *\n * The loader's `segment` selects the tab; the shell and the tab read one merged loader payload\n * (the segment's rows plus `myPermissions` and `labels` at the top level).\n * loader が返す `segment` に応じてタブを選ぶ形 (外枠と各ビューは統合済みの同一 loader データを読む)。\n *\n * @param props Optional header renderer. 任意のヘッダ描画関数。\n * @returns The admin page for the requested segment. 要求セグメントに対応する管理画面。\n */\nexport function AuthzAdminAppView({ renderHeader }: AuthzAdminAppViewProps = {}) {\n const { segment } = useLoaderData() as { segment: string }\n const View = SEGMENT_VIEWS[segment] ?? AuthzRolesView\n return (\n <AuthzLayoutShell renderHeader={renderHeader}>\n <View />\n </AuthzLayoutShell>\n )\n}\n","/**\n * Multi-resource permission widget (inline pills or a popover card) for pages spanning several resources.\n * 複数リソースにまたがる画面向けの権限表示ウィジェット (インラインのタグ表示とポップオーバー表示)。\n */\nimport { type CSSProperties, useEffect, useRef, useState } from \"react\"\nimport { MyPermissionIndicator } from \"./MyPermissionIndicator\"\nimport type { PermissionView } from \"./server\"\n\nexport type MyPermissionGroupItem = {\n /** Expected or actual resource key */\n resourceKey?: string\n /** Human-readable resource display name (e.g. \"NI\", \"KT\", \"オリジナル\") */\n resourceName: string\n /** Permission view data for this resource */\n permission: PermissionView | null | undefined\n /** Expected resource key to detect mismatches with permission.resourceKey during development */\n expectedResourceKey?: string\n /** Nesting level for hierarchical rendering (0: root, 1: child, etc.) */\n level?: number\n /** Optional child items for hierarchical rendering */\n children?: MyPermissionGroupItem[]\n}\n\n/**\n * Flattens hierarchical items tree into a single array with computed nesting levels.\n * 木構造またはフラット配列のアイテムに level 属性を付与してフラット化する処理。\n */\nexport function flattenGroupItems(items: MyPermissionGroupItem[], currentLevel = 0): MyPermissionGroupItem[] {\n const result: MyPermissionGroupItem[] = []\n for (const item of items) {\n const itemLevel = item.level ?? currentLevel\n result.push({ ...item, level: itemLevel })\n if (item.children && item.children.length > 0) {\n result.push(...flattenGroupItems(item.children, itemLevel + 1))\n }\n }\n return result\n}\n\nexport type MyPermissionGroupStatusProps = {\n /** List of resources and their permissions */\n items: MyPermissionGroupItem[]\n /** Prefix label shown before group items or inside popover header. Default: \"あなたの権限\" */\n prefixLabel?: string\n /**\n * Display style variant:\n * - \"pills\": Inline parent label + compact status tags per item\n * - \"popover\": Compact single button trigger opening detailed popup card\n * Default: \"pills\"\n */\n variant?: \"pills\" | \"popover\"\n /**\n * Component size variant:\n * - \"2xs\": Ultra micro size for extremely dense UI\n * - \"xs\": Extra small size for compact headers\n * - \"sm\": Small size\n * - \"md\": Standard default size\n * - \"lg\": Larger size\n * Default: \"md\"\n */\n size?: \"2xs\" | \"xs\" | \"sm\" | \"md\" | \"lg\"\n labels?: {\n read?: string\n write?: string\n delete?: string\n noPermission?: string\n fullWrite?: string\n partialWrite?: string\n readOnly?: string\n partialRead?: string\n }\n className?: string\n style?: CSSProperties\n}\n\nconst SIZE_CONFIGS = {\n \"2xs\": {\n fontSize: \"0.6rem\",\n pillPadding: \"0.05rem 0.25rem\",\n pillGap: \"0.15rem\",\n wrapperGap: \"0.15rem\",\n triggerPadding: \"0.1rem 0.35rem\",\n triggerFontSize: \"0.62rem\",\n badgeFontSize: \"0.58rem\",\n },\n xs: {\n fontSize: \"0.65rem\",\n pillPadding: \"0.08rem 0.35rem\",\n pillGap: \"0.18rem\",\n wrapperGap: \"0.2rem\",\n triggerPadding: \"0.12rem 0.4rem\",\n triggerFontSize: \"0.68rem\",\n badgeFontSize: \"0.62rem\",\n },\n sm: {\n fontSize: \"0.72rem\",\n pillPadding: \"0.1rem 0.4rem\",\n pillGap: \"0.2rem\",\n wrapperGap: \"0.25rem\",\n triggerPadding: \"0.15rem 0.45rem\",\n triggerFontSize: \"0.72rem\",\n badgeFontSize: \"0.65rem\",\n },\n md: {\n fontSize: \"0.78rem\",\n pillPadding: \"0.15rem 0.5rem\",\n pillGap: \"0.3rem\",\n wrapperGap: \"0.35rem\",\n triggerPadding: \"0.2rem 0.6rem\",\n triggerFontSize: \"0.78rem\",\n badgeFontSize: \"0.7rem\",\n },\n lg: {\n fontSize: \"0.85rem\",\n pillPadding: \"0.25rem 0.65rem\",\n pillGap: \"0.4rem\",\n wrapperGap: \"0.45rem\",\n triggerPadding: \"0.25rem 0.75rem\",\n triggerFontSize: \"0.85rem\",\n badgeFontSize: \"0.75rem\",\n },\n} as const\n\n/**\n * Get human-readable summary text and style for a resource permission.\n * 権限状態から簡潔なサマリテキストとスタイルを判定 (カスタムラベル対応)。\n */\nexport function getPermissionSummary(\n permission: PermissionView | null | undefined,\n customLabels?: MyPermissionGroupStatusProps[\"labels\"],\n): {\n text: string\n color: string\n bgColor: string\n borderColor: string\n} {\n const txtNoPerm = customLabels?.noPermission ?? \"権限なし\"\n const txtFullWrite = customLabels?.fullWrite ?? \"編集可\"\n const txtPartialRead = customLabels?.partialRead ?? \"一部閲覧\"\n const txtReadOnly = customLabels?.readOnly ?? \"閲覧のみ\"\n\n if (!(permission && (permission.canRead || permission.canWrite || permission.canDelete))) {\n return { text: txtNoPerm, color: \"#64748b\", bgColor: \"#f1f5f9\", borderColor: \"#cbd5e1\" }\n }\n if (permission.canWrite) {\n return { text: txtFullWrite, color: \"#047857\", bgColor: \"#ecfdf5\", borderColor: \"#a7f3d0\" }\n }\n if (permission.canRead) {\n if (permission.readPartial) {\n return { text: txtPartialRead, color: \"#b45309\", bgColor: \"#fffbeb\", borderColor: \"#fde68a\" }\n }\n return { text: txtReadOnly, color: \"#1d4ed8\", bgColor: \"#eff6ff\", borderColor: \"#bfdbfe\" }\n }\n return { text: txtNoPerm, color: \"#64748b\", bgColor: \"#f1f5f9\", borderColor: \"#cbd5e1\" }\n}\n\n/**\n * Render compact status pill for a single resource item in \"pills\" mode.\n * 「pills」モード用: リソースごとのコンパクトなステータスタグを描画。\n */\nfunction GroupItemPill({ item, size = \"md\", labels }: { item: MyPermissionGroupItem; size?: \"2xs\" | \"xs\" | \"sm\" | \"md\" | \"lg\"; labels?: MyPermissionGroupStatusProps[\"labels\"] }) {\n const summary = getPermissionSummary(item.permission, labels)\n const resKey = item.permission?.resourceKey || item.resourceKey || item.expectedResourceKey\n const resName = item.resourceName || resKey\n const hasMismatch = Boolean(item.expectedResourceKey && item.permission?.resourceKey && item.permission.resourceKey !== item.expectedResourceKey)\n const cfg = SIZE_CONFIGS[size]\n\n const tooltipText = `対象: ${resName}${resKey ? ` (${resKey})` : \"\"}${item.permission ? ` | ${labels?.read ?? \"閲覧\"}:${item.permission.canRead ? \"○\" : \"×\"} ${labels?.write ?? \"編集\"}:${item.permission.canWrite ? \"○\" : \"×\"} ${labels?.delete ?? \"削除\"}:${item.permission.canDelete ? \"○\" : \"×\"}` : \"\"}`\n\n return (\n <span\n title={tooltipText}\n data-testid={`permission-pill-${item.resourceName}`}\n data-mismatch={hasMismatch ? \"true\" : \"false\"}\n style={{\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: cfg.pillGap,\n fontSize: cfg.fontSize,\n padding: cfg.pillPadding,\n borderRadius: \"9999px\",\n backgroundColor: hasMismatch ? \"#fee2e2\" : summary.bgColor,\n color: hasMismatch ? \"#991b1b\" : summary.color,\n border: `1px solid ${hasMismatch ? \"#fca5a5\" : summary.borderColor}`,\n fontWeight: 600,\n lineHeight: \"1.2\",\n }}>\n <span>{resName}:</span>\n <span>{summary.text}</span>\n {hasMismatch && <span title={`expected \"${item.expectedResourceKey}\", actual \"${item.permission?.resourceKey}\"`}>⚠️</span>}\n </span>\n )\n}\n\n/**\n * Multi-resource \"Your Permissions\" status component.\n * Supports inline compact pills (Proposal 1) and dropdown popover card (Proposal 2).\n * 複数リソースの権限状態を共通レイアウト(pills/popover)で綺麗に整列・表示するコンポーネント。\n */\nexport function MyPermissionGroupStatus({ items, prefixLabel = \"あなたの権限\", variant = \"pills\", size = \"md\", labels, className = \"authz-permission-group-status\", style }: MyPermissionGroupStatusProps) {\n const [isOpen, setIsOpen] = useState(false)\n const popoverRef = useRef<HTMLSpanElement>(null)\n const cfg = SIZE_CONFIGS[size]\n\n // 外側クリックまたは Escape キーでポップオーバーを閉じる\n useEffect(() => {\n if (variant !== \"popover\" || !isOpen) return\n\n /**\n * Close the popover when the click landed outside it.\n * ポップオーバーの外側がクリックされた場合に閉じる処理。\n *\n * @param event The document-level mouse event. ドキュメント全体のマウスイベント。\n * @returns Nothing. 戻り値なし。\n */\n function handleClickOutside(event: MouseEvent) {\n if (popoverRef.current && !popoverRef.current.contains(event.target as Node)) {\n setIsOpen(false)\n }\n }\n /**\n * Close the popover when Escape is pressed.\n * Escape キー押下でポップオーバーを閉じる処理。\n *\n * @param event The document-level keyboard event. ドキュメント全体のキーボードイベント。\n * @returns Nothing. 戻り値なし。\n */\n function handleKeyDown(event: KeyboardEvent) {\n if (event.key === \"Escape\") {\n setIsOpen(false)\n }\n }\n\n document.addEventListener(\"mousedown\", handleClickOutside)\n document.addEventListener(\"keydown\", handleKeyDown)\n return () => {\n document.removeEventListener(\"mousedown\", handleClickOutside)\n document.removeEventListener(\"keydown\", handleKeyDown)\n }\n }, [variant, isOpen])\n\n if (variant === \"popover\") {\n // トリガーの総括文言。**権限が無い/データが無いときに許可を名乗らない** ことが要件。\n // 以前は項目 0 件で `0 === 0` が成立して「編集可」、全項目が権限なしでも「閲覧のみ」と表示していた。\n // 子要素 (`children`) も対象に含める。トップレベルだけを数えると、書き込み可の親の下に\n // 権限なしの子がぶら下がっていても「編集可」と名乗ってしまう。\n const allItems = flattenGroupItems(items)\n const writeCount = allItems.filter((i) => i.permission?.canWrite).length\n const readCount = allItems.filter((i) => i.permission?.canRead).length\n const totalCount = allItems.length\n const summaryLabel = totalCount === 0 || (writeCount === 0 && readCount === 0) ? (labels?.noPermission ?? \"権限なし\") : writeCount === totalCount ? (labels?.fullWrite ?? \"編集可\") : writeCount > 0 ? (labels?.partialWrite ?? \"一部編集可\") : (labels?.readOnly ?? \"閲覧のみ\")\n\n return (\n <span ref={popoverRef} className={className} data-testid=\"my-permission-group-popover\" style={{ position: \"relative\", display: \"inline-block\", ...style }}>\n <button\n type=\"button\"\n onClick={() => setIsOpen(!isOpen)}\n data-testid=\"permission-popover-trigger\"\n style={{\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: cfg.wrapperGap,\n fontSize: cfg.triggerFontSize,\n padding: cfg.triggerPadding,\n borderRadius: \"6px\",\n backgroundColor: \"#f8fafc\",\n color: \"#334155\",\n border: \"1px solid #cbd5e1\",\n fontWeight: 600,\n cursor: \"pointer\",\n }}>\n <span>🔒 {prefixLabel}</span>\n <span style={{ fontSize: cfg.badgeFontSize, color: \"#64748b\" }}>({summaryLabel})</span>\n <span style={{ fontSize: \"0.65rem\", marginLeft: \"0.1rem\" }}>{isOpen ? \"▲\" : \"▾\"}</span>\n </button>\n\n {isOpen && (\n <div\n data-testid=\"permission-popover-card\"\n style={{\n position: \"absolute\",\n top: \"calc(100% + 6px)\",\n right: 0,\n zIndex: 50,\n minWidth: \"280px\",\n whiteSpace: \"nowrap\",\n padding: \"0.75rem 0.85rem\",\n backgroundColor: \"#ffffff\",\n borderRadius: \"8px\",\n boxShadow: \"0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)\",\n border: \"1px solid #e2e8f0\",\n fontSize: \"0.82rem\",\n }}>\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"space-between\",\n marginBottom: \"0.5rem\",\n paddingBottom: \"0.4rem\",\n borderBottom: \"1px solid #f1f5f9\",\n fontWeight: 700,\n color: \"#1e293b\",\n whiteSpace: \"nowrap\",\n }}>\n <span>🔒 {prefixLabel}</span>\n <button\n type=\"button\"\n onClick={() => setIsOpen(false)}\n style={{\n border: \"none\",\n background: \"none\",\n cursor: \"pointer\",\n color: \"#94a3b8\",\n fontSize: \"0.85rem\",\n padding: \"0 0.2rem\",\n }}>\n ✕\n </button>\n </div>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: \"0.4rem\" }}>\n {flattenGroupItems(items).map((item) => {\n const level = item.level ?? 0\n const isChild = level > 0\n return (\n <div\n key={item.resourceName}\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"space-between\",\n gap: \"1.25rem\",\n whiteSpace: \"nowrap\",\n paddingLeft: isChild ? `${level * 0.85}rem` : \"0\",\n }}>\n <div style={{ display: \"inline-flex\", alignItems: \"center\", whiteSpace: \"nowrap\" }}>\n {isChild && (\n <span\n aria-hidden=\"true\"\n style={{\n display: \"inline-block\",\n width: \"10px\",\n height: \"10px\",\n borderLeft: \"1.5px solid #cbd5e1\",\n borderBottom: \"1.5px solid #cbd5e1\",\n borderBottomLeftRadius: \"3px\",\n marginRight: \"6px\",\n marginTop: \"-4px\",\n flexShrink: 0,\n }}\n />\n )}\n <span\n style={{\n fontWeight: isChild ? 500 : 600,\n color: isChild ? \"#64748b\" : \"#1e293b\",\n fontSize: isChild ? \"0.78rem\" : \"0.82rem\",\n whiteSpace: \"nowrap\",\n }}>\n {item.resourceName}:\n </span>\n </div>\n <MyPermissionIndicator permission={item.permission} resourceName={item.resourceName} expectedResourceKey={item.expectedResourceKey} labels={labels} />\n </div>\n )\n })}\n </div>\n </div>\n )}\n </span>\n )\n }\n\n // 既定の \"pills\" バリアント\n return (\n <span\n className={className}\n data-testid=\"my-permission-group-pills\"\n style={{\n display: \"inline-flex\",\n alignItems: \"center\",\n flexWrap: \"wrap\",\n gap: cfg.pillGap,\n fontSize: cfg.fontSize,\n color: \"#475569\",\n ...style,\n }}>\n {prefixLabel && <span style={{ fontWeight: 600, color: \"#334155\" }}>{prefixLabel}:</span>}\n <span style={{ display: \"inline-flex\", alignItems: \"center\", flexWrap: \"wrap\", gap: cfg.pillGap }}>\n {items.map((item) => (\n <GroupItemPill key={item.resourceName} item={item} size={size} labels={labels} />\n ))}\n </span>\n </span>\n )\n}\n","/**\n * Display-state derivation for a {@link PermissionView} (pure, no React state involved).\n * {@link PermissionView} から表示状態を導出するモジュール (React の状態は一切持たない純粋関数)。\n */\nimport type { PermissionView } from \"./server\"\n\nexport type PermState = \"full\" | \"partial\" | \"none\"\n\n/**\n * Display-oriented reading of a {@link PermissionView}: the same `can*` booleans (safe defaults when the\n * view is absent) plus the ○△× tri-state used by the indicator components.\n * {@link PermissionView} の表示向け読み取り結果。同名の `can*` 真偽値と ○△× の三値状態を併せ持つ形。\n */\nexport type UsePermission = {\n canRead: boolean\n canCreate: boolean\n canUpdate: boolean\n canDelete: boolean\n /** create ∪ update */\n canWrite: boolean\n /** read: none(×) / partial(△, row|col restricted) / full(○). */\n readState: PermState\n /** write = create ∪ update: full(○ both) / partial(△ one) / none(×). */\n writeState: PermState\n deleteState: PermState\n}\n\n/**\n * Derive ○△× display states from a {@link PermissionView}. Pure (safe to call in render);\n * named `usePermission` for ergonomic call-sites.\n * {@link PermissionView} から ○△× の表示状態を導出する純粋関数 (レンダー内から安全に呼べる形)。\n *\n * @returns The permission booleans plus their display states. 権限の真偽値と表示状態の組。\n */\nexport function usePermission(view: PermissionView | null | undefined): UsePermission {\n const readState: PermState = !view?.canRead ? \"none\" : view.readPartial ? \"partial\" : \"full\"\n const writeState: PermState = view?.canCreate && view?.canUpdate ? \"full\" : view?.canCreate || view?.canUpdate ? \"partial\" : \"none\"\n const deleteState: PermState = view?.canDelete ? \"full\" : \"none\"\n return {\n canRead: !!view?.canRead,\n canCreate: !!view?.canCreate,\n canUpdate: !!view?.canUpdate,\n canDelete: !!view?.canDelete,\n canWrite: !!view?.canWrite,\n readState,\n writeState,\n deleteState,\n }\n}\n","/**\n * Badge components that show the acting user their own permissions for one resource.\n * 操作ユーザー自身の権限を 1 リソース分バッジ表示するコンポーネント群。\n */\nimport type { PermissionView } from \"./server\"\nimport { type PermState, usePermission } from \"./usePermission\"\n\nexport type MyPermissionIndicatorProps<K extends string = string> = {\n permission: PermissionView<K> | null | undefined\n /** Human-readable resource display name (e.g. \"文書データ\"). Falls back to permission.resourceKey. */\n resourceName?: string\n /**\n * Expected resource key. Typed as `NoInfer<K>`, so when the permission carries a literal key\n * (`getMyPermissions` / `requirePermission` both do) a mismatch is a **compile error**; the runtime\n * detector below stays for dynamically-keyed and JavaScript callers.\n */\n expectedResourceKey?: NoInfer<K>\n /** Whether to include resource name/key tooltip. Default true. */\n showResourceTooltip?: boolean\n labels?: { read?: string; write?: string; delete?: string }\n /** Show a \"(部分)\" hint when read is partial. Default true. */\n showScopeSummary?: boolean\n className?: string\n}\n\nexport type MyPermissionStatusProps<K extends string = string> = MyPermissionIndicatorProps<K> & {\n /** Label prefix shown before the indicator badges. Default: \"あなたの権限\" */\n prefixLabel?: string\n /** Whether to display the prefix label. Default: true */\n showPrefixLabel?: boolean\n /** Additional inline styles for wrapper element. */\n style?: React.CSSProperties\n}\n\nconst STATE_STYLE: Record<PermState, { bg: string; text: string; border: string }> = {\n full: { bg: \"#ecfdf5\", text: \"#047857\", border: \"#a7f3d0\" },\n partial: { bg: \"#fffbeb\", text: \"#b45309\", border: \"#fde68a\" },\n none: { bg: \"#f8fafc\", text: \"#94a3b8\", border: \"#cbd5e1\" },\n}\n\n/**\n * Format human-readable resource tooltip text.\n * リソース表示名およびキーからツールチップ用テキストを生成。\n */\nexport function formatResourceTooltip(resourceKey?: string, resourceName?: string): string | undefined {\n const key = resourceKey?.trim()\n const name = resourceName?.trim()\n if (name && key && name !== key) {\n return `対象リソース: ${name} (${key})`\n }\n if (name || key) {\n return `対象リソース: ${name || key}`\n }\n return undefined\n}\n\n/**\n * Render icon according to permission state.\n * 権限状態に応じた SVG アイコンをレンダリング。\n */\nfunction PermIcon({ state }: { state: PermState }) {\n if (state === \"full\") {\n return (\n <svg style={{ width: \"0.85rem\", height: \"0.85rem\", flexShrink: 0 }} fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={3} aria-hidden=\"true\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M5 13l4 4L19 7\" />\n </svg>\n )\n }\n if (state === \"partial\") {\n return (\n <svg style={{ width: \"0.85rem\", height: \"0.85rem\", flexShrink: 0 }} fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2.5} aria-hidden=\"true\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\" />\n </svg>\n )\n }\n return (\n <svg style={{ width: \"0.85rem\", height: \"0.85rem\", flexShrink: 0 }} fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2.5} aria-hidden=\"true\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636\" />\n </svg>\n )\n}\n\n/**\n * Display permission indicator badges for read, write, and delete capabilities.\n * 閲覧・編集・削除の権限状態バッジ群を描画。\n */\nexport function MyPermissionIndicator<K extends string = string>({ permission, resourceName, expectedResourceKey, showResourceTooltip = true, labels, showScopeSummary = true, className = \"authz-permission-indicator\" }: MyPermissionIndicatorProps<K>) {\n const p = usePermission(permission)\n const l = { read: labels?.read ?? \"閲覧\", write: labels?.write ?? \"編集\", delete: labels?.delete ?? \"削除\" }\n const resKey = permission?.resourceKey\n const resName = resourceName || resKey\n const tooltipText = showResourceTooltip ? formatResourceTooltip(resKey, resourceName) : undefined\n\n const hasMismatch = Boolean(expectedResourceKey && resKey && resKey !== expectedResourceKey)\n\n if (hasMismatch && typeof console !== \"undefined\" && typeof console.error === \"function\") {\n console.error(`[Authz Warning] Resource key mismatch in MyPermissionIndicator! Expected: \"${expectedResourceKey}\", but received permission for: \"${resKey}\".`)\n }\n\n /**\n * Render one permission badge, coloured by its state and carrying the tooltip text.\n * 権限バッジ 1 個を描画する処理 (状態に応じた配色とツールチップ文言を持つ形)。\n *\n * @param key Which permission the badge stands for. バッジが表す権限。\n * @param state Granted / denied / unknown state to colour. 配色の基となる状態。\n * @param label Visible short label. 表示する短いラベル。\n * @returns The badge element. バッジ要素。\n */\n const renderBadge = (key: \"read\" | \"write\" | \"delete\", state: PermState, label: string) => {\n const style = STATE_STYLE[state]\n const badgeTitle = tooltipText ? `${label}: ${state}\\n${tooltipText}` : `${label}: ${state}`\n return (\n <span\n data-perm={key}\n data-state={state}\n title={badgeTitle}\n style={{\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: \"0.25rem\",\n padding: \"0.2rem 0.5rem\",\n borderRadius: \"100px\",\n fontSize: \"0.75rem\",\n fontWeight: 600,\n backgroundColor: style.bg,\n color: style.text,\n border: `1px solid ${style.border}`,\n lineHeight: 1,\n userSelect: \"none\",\n }}>\n <PermIcon state={state} />\n <span>{label}</span>\n </span>\n )\n }\n\n return (\n <span\n className={className}\n title={tooltipText}\n data-resource={resKey ?? \"\"}\n data-resource-name={resName ?? \"\"}\n data-mismatch={hasMismatch ? \"true\" : undefined}\n style={{\n display: \"inline-flex\",\n gap: \"0.35rem\",\n alignItems: \"center\",\n verticalAlign: \"middle\",\n }}>\n {renderBadge(\"read\", p.readState, l.read)}\n {renderBadge(\"write\", p.writeState, l.write)}\n {renderBadge(\"delete\", p.deleteState, l.delete)}\n {showScopeSummary && p.readState === \"partial\" ? (\n <span\n data-perm=\"scope\"\n title={tooltipText}\n style={{\n fontSize: \"0.7rem\",\n color: \"#b45309\",\n backgroundColor: \"#fef3c7\",\n padding: \"0.15rem 0.35rem\",\n borderRadius: \"4px\",\n fontWeight: 600,\n }}>\n 部分制限\n </span>\n ) : null}\n {hasMismatch ? (\n <span\n data-testid=\"resource-mismatch-warning\"\n title={`expected: \"${expectedResourceKey}\", actual: \"${resKey}\"`}\n style={{\n fontSize: \"0.7rem\",\n color: \"#991b1b\",\n backgroundColor: \"#fee2e2\",\n border: \"1px solid #fca5a5\",\n padding: \"0.15rem 0.35rem\",\n borderRadius: \"4px\",\n fontWeight: 700,\n }}>\n ⚠️ リソース不一致 ({resKey} ≠ {expectedResourceKey})\n </span>\n ) : null}\n </span>\n )\n}\n\n/**\n * Reusable \"Your Permissions\" status bar component suitable for any application page.\n * 「あなたの権限」ラベルと権限状態インジケータを束ねた再利用可能なコンポーネント。\n */\nexport function MyPermissionStatus<K extends string = string>({\n permission,\n resourceName,\n expectedResourceKey,\n prefixLabel = \"あなたの権限\",\n showPrefixLabel = true,\n showResourceTooltip = true,\n labels,\n showScopeSummary = true,\n className = \"authz-permission-status\",\n style,\n}: MyPermissionStatusProps<K>) {\n const resKey = permission?.resourceKey\n const resName = resourceName || resKey\n const tooltipText = showResourceTooltip ? formatResourceTooltip(resKey, resourceName) : undefined\n\n return (\n <span\n className={className}\n title={tooltipText}\n data-testid=\"my-permission-status\"\n data-resource={resKey ?? \"\"}\n data-resource-name={resName ?? \"\"}\n style={{\n fontSize: \"0.82rem\",\n color: \"#475569\",\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: \"0.4rem\",\n ...style,\n }}>\n {showPrefixLabel && <span data-testid=\"prefix-label\">{prefixLabel}:</span>}\n <MyPermissionIndicator permission={permission} resourceName={resourceName} expectedResourceKey={expectedResourceKey} showResourceTooltip={showResourceTooltip} labels={labels} showScopeSummary={showScopeSummary} />\n </span>\n )\n}\n","/**\n * Resource definition and registry utilities for preventing page-resource mismatches.\n * ページとリソースの表示・権限食い違いを防止するためのリソースレジストリ定義。\n */\n\nexport type ResourceDefinition = {\n /** Unique resource key used in authorization rules (e.g. \"documents\") */\n key: string\n /** Human-readable display name (e.g. \"文書管理\") */\n name: string\n /** Optional description for administration UI */\n description?: string\n}\n\nexport type ResourceDefinitionInput = {\n /** Optional explicit resource key; defaults to the object property name */\n key?: string\n /** Human-readable display name (e.g. \"文書管理\") */\n name: string\n /** Optional description for administration UI */\n description?: string\n}\n\nexport type ResourceRegistry<T extends Record<string, ResourceDefinition>> = T\n\n/**\n * The resource key an entry resolves to: its explicit `key`, otherwise the property name.\n * `key` を明示していればそれ、無ければプロパティ名。\n *\n * `Extract<…, string>` を挟むのは `key?: string` (省略可) が `string | undefined` になり、素の\n * `extends string` では偽になってプロパティ名を名乗ってしまうため (実行時は `key` を使うので型が嘘になる)。\n * `key` を書いていないエントリでは `Extract` が `never` になり、プロパティ名へ落ちる。\n */\ntype ResolvedResourceKey<PropertyName extends string, Input extends ResourceDefinitionInput> = [Extract<Input[\"key\"], string>] extends [never] ? PropertyName : Extract<Input[\"key\"], string>\n\n/**\n * Define type-safe resource registry for an application.\n * アプリ全体のリソース定義(キー・表示名・説明)を一元管理するヘルパー。\n *\n * 各エントリの `key` はリテラル型のまま保持する。`Record<string, …>` へ広げてしまうと\n * {@link resourceKeysOf} が `string[]` に落ち、権限マップのキー型検査が効かなくなるため。\n *\n * @returns The registry with literal-typed resource keys. リソースキーをリテラル型で保持したレジストリ。\n */\nexport function defineResources<const T extends Record<string, ResourceDefinitionInput>>(resources: T): { [K in keyof T & string]: { key: ResolvedResourceKey<K, T[K]>; name: string; description?: string } } {\n const result = {} as { [K in keyof T & string]: { key: ResolvedResourceKey<K, T[K]>; name: string; description?: string } }\n for (const k of Object.keys(resources) as Array<keyof T & string>) {\n const item = resources[k]\n result[k] = {\n key: (item.key ?? k) as ResolvedResourceKey<typeof k, T[typeof k]>,\n name: item.name,\n description: item.description,\n }\n }\n return result\n}\n\n/**\n * Extract a registry's resource keys with their literal types preserved.\n * レジストリのリソースキーをリテラル型を保ったまま取り出す処理。\n *\n * `Object.keys(registry)` は `string[]` になり、`getMyPermissions` の戻り値も索引型へ落ちてキーの誤りを\n * 検出できなくなる。加えて `Object.keys` は **プロパティ名** を返すため、`key` を明示したエントリでは\n * 実際のリソースキーと食い違う。\n *\n * ⚠️ 1 エントリでもリテラルでない `key` (例: 変数由来の `string`) を持つと、合併の性質上レジストリ全体の\n * キー型が `string` へ広がり、他のエントリ分のコンパイル時検査も無効になる。`key` は必ずリテラルで書くこと。\n *\n * @returns The registry's resource keys as a literal union array. リテラル合併型のリソースキー配列。\n */\nexport function resourceKeysOf<T extends Record<string, { key: string }>>(registry: T): Array<T[keyof T & string][\"key\"]> {\n return Object.values(registry).map((r) => r.key) as Array<T[keyof T & string][\"key\"]>\n}\n","/**\n * React Router (v7) adapter: binds `appKey` + the DI ports and exposes the loader/action authorization API.\n * React Router (v7) 向けアダプタ。`appKey` と DI ポートを束ね、loader/action 用の認可 API を公開するモジュール。\n *\n * 設計上の中心的な不変条件は **判定は 1 回だけ** ということ。ガード (`requirePermission`) と\n * UI 表示用の権限ビューは同一の実効権限から導出されるため、「画面は開けるのに一覧では false」\n * という乖離が構造的に起こり得ない。\n */\nimport { AUTHZ_ACTIONS, type AuthzAction, assertTenantId, can, createRequirePermission, type DenyInfo, type EffectivePermission, isSameTenant, type MergedScope } from \"@aiquants/authz-core\"\nimport { redirect } from \"react-router\"\n\ntype Awaitable<T> = T | Promise<T>\nexport type AuthzUserId = string | number\n\nconst PKG = \"@aiquants/authz-react-router\"\n\n/**\n * Thrown when the API itself is called incorrectly (bad arguments, missing port) — a programmer error,\n * never an authorization decision. It is deliberately NOT an `AuthzDeniedError`: a misuse must surface as a\n * loud 500, not as a silent 403 that looks like a permission problem.\n * API の使い方が誤っている場合に送出される例外。認可拒否 (403) とは明確に区別されるプログラマ向けエラー。\n */\nexport class AuthzUsageError extends Error {\n constructor(message: string) {\n super(`[${PKG}] ${message}`)\n this.name = \"AuthzUsageError\"\n }\n}\n\n/**\n * Arguments handed to the {@link AuthzConfig.getEffectivePermissions} port.\n * 実効権限ポートへ渡す引数。\n *\n * `tenantId` is a **required, non-empty string**. It is never absent and never `null`: a port that\n * receives no tenant would have to guess, and the only guess an SQL adapter can make — omit the\n * predicate — returns every tenant's grants. That inversion is the failure this contract removes.\n * `tenantId` は必須かつ非空 (欠落を「全テナント」に読み替える経路を型ごと塞ぐ形)。\n */\nexport type EffectivePermissionsQuery = {\n userId: AuthzUserId | null\n tenantId: string\n groupIds?: (string | number)[]\n appKey: string\n /** Always a de-duplicated, non-empty list — the port may load them all in one round-trip. Read-only: the adapter reports on exactly these keys. */\n resourceKeys: readonly string[]\n}\n\nexport type AuthzConfig = {\n /** Stable app identifier (e.g. \"quants\"). Bound into every permission query. */\n appKey: string\n /** Resolve the acting user id from the request (cookie/session/openid → the consumer's user id). */\n resolveUserId: (request: Request) => Awaitable<AuthzUserId | null | undefined>\n /**\n * Resolve the acting tenant id from the request (header / cookie / subdomain → tenant id).\n * **Required**: every permission question is asked within exactly one tenant, so a host that\n * cannot name it must not be able to ask at all. Returning a blank string raises\n * `AuthzTenantError` — it is never read as \"no filter\" / \"every tenant\".\n * リクエストからテナント ID を解決するポート (必須)。空文字は「全テナント」ではなく例外。\n */\n resolveTenantId: (request: Request) => Awaitable<string>\n /**\n * Prove the acting identity may act **as** the resolved tenant. **Required**, with no permissive\n * default: `resolveTenantId` only says *which* tenant was named, and that name comes from a\n * client-influenced channel (header / sub-domain / cookie). Without this seam a user of tenant A\n * is authorized inside tenant B by naming tenant B — B's `@authenticated` grants apply and every\n * downstream tenant check still passes, because the permission genuinely belongs to B.\n * リクエストが名乗ったテナントへの帰属証明 (必須・既定値なし)。テナント名は利用者が影響できる経路から\n * 来るため、これが無いと「別テナントを名乗るだけで越境認可」になる。\n *\n * Called once per check, **before** any permission is loaded. `userId` is `null` for an anonymous\n * caller; the host decides whether that tenant's `@anonymous` grants may be used (they are public\n * by construction, so `true` is the usual answer).\n * 権限読込より前に 1 回だけ呼ばれる。匿名呼び出しの扱いはホストが決める。\n */\n assertTenantMembership: (request: Request, tenantId: string, userId: AuthzUserId | null) => Awaitable<boolean>\n /**\n * Optionally resolve acting user's group IDs, for the tenant the request was scoped to.\n * リクエストのスコープとなったテナントに対して、実行ユーザーのグループ ID を解決する任意ポート。\n *\n * `tenantId` is the value `resolveTenantId` already produced and validated, so a host never has to\n * derive the tenant a second time — a second derivation is a second thing that can disagree.\n * `tenantId` は解決・検証済みの値 (ホスト側での二重解決を不要にし、乖離の余地を作らないため)。\n */\n resolveGroupIds?: (request: Request, tenantId: string) => Awaitable<(string | number)[] | null | undefined>\n /**\n * Load effective permissions for (user, app, resources) — **one call, many resources** (typically\n * `getEffectivePermissionsMany` from `@aiquants/authz-drizzle`). A key with no grants may be omitted or\n * mapped to `null`; both mean \"no permission\" (fail-close).\n */\n getEffectivePermissions: (args: EffectivePermissionsQuery) => Awaitable<Record<string, EffectivePermission | null | undefined> | null | undefined>\n /**\n * Optional: every resource key registered for this app **within `tenantId`** (typically\n * `listResourceKeys` from `@aiquants/authz-drizzle`, whose 4th argument is that same tenant).\n * Configuring it is what makes `getMyPermissions(request)` — with no resource list — legal;\n * without it that call throws {@link AuthzUsageError} instead of guessing.\n * 指定テナントに登録済みの全リソースキーを返す任意ポート。\n *\n * The tenant is handed in already resolved and validated: making the host re-derive it from the\n * raw `Request` would create a second, divergable tenant source, and a host that derived it\n * differently (or could not) would report on another tenant's resource keys.\n * テナントは解決・検証済みの値として渡す (ホスト側での再導出は第 2 のテナント源となり乖離するため)。\n *\n * Called only after `assertTenantMembership` has proven the caller may act as `tenantId`: the set of\n * registered keys is itself that tenant's information, so a non-member must not be able to provoke\n * this query at all.\n * 帰属証明を通った後にのみ呼ばれる (登録済みキーの集合自体がテナントの情報であるため)。\n */\n listResourceKeys?: (request: Request, tenantId: string) => Awaitable<readonly string[] | null | undefined>\n /** Default deny handler. If omitted, denies throw AuthzDeniedError (403). */\n onDeny?: (info: DenyInfo) => void\n}\n\nexport type RequirePermissionOptions = {\n /** When set, denials `throw redirect(failureRedirect)` instead of throwing 403. */\n failureRedirect?: string\n}\n\n/**\n * Serializable per-resource permission summary for UI (loader → component).\n * UI 向けの直列化可能なリソース単位権限サマリ (loader からコンポーネントへ受け渡す形)。\n *\n * 真偽値は `can` 接頭辞で統一されている。`delete` のような素の動詞名は分割代入できず\n * (`const { delete } = view` は構文エラー)、フックの戻り値との二重語彙も生むため採用しない。\n */\nexport type PermissionView<K extends string = string> = {\n resourceKey: K\n canRead: boolean\n canCreate: boolean\n canUpdate: boolean\n canDelete: boolean\n /** create ∪ update */\n canWrite: boolean\n /** read granted but row and/or column restricted (→ △ partial). */\n readPartial: boolean\n actions: AuthzAction[]\n}\n\n/** Result of a successful {@link createAuthz} guard. ガード成功時の戻り値。 */\nexport type RequirePermissionResult<K extends string = string> = {\n userId: AuthzUserId | null\n /** The tenant the decision was made within (always present, never blank). 判定を行ったテナント。 */\n tenantId: string\n /** Effective row/column scope for the guarded action — apply as WHERE / column mask. */\n scope: MergedScope\n /** UI view derived from the **same** evaluation as the guard decision (no second lookup). */\n permission: PermissionView<K>\n}\n\n/**\n * Permission views keyed by the exact resource keys that were requested.\n * 要求したキーで型付けした権限ビュー群。\n *\n * リテラルのキーを渡した場合だけ「そのキーは必ず存在する」形になる。`string[]` のようにキーが実行時に\n * しか分からない場合は `| undefined` を含む索引型へ落ち、存在チェックを強制する (総和型の索引を\n * 名乗ると、要求していないキーが型の上では必ず存在することになり、元の不具合と同じ罠に戻る)。\n *\n * ⚠️ キーは配列の **要素型** から導かれる。`flag ? [\"a\"] : [\"b\"]` のように要素型が合併になる式では、\n * 実行時に片方しか無くても型の上では両方が存在する扱いになる。リテラル配列を直接渡すか\n * `resourceKeysOf(registry)` を使うこと。\n * ⚠️ 戻り値を `Record<string, …>` 型の変数へ代入すると、その文脈から `K` が `string` へ広がり\n * 索引型 (値は `| undefined`) に落ちる。リテラルキーの保証が要る箇所では代入先を広げないこと。\n * ⚠️ 戻り値は素のオブジェクト。要求していない `toString` などのキーを読むと `Object.prototype` の\n * メンバが返る (索引型の `| undefined` とは一致しない)。要求したキーだけを読むこと。\n */\nexport type PermissionViewMap<K extends string> = string extends K ? Record<string, PermissionView | undefined> : { [P in K]: PermissionView<P> }\n\n/**\n * Map an EffectivePermission to a serializable {@link PermissionView}.\n * 実効権限を直列化可能な {@link PermissionView} へ変換する処理。\n *\n * @returns The UI-facing permission summary (all-false when `perm` is absent). UI 表示用の権限サマリ。\n */\nexport function toPermissionView<K extends string>(resourceKey: K, perm: EffectivePermission | null | undefined): PermissionView<K> {\n const canRead = can(perm, \"read\")\n const canCreate = can(perm, \"create\")\n const canUpdate = can(perm, \"update\")\n const canDelete = can(perm, \"delete\")\n const readScope = perm?.scopeByAction.read\n // read が許可されていても行/列のいずれかが絞られていれば「部分」扱い (△)。\n // スコープ自体が欠けている場合、ガードは DENY_ALL_SCOPE (0 行 0 列) に倒すため、表示も「制限あり」に\n // 揃える。ここで「制限なし」に倒すと、1 行も見えないのにバッジだけ全閲覧可を示す乖離になる。\n const readPartial = canRead && (!readScope || readScope.rowScope !== null || readScope.columnScope !== null)\n return {\n resourceKey,\n canRead,\n canCreate,\n canUpdate,\n canDelete,\n canWrite: canCreate || canUpdate,\n readPartial,\n actions: perm ? Array.from(perm.actions) : [],\n }\n}\n\n/**\n * Describe a runtime value for an error message without dumping the whole object.\n * エラーメッセージ用に値の型と概要を短く表現する処理。\n *\n * @returns A short human-readable description of the value. 値を要約した短い説明文字列。\n */\nfunction describe(value: unknown): string {\n if (value === null) return \"null\"\n if (typeof value === \"string\") return `a string (${JSON.stringify(value)})`\n if (Array.isArray(value)) return `an array`\n return `${typeof value} (${String(value)})`\n}\n\n/**\n * Validate a caller-supplied resource-key list, failing loudly on every shape that would otherwise\n * degrade silently — most importantly a bare string, which `for...of` would iterate character by character.\n * 呼び出し側が渡したリソースキー配列を検証する処理。無言で劣化する形 (特に素の文字列) を全て明示的に弾く。\n *\n * @returns The validated keys, de-duplicated and order-preserving. 重複を除いた検証済みのキー配列。\n */\nfunction assertResourceKeys(value: unknown, label: string): string[] {\n if (!Array.isArray(value)) {\n const hint = typeof value === \"string\" ? ` A bare string would be iterated character by character; pass \\`{ resourceKeys: [${JSON.stringify(value)}] }\\` instead.` : \"\"\n throw new AuthzUsageError(`${label} must be an array of resource keys, but received ${describe(value)}.${hint}`)\n }\n const seen = new Set<string>()\n const keys: string[] = []\n value.forEach((key, index) => {\n if (typeof key !== \"string\" || key.trim() === \"\") {\n throw new AuthzUsageError(`${label}[${index}] must be a non-empty resource key string, but received ${describe(key)}.`)\n }\n if (seen.has(key)) return\n seen.add(key)\n keys.push(key)\n })\n return keys\n}\n\n/**\n * Reject a port result that is a single {@link EffectivePermission} instead of a per-resource record.\n * 実効権限ポートが「1 件の実効権限」を返した場合を明示的に弾く処理。\n *\n * このポートは 1 回の呼び出しで **複数リソース分** を返す契約。旧契約 (1 リソース 1 件) のまま\n * 移行し忘れると、キー参照が全て `undefined` になり **全リソース拒否が沈黙して**成立してしまう。\n * 形で見分けられるうちに落とす。\n */\nfunction assertPermissionRecord(loaded: unknown): void {\n if (loaded === null || loaded === undefined) return\n const shape = loaded as { actions?: unknown; scopeByAction?: unknown }\n if (shape.actions instanceof Set && typeof shape.scopeByAction === \"object\") {\n throw new AuthzUsageError(\n \"createAuthz({ getEffectivePermissions }) must resolve to a record keyed by resource key (e.g. getEffectivePermissionsMany from @aiquants/authz-drizzle), but it resolved to a single EffectivePermission. Keeping the single-resource port would deny every resource silently.\",\n )\n }\n}\n\n/**\n * Validate the guard query so a typo surfaces as an explicit error instead of an unexplained 403.\n * ガードの問い合わせ内容を検証する処理。誤記が「理由の分からない 403」ではなく明示的な例外になる形。\n *\n * @returns The validated resource key and action. 検証済みのリソースキーとアクション。\n */\nfunction assertPermissionQuery<K extends string>(query: unknown): { resourceKey: K; action: AuthzAction } {\n if (typeof query !== \"object\" || query === null) {\n throw new AuthzUsageError(`requirePermission(request, query) requires a query object like { resourceKey: \"documents\", action: \"read\" }, but received ${describe(query)}.`)\n }\n const { resourceKey, action } = query as { resourceKey?: unknown; action?: unknown }\n if (typeof resourceKey !== \"string\" || resourceKey.trim() === \"\") {\n throw new AuthzUsageError(`query.resourceKey must be a non-empty string, but received ${describe(resourceKey)}.`)\n }\n if (typeof action !== \"string\" || !(AUTHZ_ACTIONS as readonly string[]).includes(action)) {\n const extra = action === \"write\" ? ` \"write\" is a derived concept (create ∪ update) and is never guarded directly — guard \"create\" or \"update\".` : \"\"\n throw new AuthzUsageError(`query.action must be one of ${AUTHZ_ACTIONS.join(\" | \")}, but received ${describe(action)}.${extra}`)\n }\n return { resourceKey: resourceKey as K, action: action as AuthzAction }\n}\n\n/**\n * Keep an effective permission only when it was issued for the requested tenant (fail-close).\n * 要求テナント向けに発行された実効権限だけを残し、それ以外は「権限なし」へ倒す処理 (fail-close)。\n *\n * The guard reaches the same verdict through `@aiquants/authz-core` (deny `tenant-mismatch`).\n * Letting the UI side through instead would recreate the exact divergence this adapter exists to\n * prevent — a badge that says \"allowed\" over an action that answers 403.\n * ガード側は core が同じ判定 (deny `tenant-mismatch`) を下すため、表示側だけ通すと「バッジは可・操作は 403」の乖離になる。\n *\n * @param perm The permission the port returned. ポートが返した実効権限。\n * @param tenantId The tenant the request was scoped to. 要求のスコープとなったテナント。\n * @returns The permission when it belongs to the tenant, otherwise `null`. 帰属時のみ実効権限、他は `null`。\n */\nfunction permissionForTenant(perm: EffectivePermission | null, tenantId: string): EffectivePermission | null {\n return perm !== null && isSameTenant(tenantId, perm.tenantId) ? perm : null\n}\n\n/**\n * The resources one `getMyPermissions` call reports on, decided before any port is consulted.\n * 1 回の `getMyPermissions` 呼び出しが対象とするリソースの決定結果 (ポート呼び出し前に確定させる形)。\n *\n * 明示されたキーと「登録済みの全リソース」を型で区別するのは、帰属証明に失敗したときの扱いが\n * 異なるため。前者は呼び出し側が既に知っている名前だが、後者の鍵集合そのものがそのテナントの\n * 登録情報であり、帰属していない呼び出し側へ返してはならない。\n */\ntype ResourceKeyPlan = { kind: \"explicit\"; keys: string[] } | { kind: \"all\"; listResourceKeys: NonNullable<AuthzConfig[\"listResourceKeys\"]> }\n\n/**\n * React Router adapter. Binds `appKey` and the DI deps, exposing:\n * - `requirePermission(request, {resourceKey, action}, options?)` → `{ userId, tenantId, scope, permission }` on allow\n * (apply row WHERE / column mask with `scope`; render `permission` in the UI); denies throw 403 or redirect.\n * Every check is scoped to the tenant `resolveTenantId` returns, that tenant must be one the caller is proven\n * to belong to (`assertTenantMembership`, else deny `not-a-tenant-member`), and a permission issued for\n * another tenant is denied (`tenant-mismatch`) rather than trusted.\n * - `getMyPermissions(request, {resourceKeys})` → `Record<resourceKey, PermissionView>` for the UI,\n * or `getMyPermissions(request)` for every registered resource when `listResourceKeys` is configured.\n * Membership is proven **before** that resource list is read, so naming a foreign tenant never reveals\n * which resources it has registered.\n * `appKey` と DI ポートを束ね、ガードと自己権限取得の 2 面だけを公開するファクトリ。\n *\n * @returns The bound `requirePermission` / `getMyPermissions` pair. 結線済みの認可 API 一式。\n */\nexport function createAuthz(config: AuthzConfig) {\n // ポート未設定は型では防げない (JS ホスト / 部分的な移行)。ここで落とさないと\n // 呼び出し時に \"not a function\" という無関係な TypeError になり、原因が見えなくなる。\n if (typeof config.resolveTenantId !== \"function\") {\n throw new AuthzUsageError(`createAuthz({ resolveTenantId }) is required: every permission check is scoped to exactly one tenant, but received ${describe(config.resolveTenantId)}.`)\n }\n if (typeof config.assertTenantMembership !== \"function\") {\n throw new AuthzUsageError(\n `createAuthz({ assertTenantMembership }) is required: resolving a tenant only says which tenant the request named — it never proves the caller may act as it, so without this port naming another tenant is enough to be authorized inside it. Received ${describe(config.assertTenantMembership)}.`,\n )\n }\n\n /**\n * Resolve the acting user's group ids once per call, normalizing \"no groups port\" to `undefined`.\n * 1 回の呼び出しにつき 1 度だけグループ ID を解決する処理 (ポート未設定は `undefined` へ正規化)。\n *\n * @param request The incoming request. 受信したリクエスト。\n * @param tenantId The already-validated tenant of this check. 検証済みの対象テナント。\n * @returns The group ids, or `undefined` when no port is configured. グループ ID 配列、または未設定を表す `undefined`。\n */\n async function resolveGroupIds(request: Request, tenantId: string): Promise<(string | number)[] | undefined> {\n return config.resolveGroupIds ? ((await config.resolveGroupIds(request, tenantId)) ?? undefined) : undefined\n }\n\n /**\n * Resolve and validate the acting tenant id once per call — the single place this adapter reads it.\n * 1 回の呼び出しにつき 1 度だけテナント ID を解決・検証する処理 (このアダプタ唯一の読み取り点)。\n *\n * @param request The incoming request. 受信したリクエスト。\n * @returns The validated, non-empty tenant id. 検証済みで非空のテナント ID。\n * @throws {AuthzTenantError} When the port yields a blank id. ポートが空を返した場合。\n */\n async function resolveTenantId(request: Request): Promise<string> {\n return assertTenantId(await config.resolveTenantId(request), `${PKG} createAuthz(resolveTenantId)`)\n }\n\n /**\n * Load effective permissions for a set of resources through the single batch port.\n * 一括ポートを通じて複数リソースの実効権限をまとめて読み込む処理。\n *\n * @param tenantId The validated tenant every returned permission is scoped to. 検証済みの対象テナント。\n * @returns A permission (or `null`) for every requested key. 要求した全キー分の実効権限 (無い場合は `null`)。\n */\n async function loadPermissions(request: Request, resourceKeys: string[], userId: AuthzUserId | null, tenantId: string): Promise<Record<string, EffectivePermission | null>> {\n if (resourceKeys.length === 0) return {}\n\n const groupIds = await resolveGroupIds(request, tenantId)\n const loaded = await config.getEffectivePermissions({ userId, tenantId, groupIds, appKey: config.appKey, resourceKeys })\n assertPermissionRecord(loaded)\n // ポートがキーを落としても undefined を漏らさず null (= 権限なし) に倒す。\n // 自前プロパティのみを見るのは、`constructor` 等の継承プロパティを実効権限と取り違えないため。\n // 組み立てに fromEntries を使うのは、`__proto__` というキーが素の代入では黙って落ちるため。\n return Object.fromEntries(resourceKeys.map((key) => [key, (loaded && Object.hasOwn(loaded, key) ? loaded[key] : null) ?? null]))\n }\n\n /**\n * Guard a loader/action: deny (403 or redirect) unless the acting user holds `action` on `resourceKey`.\n * loader / action 用のガード。指定アクションを保持しない限り拒否 (403 またはリダイレクト) する処理。\n *\n * @returns The user id, the effective scope, and the matching UI view. ユーザー ID・実効スコープ・UI 表示用ビュー。\n */\n async function requirePermission<K extends string>(request: Request, query: { resourceKey: K; action: AuthzAction }, options?: RequirePermissionOptions): Promise<RequirePermissionResult<K>> {\n const { resourceKey, action } = assertPermissionQuery<K>(query)\n\n const onDeny: ((info: DenyInfo) => void) | undefined = options?.failureRedirect\n ? () => {\n throw redirect(options.failureRedirect as string)\n }\n : config.onDeny\n\n const guard = createRequirePermission<Request, AuthzUserId>({\n resolveUserId: config.resolveUserId,\n resolveTenantId,\n // 帰属証明もリクエスト単位のポートへそのまま委譲する (core が解決済みテナントを渡してくる)\n assertTenantMembership: ({ ctx, tenantId, userId }) => config.assertTenantMembership(ctx, tenantId, userId),\n // core が解決済みテナントを渡してくる。ここで別の値を引き直すと 1 リクエスト内で 2 つの\n // テナントが並立し、「判定したテナント」と「読んだテナント」がずれる。\n getEffectivePermissions: async (args) => (await loadPermissions(request, [args.resourceKey], args.userId, args.tenantId))[args.resourceKey],\n onDeny,\n })\n const result = await guard(request, { appKey: config.appKey, resourceKey, action })\n // ガードが判定に用いた実効権限をそのままビュー化する (再取得しないので乖離し得ない)\n return { userId: result.userId, tenantId: result.tenantId, scope: result.scope, permission: toPermissionView(resourceKey, result.effectivePermission) }\n }\n\n /**\n * Decide which resources one `getMyPermissions` call reports on, rejecting misuse before any port runs.\n * 1 回の `getMyPermissions` 呼び出しが対象とするリソースを決定する処理 (ポート実行前に誤用を弾く形)。\n *\n * @param options The caller-supplied options, if any. 呼び出し側が渡したオプション。\n * @returns The validated plan for this call. この呼び出しの検証済みプラン。\n * @throws {AuthzUsageError} When the keys are malformed, or omitted with no `listResourceKeys` port configured. キーが不正な場合、またはポート未設定でキーを省略した場合。\n */\n function planResourceKeys(options?: { resourceKeys?: readonly string[] }): ResourceKeyPlan {\n if (options?.resourceKeys !== undefined) {\n return { kind: \"explicit\", keys: assertResourceKeys(options.resourceKeys, \"getMyPermissions options.resourceKeys\") }\n }\n if (!config.listResourceKeys) {\n throw new AuthzUsageError(\n 'getMyPermissions(request) needs to know which resources exist. Either pass them explicitly — getMyPermissions(request, { resourceKeys: [\"documents\"] }) — or configure createAuthz({ listResourceKeys }) (e.g. listResourceKeys from @aiquants/authz-drizzle) to report on every registered resource.',\n )\n }\n return { kind: \"all\", listResourceKeys: config.listResourceKeys }\n }\n\n function getMyPermissions<K extends string>(request: Request, options: { resourceKeys: readonly K[] }): Promise<PermissionViewMap<K>>\n // キーを指定しない形は実行時に決まるキーを返すため、値は `| undefined` を含む\n // (`Record<string, PermissionView>` にすると、要求していないキーが型の上では必ず存在することになる)。\n function getMyPermissions(request: Request, options?: { resourceKeys?: readonly string[] }): Promise<Record<string, PermissionView | undefined>>\n /**\n * Report the acting user's permissions for the UI — for the named resources, or for every registered\n * resource when `resourceKeys` is omitted (requires the `listResourceKeys` port).\n * UI 表示用に操作ユーザーの権限を返す処理。キー省略時は登録済みの全リソースが対象 (要 `listResourceKeys`)。\n *\n * Views are reported for the tenant `resolveTenantId` returns, and only when\n * `assertTenantMembership` proves the caller may act as it; a permission stamped with any other\n * tenant is reported as \"no permission\", matching what the guard would decide.\n * 対象は `resolveTenantId` が返したテナント (帰属証明を通った場合のみ)。別テナントの実効権限は\n * ガードと同じく「権限なし」として報告する。\n *\n * A caller that is not a member of the tenant it named learns nothing about that tenant: the named\n * keys come back all-false, and an omitted key list yields `{}` rather than that tenant's registered\n * resource inventory — the key set is itself tenant-owned information.\n * 帰属していないテナントについては何も返さない (明示キーは全 false、キー省略時は空マップ)。\n *\n * @returns A view per requested resource key; requested keys are never missing. リソースキーごとの権限ビュー。\n */\n async function getMyPermissions(request: Request, options?: { resourceKeys?: readonly string[] }): Promise<Record<string, PermissionView>> {\n // 対象リソースの決定は I/O を伴わない検証なので先に済ませる。誤用 (キー不正 / ポート未設定) の\n // 発覚が帰属の可否に左右されると、非会員のときだけ静かに空が返る形になる。\n const plan = planResourceKeys(options)\n const userId = (await config.resolveUserId(request)) ?? null\n const tenantId = await resolveTenantId(request)\n // 帰属証明はリソースキーの解決より **前** に置く。`listResourceKeys` はそのテナントに登録済みの\n // リソース一覧を実際に問い合わせるため、後ろに置くと「名乗っただけの他テナント」へクエリが走り、\n // 返るマップの鍵集合がそのテナントの登録リソース目録そのものになる (値が全 false でも漏洩する)。\n if (!(await config.assertTenantMembership(request, tenantId, userId))) {\n // 明示されたキーは呼び出し側が既に知っている名前なので全 false のビューで返す。\n // キー省略時は鍵集合自体が他テナントの情報なので、空のマップへ倒す。\n return plan.kind === \"explicit\" ? Object.fromEntries(plan.keys.map((key) => [key, toPermissionView(key, null)])) : {}\n }\n const resourceKeys = plan.kind === \"explicit\" ? plan.keys : assertResourceKeys((await plan.listResourceKeys(request, tenantId)) ?? [], \"the listResourceKeys port result\")\n const permissions = await loadPermissions(request, resourceKeys, userId, tenantId)\n\n // `__proto__` を含むどんなキーでも必ず自前プロパティとして現れるようにする (素の代入では落ちる)。\n // 別テナントの実効権限はガードと同じく「権限なし」へ倒してから描画する。\n return Object.fromEntries(resourceKeys.map((key) => [key, toPermissionView(key, permissionForTenant(permissions[key], tenantId))]))\n }\n\n return { requirePermission, getMyPermissions }\n}\n"],"mappings":"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","names":["index_exports","__export","AuthzAdminAppView","AuthzErrorBoundary","AuthzGrantsView","AuthzGraphView","AuthzGroupRolesView","AuthzLayout","AuthzResourcesView","AuthzRolesView","AuthzUsageError","AuthzUserRolesView","ErrorAlert","Field","MyPermissionGroupStatus","MyPermissionIndicator","MyPermissionStatus","createAuthz","createAuthzAdminApp","createAuthzAdminServer","defaultAuthzAdminLabels","defineResources","flattenGroupItems","formatResourceTooltip","getPermissionSummary","makeAuthzErrorBoundary","resolveLabels","resourceKeysOf","toPermissionView","ui","usePermission","__toCommonJS","import_react_router","import_authz_core","import_react_router","defaultAuthzAdminLabels","resolveLabels","over","d","isResponse","e","toActionError","createAuthzAdminServer","opts","store","RES","labels","resolveLabels","denyCrossTenant","ctx","action","pickOwned","rows","id","row","r","pickOwnedTenanted","assertPayloadTenant","payloadTenantId","ownedRole","roleId","ownedResource","resourceId","ownedGroup","groupId","assertOwnedUser","userId","assertOwnedGrant","grantId","requirePermission","request","query","tenantId","permission","layoutGuard","rolesData","roles","self","resourcesData","grantsData","grants","resources","userRolesData","assignments","users","groupRolesData","groupRoles","groups","graphData","myPermissions","form","intent","roleKey","name","role","appKey","resourceKey","resource","rawAction","group","segmentOf","params","createAuthzAdminApp","opts","server","createAuthzAdminServer","basePath","SEGMENTS","segmentData","actions","loader","args","segment","userId","tenantId","myPermissions","segData","action","act","import_jsx_runtime","ui","Field","label","hint","children","ErrorAlert","message","import_authz_core","import_select_box","import_react","import_react_router","import_react","usePermission","view","readState","writeState","deleteState","import_jsx_runtime","STATE_STYLE","formatResourceTooltip","resourceKey","resourceName","key","name","PermIcon","state","MyPermissionIndicator","permission","expectedResourceKey","showResourceTooltip","labels","showScopeSummary","className","p","usePermission","l","resKey","resName","tooltipText","hasMismatch","renderBadge","label","style","badgeTitle","MyPermissionStatus","prefixLabel","showPrefixLabel","import_jsx_runtime","flattenGroupItems","items","currentLevel","result","item","itemLevel","SIZE_CONFIGS","getPermissionSummary","permission","customLabels","txtNoPerm","txtFullWrite","txtPartialRead","txtReadOnly","GroupItemPill","size","labels","summary","resKey","resName","hasMismatch","cfg","tooltipText","MyPermissionGroupStatus","prefixLabel","variant","className","style","isOpen","setIsOpen","popoverRef","handleClickOutside","event","handleKeyDown","allItems","writeCount","i","readCount","totalCount","summaryLabel","level","isChild","MyPermissionIndicator","import_jsx_runtime","actionError","d","TenantCell","tenantId","testId","ui","tenantOfRole","roles","roleId","labels","role","r","AuthzLayoutShell","children","renderHeader","myPermissions","adminResourceKey","basePath","base","variant","setVariant","tabs","permissionItems","key","perm","items","annotation","MyPermissionGroupStatus","label","isActive","AuthzLayout","makeAuthzErrorBoundary","over","L","resolveLabels","error","routeError","is403","title","message","AuthzErrorBoundary","confirmGuard","active","e","AuthzRolesView","selfAdminRoleIds","C","selfAdmin","ErrorAlert","Field","AuthzResourcesView","resources","GrantRow","g","action","setAction","a","val","AuthzGrantsView","grants","roleOptions","resourceOptions","actionOptions","selectedRole","setSelectedRole","selectedResource","setSelectedResource","selectedAction","setSelectedAction","AuthzUserRolesView","assignments","users","actorUserId","userOptions","u","selectedUser","setSelectedUser","AuthzGroupRolesView","groupRoles","groups","actionData","err","activeRoles","groupOptions","selectedGroup","setSelectedGroup","ACTION_COLORS","AuthzTopologyMap","selectedNode","onNodeClick","hoveredNode","setHoveredNode","connectedUserIds","connectedGroupIds","connectedRoleIds","connectedResourceIds","activeUsers","activeGroups","activeResources","res","getResourceActions","resId","resGrants","uniqueActions","getResourceCardHeight","resourceCardGap","cumulativeResY","resourceYMap","actions","height","top","actionYMap","act","actIdx","actY","totalResourceHeight","stdCardHeight","stdCardGap","stdItemHeight","totalRoleHeight","totalGroupHeight","totalUserHeight","svgHeight","resourceStartTop","roleStartTop","groupStartTop","userStartTop","getResourceActionCenterY","info","relativeActY","getRoleCenterY","roleIndex","getGroupCenterY","groupIndex","getUserCenterY","userIndex","resourceX","roleX","groupX","userX","activeNode","isUserActive","uId","roleIds","isGroupActive","gId","gr","isRoleActive","rId","isResourceActive","groupRoleIds","userRoleIds","isUserRoleLinkActive","isGroupRoleLinkActive","isRoleResourceLinkActive","userRoleLinks","uIdx","rIdx","roleAssignments","item","assignIdx","totalAssigns","offset","groupRoleLinks","gIdx","gRoleAssigns","roleResourceLinks","resIdx","sameResourceActionGrants","actionRoleIdx","totalSameGrants","colorConf","color","link","grantItem","y1","y2","cX1","cX2","isSelected","cardInfo","cardTop","i","y","AuthzGraphView","setSelectedNode","searchQuery","setSearchQuery","viewMode","setViewMode","roleMap","resourceMap","groupMap","userMap","isUserRelated","userId","roleIdsForResource","isGroupRelated","groupId","isRoleRelated","isResourceRelated","resourceId","query","filteredUsers","filteredGroups","filteredRoles","filteredResources","selectedUserObj","selectedGroupObj","selectedRoleObj","selectedResourceObj","handleNodeClick","type","id","resourceGrants","actStyle","roleGrants","roleGroupAssignments","assignedRoles","userAssignments","SEGMENT_VIEWS","AuthzAdminAppView","segment","View","defineResources","resources","result","k","item","resourceKeysOf","registry","import_authz_core","import_react_router","PKG","AuthzUsageError","message","toPermissionView","resourceKey","perm","canRead","canCreate","canUpdate","canDelete","readScope","readPartial","describe","value","assertResourceKeys","label","hint","seen","keys","key","index","assertPermissionRecord","loaded","shape","assertPermissionQuery","query","action","extra","permissionForTenant","tenantId","createAuthz","config","resolveGroupIds","request","resolveTenantId","loadPermissions","resourceKeys","userId","groupIds","requirePermission","options","onDeny","result","ctx","args","planResourceKeys","getMyPermissions","plan","permissions"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/admin/app.ts","../src/admin/server.ts","../src/admin/labels.ts","../src/admin/ui.tsx","../src/admin/views.tsx","../src/MyPermissionGroupStatus.tsx","../src/usePermission.ts","../src/MyPermissionIndicator.tsx","../src/resources.ts","../src/server.ts"],"sourcesContent":["/**\n * Public entry point of the React Router authorization adapter.\n * React Router 向け認可アダプタの公開エントリポイント。\n */\nexport * from \"./admin/app\"\nexport * from \"./admin/labels\"\nexport * from \"./admin/server\"\nexport * from \"./admin/ui\"\nexport * from \"./admin/views\"\nexport * from \"./MyPermissionGroupStatus\"\nexport * from \"./MyPermissionIndicator\"\nexport * from \"./resources\"\nexport * from \"./server\"\nexport * from \"./usePermission\"\n","/**\n * Server wiring that mounts the whole authorization admin UI on **one splat route** (`authz/*`).\n * A host needs a single `routes.ts` entry plus one minimal mount file to serve every tab — no\n * per-tab route directory or file.\n * 認可管理 UI を **スプラット 1 ルート (`authz/*`)** にマウントするためのサーバ結線。\n * `routes.ts` の 1 エントリ + 極小のマウントファイル 1 個で全タブを提供できる形\n * (タブごとのルートディレクトリ / ファイルが不要)。\n *\n * The returned `loader` / `action` are store-dependent (server side); the component is the\n * store-free `AuthzAdminAppView` (`views.tsx`) and the error boundary is `makeAuthzErrorBoundary`.\n * 返り値の `loader` / `action` は store 依存 (サーバ)。Component は store 非依存の\n * `AuthzAdminAppView` (`views.tsx`)、ErrorBoundary は `makeAuthzErrorBoundary` を使う形。\n */\nimport { data, redirect } from \"react-router\"\nimport { type CreateAuthzAdminServerOptions, createAuthzAdminServer } from \"./server\"\n\ntype RouteArgs = { request: Request; params: Record<string, string | undefined> }\n\nexport type CreateAuthzAdminAppOptions = CreateAuthzAdminServerOptions & {\n /** マウントのベースパス(タブリンク/未指定セグメントのリダイレクト先)。既定 `/authz`。 */\n basePath?: string\n}\n\n/**\n * Take the leading segment out of the splat param (`roles/...` → `roles`).\n * スプラット param (\"*\") から先頭セグメントを取り出す処理。\n *\n * @returns The first path segment (empty string when absent). 先頭のパスセグメント (無ければ空文字)。\n */\nfunction segmentOf(params: Record<string, string | undefined>): string {\n return String(params[\"*\"] ?? \"\").split(\"/\")[0]\n}\n\n/**\n * Build the loader/action pair that serves the whole authorization admin UI from one splat route.\n * 認可管理 UI 全体を 1 本のスプラットルートで提供する loader/action 対を生成するファクトリ。\n *\n * @returns The route `loader` / `action` plus the underlying server for direct use. ルートの loader/action と内部サーバ。\n */\nexport function createAuthzAdminApp(opts: CreateAuthzAdminAppOptions) {\n const server = createAuthzAdminServer(opts)\n const basePath = opts.basePath ?? \"/authz\"\n const SEGMENTS = [\"roles\", \"resources\", \"role_permissions\", \"user_roles\", \"group_roles\", \"graph\"] as const\n // データ取得(ガード無し)。ガードはスプラット loader で 1 回だけ行い、ガード済み文脈\n // (テナント + userId) を渡す。全セグメントが同じ引数形なので、テナントの渡し忘れは型で落ちる。\n const segmentData = { roles: server.roles.data, resources: server.resources.data, role_permissions: server.grants.data, user_roles: server.userRoles.data, group_roles: server.groupRoles.data, graph: server.graph.data }\n // Map で持つ。素のオブジェクトだと `POST /authz/toString` が prototype 継承のメソッドを引き当て、\n // ガードを 1 度も通らずに「ハンドラ」が実行されてしまう。\n const actions = new Map<string, (args: RouteArgs) => Promise<unknown>>([\n [\"roles\", server.roles.action],\n [\"resources\", server.resources.action],\n [\"role_permissions\", server.grants.action],\n [\"user_roles\", server.userRoles.action],\n [\"group_roles\", server.groupRoles.action],\n ])\n\n /**\n * Splat loader: validate the segment, guard once, then load that segment's data.\n * スプラット loader。セグメントを検証し、ガードを 1 回だけ通してから該当データを読む処理。\n *\n * @returns The segment's data merged with the layout shell's fields. セグメントのデータとレイアウト用フィールド。\n */\n async function loader(args: RouteArgs) {\n const segment = segmentOf(args.params)\n if (!(SEGMENTS as readonly string[]).includes(segment)) throw redirect(`${basePath}/roles`)\n // segment は上で SEGMENTS に含まれることを確認済み (prototype 継承キーは到達しない)\n // ガードは 1 回だけ(requireUser + requirePermission(read))。userId と自己権限を取り回して再ガード/再取得しない。\n const { userId, tenantId, myPermissions } = await server.layoutGuard(args.request)\n const segData = await segmentData[segment as (typeof SEGMENTS)[number]]({ tenantId, userId })\n // segData は labels と tenantId を含む(各 data 関数が付与)。myPermissions/adminResourceKey はレイアウト殻用。\n return { segment, basePath, myPermissions, adminResourceKey: server.resourceKey, ...segData }\n }\n\n /**\n * Splat action: dispatch to the segment's own handler, or 400 when the segment has none.\n * スプラット action。セグメント固有のハンドラへ振り分け、無ければ 400 を返す処理。\n *\n * @returns The handler's result, or a 400 payload. ハンドラの結果、または 400 のペイロード。\n */\n async function action(args: RouteArgs) {\n const segment = segmentOf(args.params)\n const act = actions.get(segment)\n if (!act) return data({ error: \"unknown action\" }, { status: 400 })\n return act(args)\n }\n\n // server: 個別タブの loader/action を露出(統合テスト等から直接呼ぶ用)。\n return { loader, action, server }\n}\n","/**\n * Server wiring for the authorization admin UI: a factory that binds the store port to the app's own\n * guard / session and emits each route's loader and action.\n * 認可管理 UI のサーバ結線 (loader/action 工場)。store ポート + アプリのガード/セッションを束ね、\n * 各ルートの loader/action を生成する。返り値の loader データには解決済みラベルを含める。\n * store(drizzle)を渡すのはこの工場の呼び出し側(アプリのサーバモジュール)に限る。\n *\n * **Tenant contract**: the screen can only ever act within the single tenant the guard resolved.\n * Every read is passed to the store together with that `tenantId`, and every write re-loads the\n * target row server-side to prove it really belongs to that tenant — having rendered a filtered\n * listing is not an authorization check.\n * **テナント契約**: 画面が扱えるのは、ガードが解決した 1 つのテナントだけ。読み取りは必ず\n * `tenantId` を伴ってストアへ渡し、書き込みは対象行が本当にそのテナントのものかをサーバ側で\n * 引き直して確認する(絞り込まれた一覧を描画したことは認可の証明にならないため)。\n */\nimport { AUTHZ_ACTIONS, type AuthzAction, type AuthzAdminStore, AuthzDeniedError, type AuthzGroupSummary, type AuthzResource, type AuthzRole, assertTenantId, isSameTenant, RESERVED_ROLE_ANONYMOUS, RESERVED_ROLE_AUTHENTICATED } from \"@aiquants/authz-core\"\nimport { data, redirect } from \"react-router\"\nimport type { PermissionView } from \"../server\"\nimport { type AuthzAdminLabels, type PartialAuthzAdminLabels, resolveLabels } from \"./labels\"\nimport type { AuthzGrantsData, AuthzGraphData, AuthzGroupRolesData, AuthzLayoutData, AuthzResourcesData, AuthzRolesData, AuthzUserRolesData } from \"./views\"\n\n/**\n * Tells whether a role key is one of the two reserved subject roles.\n * role_key が 2 つの予約ロールのいずれかであるかの判定。\n *\n * @param roleKey Role key to test. 判定する role_key。\n * @returns True for `@authenticated` / `@anonymous`. 予約ロールなら true。\n */\nfunction isReservedRoleKey(roleKey: string): boolean {\n return roleKey === RESERVED_ROLE_AUTHENTICATED || roleKey === RESERVED_ROLE_ANONYMOUS\n}\n\ntype Req = { request: Request }\ntype PermissionQuery = { resourceKey: string; action: AuthzAction }\n\n/**\n * Everything a guarded admin request resolved to: the tenant it acts within and the acting user.\n * ガード済みの管理リクエストが解決した文脈 (操作対象テナントと実行ユーザー)。\n *\n * Both fields are required. The tenant is what every store call is scoped by, and the user id is what\n * the audit columns and the \"last administrator\" warning are computed from; neither has a meaningful\n * \"absent\" value on this route.\n * 双方必須 (テナントは全ストア呼び出しの絞り込み、ユーザー ID は監査列と自己管理者判定の基準)。\n */\nexport type AuthzAdminContext = { tenantId: string; userId: string | number }\n\nexport type CreateAuthzAdminServerOptions = {\n store: AuthzAdminStore\n /** 管理画面のガード資源キー(既定 `authz_admin`)。 */\n resourceKey?: string\n /** 未ログインをログインへ誘導する(例: throw redirect(...))。 */\n requireUser: (request: Request) => Promise<unknown>\n /**\n * Authorization guard supplied by the app (from `createAuthz`); allow → `{ userId, tenantId, permission }`.\n * 認可ガード (アプリの createAuthz 由来)。allow で { userId, tenantId, permission } を返すポート。\n * `userId` は nullable(未解決/万一の public 付与)であり、createAuthzAdminServer 内で deny する\n * (管理経路は監査・自己権限計算に実 id が必須のため非 null を保証)。\n * `tenantId` は **ガードが判定に使ったテナントそのもの**。ここで捨てて画面側が別経路で引き直すと、\n * 「A テナントの権限で B テナントを編集できる」という越境が構造的に開く。\n * `permission` は **同一判定から導かれた** 自己権限ビューであり、表示用に再取得しない\n * (再取得は「ガードは通るのにバッジは権限なし」という乖離の温床)。\n */\n requirePermission: (request: Request, query: PermissionQuery) => Promise<{ userId: string | number | null; tenantId: string; permission: PermissionView }>\n /** 文言の部分上書き(既定は日本語)。 */\n labels?: PartialAuthzAdminLabels\n}\n\n/** Whether a caught value is a `Response` to be rethrown as-is. 捕捉値がそのまま再送出すべき `Response` かの判定。 */\nconst isResponse = (e: unknown): e is Response => e instanceof Response\n\n/**\n * Convert an exception raised inside an action into `403` (authorization denials only) or `400` (validation etc.).\n * action 内の例外を 403(認可拒否のみ)/ 400(検証等)へ変換する処理。\n *\n * @param e The caught value. 捕捉した値。\n * @returns A `400` data payload for a non-authorization failure. 認可以外の失敗に対する 400 応答。\n * @throws {Response} The original `Response`, or a `403` for an {@link AuthzDeniedError}. 元の `Response`、または拒否時の 403。\n */\nfunction toActionError(e: unknown) {\n if (isResponse(e)) throw e\n if (e instanceof AuthzDeniedError) throw new Response(e.message, { status: 403 })\n return data({ error: e instanceof Error ? e.message : \"エラーが発生しました\" }, { status: 400 })\n}\n\n/**\n * Build the authorization admin UI's server side: one guard seam, per-tab data loaders, and the\n * loader / action pair of every route.\n * 認可管理 UI のサーバ側 (単一のガードシーム、タブ別データ取得、各ルートの loader/action) を構築する処理。\n *\n * Every route entry is guarded through the app-supplied `requirePermission`, whose result — the\n * tenant, the acting user, and the self-permission view — comes from **one** evaluation and is\n * carried onward as an {@link AuthzAdminContext}. Nothing here re-derives the tenant from the request\n * or re-loads the permission for display: a second lookup can disagree with the first, which is\n * exactly the \"guard allows but the badge says denied\" failure, and re-deriving the tenant is how a\n * screen ends up editing a tenant the guard never authorized.\n * 各ルートはアプリ由来の `requirePermission` を通し、その 1 回の判定から得たテナント・実行ユーザー・\n * 自己権限を {@link AuthzAdminContext} として持ち回る形 (テナントの引き直しも表示用の再取得も行わない)。\n *\n * Writes never trust the submitted ids: each one is re-loaded from a tenant-scoped listing and, where\n * the row carries its own tenant, compared against it. A missing row and a foreign row are answered\n * identically, so the screen cannot be used as a cross-tenant existence oracle.\n * 書き込みは送信された id を信用せず、テナント絞り込みの一覧から引き直して帰属を確認する形\n * (「存在しない」と「別テナント」は同じ応答にし、他テナントの存在確認手段にしない)。\n *\n * @param opts Store port, guard seams, admin resource key and label overrides.\n * store ポート・ガードシーム・管理リソースキー・文言上書き。\n * @returns The labels, the guarded resource key, `layoutGuard`, and every route's loader / action.\n * 文言・ガード対象リソースキー・`layoutGuard`・各ルートの loader/action。\n */\nexport function createAuthzAdminServer(opts: CreateAuthzAdminServerOptions) {\n const store = opts.store\n const RES = opts.resourceKey ?? \"authz_admin\"\n const labels: AuthzAdminLabels = resolveLabels(opts.labels)\n\n /**\n * Refuse an operation whose target does not belong to the request's tenant (fail-close, 403).\n * 要求テナントに属さない対象への操作を拒否する処理 (fail-close、403)。\n *\n * A missing row and a foreign row are deliberately indistinguishable: answering them differently\n * would turn this screen into a cross-tenant existence oracle for role / resource / grant ids.\n * 「存在しない」と「別テナント」を区別しない (区別自体が他テナントの存在確認手段になるため)。\n *\n * @param ctx The guarded tenant and acting user. ガード済みのテナントと実行ユーザー。\n * @param action The admin action being attempted. 試みられた管理操作。\n * @returns Never returns; always throws. 常に送出 (正常復帰しない)。\n * @throws {AuthzDeniedError} Always, with reason `tenant-mismatch`. 常に `tenant-mismatch` で拒否。\n */\n function denyCrossTenant(ctx: AuthzAdminContext, action: AuthzAction): never {\n throw new AuthzDeniedError({ appKey: \"\", resourceKey: RES, action, reason: \"tenant-mismatch\", tenantId: ctx.tenantId, userId: ctx.userId })\n }\n\n /**\n * Take the row with `id` out of a tenant-scoped listing, denying when it is not present.\n * テナントで絞り込んだ一覧から id の行を取り出し、無ければ拒否する処理。\n *\n * Used for the join rows (grants / assignments) whose DTOs carry no tenant of their own: for them\n * membership in the tenant-scoped listing is the ownership proof available.\n * テナント列を持たない結合行 (付与・割当) 用。絞り込み一覧への所属が唯一の所有証明となる形。\n *\n * @param rows Rows loaded for `ctx.tenantId`. `ctx.tenantId` で読み込んだ行群。\n * @param id The id the form asked to act on. フォームが指定した対象 id。\n * @param ctx The guarded tenant and acting user. ガード済みのテナントと実行ユーザー。\n * @param action The admin action being attempted. 試みられた管理操作。\n * @returns The matching row. 一致した行。\n * @throws {AuthzDeniedError} When no row matches. 一致する行が無い場合。\n */\n function pickOwned<T extends { id: number }>(rows: T[], id: number, ctx: AuthzAdminContext, action: AuthzAction): T {\n const row = rows.find((r) => r.id === id)\n if (!row) denyCrossTenant(ctx, action)\n return row\n }\n\n /**\n * Same as {@link pickOwned}, additionally comparing the row's **own** projected tenant.\n * {@link pickOwned} に加えて、行が自ら持つ (DB から射影された) テナントまで突き合わせる処理。\n *\n * The listing was already filtered, but a filtered listing is not an authorization check: if the\n * store ever forgot its predicate, every id in the database would look owned. Comparing the value\n * the row itself carries is what actually proves ownership.\n * 一覧の絞り込みは認可ではない (述語が抜ければ全 id が自テナントに見える)。行が名乗る値との突合が所有の証明。\n *\n * @param rows Rows loaded for `ctx.tenantId`. `ctx.tenantId` で読み込んだ行群。\n * @param id The id the form asked to act on. フォームが指定した対象 id。\n * @param ctx The guarded tenant and acting user. ガード済みのテナントと実行ユーザー。\n * @param action The admin action being attempted. 試みられた管理操作。\n * @returns The matching row, proven to belong to the tenant. 帰属を確認した行。\n * @throws {AuthzDeniedError} When absent or stamped with another tenant. 不在または別テナントの場合。\n */\n function pickOwnedTenanted<T extends { id: number; tenantId: string }>(rows: T[], id: number, ctx: AuthzAdminContext, action: AuthzAction): T {\n const row = pickOwned(rows, id, ctx, action)\n if (!isSameTenant(ctx.tenantId, row.tenantId)) denyCrossTenant(ctx, action)\n return row\n }\n\n /**\n * Reject a submitted payload that names a tenant other than the request's own.\n * 送信されたペイロードが要求テナント以外を名乗る場合に拒否する処理。\n *\n * The form field and the guard both name a tenant; neither side is preferred. They must agree, or\n * the write is refused — accepting the form's value would let any admin write into any tenant.\n * フォーム値とガード値のどちらも優先せず、一致しなければ書き込みを拒否する形 (フォーム採用は越境そのもの)。\n *\n * @param ctx The guarded tenant and acting user. ガード済みのテナントと実行ユーザー。\n * @param action The admin action being attempted. 試みられた管理操作。\n * @param payloadTenantId The tenant the submitted payload targets. ペイロードが対象とするテナント。\n * @returns Nothing; returns normally only when the two agree. 一致時のみ正常復帰。\n * @throws {AuthzDeniedError} When the two tenants differ. 不一致の場合。\n */\n function assertPayloadTenant(ctx: AuthzAdminContext, action: AuthzAction, payloadTenantId: string): void {\n if (!isSameTenant(ctx.tenantId, payloadTenantId)) denyCrossTenant(ctx, action)\n }\n\n /**\n * Load the tenant's roles and prove `roleId` is one of them.\n * 当該テナントのロールを読み、`roleId` がその中の 1 件であることを確認する処理。\n *\n * @param ctx The guarded tenant and acting user. ガード済みのテナントと実行ユーザー。\n * @param action The admin action being attempted. 試みられた管理操作。\n * @param roleId The role the form asked to act on. フォームが指定したロール id。\n * @returns The owned role row. 帰属を確認したロール行。\n * @throws {AuthzDeniedError} When the role is absent or foreign. 不在または別テナントの場合。\n */\n async function ownedRole(ctx: AuthzAdminContext, action: AuthzAction, roleId: number): Promise<AuthzRole> {\n return pickOwnedTenanted(await store.listRoles(ctx.tenantId), roleId, ctx, action)\n }\n\n /**\n * Load the tenant's resources and prove `resourceId` is one of them.\n * 当該テナントのリソースを読み、`resourceId` がその中の 1 件であることを確認する処理。\n *\n * @param ctx The guarded tenant and acting user. ガード済みのテナントと実行ユーザー。\n * @param action The admin action being attempted. 試みられた管理操作。\n * @param resourceId The resource the form asked to act on. フォームが指定したリソース id。\n * @returns The owned resource row. 帰属を確認したリソース行。\n * @throws {AuthzDeniedError} When the resource is absent or foreign. 不在または別テナントの場合。\n */\n async function ownedResource(ctx: AuthzAdminContext, action: AuthzAction, resourceId: number): Promise<AuthzResource> {\n return pickOwnedTenanted(await store.listResources(ctx.tenantId), resourceId, ctx, action)\n }\n\n /**\n * Load the tenant's groups and prove `groupId` is one of them.\n * 当該テナントのグループを読み、`groupId` がその中の 1 件であることを確認する処理。\n *\n * @param ctx The guarded tenant and acting user. ガード済みのテナントと実行ユーザー。\n * @param action The admin action being attempted. 試みられた管理操作。\n * @param groupId The group the form asked to act on. フォームが指定したグループ id。\n * @returns The owned group row. 帰属を確認したグループ行。\n * @throws {AuthzDeniedError} When the group is absent or foreign. 不在または別テナントの場合。\n */\n async function ownedGroup(ctx: AuthzAdminContext, action: AuthzAction, groupId: number): Promise<AuthzGroupSummary> {\n return pickOwnedTenanted(await store.listGroups(ctx.tenantId), groupId, ctx, action)\n }\n\n /**\n * Prove `userId` is an assignable user of the tenant (TDUserRole itself has no tenant column).\n * `userId` が当該テナントの割当候補ユーザーであることを確認する処理 (TDUserRole 自体はテナント列を持たない)。\n *\n * @param ctx The guarded tenant and acting user. ガード済みのテナントと実行ユーザー。\n * @param action The admin action being attempted. 試みられた管理操作。\n * @param userId The user the form asked to act on. フォームが指定したユーザー id。\n * @returns Nothing; returns normally only when the user belongs. 帰属時のみ正常復帰。\n * @throws {AuthzDeniedError} When the user is not in the tenant. 当該テナントに居ない場合。\n */\n async function assertOwnedUser(ctx: AuthzAdminContext, action: AuthzAction, userId: number): Promise<void> {\n pickOwned(await store.listUsers(ctx.tenantId), userId, ctx, action)\n }\n\n /**\n * Prove `grantId` is one of the tenant's grants (TDRolePermission has no tenant column of its own).\n * `grantId` が当該テナントの付与行であることを確認する処理 (TDRolePermission 自体はテナント列を持たない)。\n *\n * @param ctx The guarded tenant and acting user. ガード済みのテナントと実行ユーザー。\n * @param action The admin action being attempted. 試みられた管理操作。\n * @param grantId The grant the form asked to act on. フォームが指定した付与 id。\n * @returns Nothing; returns normally only when the grant belongs. 帰属時のみ正常復帰。\n * @throws {AuthzDeniedError} When the grant is absent or foreign. 不在または別テナントの場合。\n */\n async function assertOwnedGrant(ctx: AuthzAdminContext, action: AuthzAction, grantId: number): Promise<void> {\n pickOwned(await store.listGrants(ctx.tenantId), grantId, ctx, action)\n }\n\n /**\n * Boundary guard: resolve the request into a complete {@link AuthzAdminContext} exactly once.\n * 境界ガード。リクエストを完全な {@link AuthzAdminContext} へ 1 度だけ解決する処理。\n *\n * 未解決ユーザー(userId==null)と空テナントをここで 1 回だけ弾く。これ以降 `ctx.userId` は非 null、\n * `ctx.tenantId` は非空が保証される。管理リソースは public を付与しない前提だが、防御的に拒否する\n * (監査・自己権限計算に実 id が必須のため)。\n *\n * @param request The incoming request. 受信したリクエスト。\n * @param query The admin resource and action to guard. ガードする管理リソースとアクション。\n * @returns The guarded context plus the self-permission view from that same evaluation. ガード済み文脈と同一判定由来の自己権限。\n * @throws {AuthzTenantError} When the guard resolved a blank tenant. テナントが空の場合。\n * @throws {AuthzDeniedError} When the acting user is unresolved. 実行ユーザーが未解決の場合。\n */\n const requirePermission = async (request: Request, query: PermissionQuery): Promise<AuthzAdminContext & { permission: PermissionView }> => {\n const { userId, tenantId, permission } = await opts.requirePermission(request, query)\n // テナント未解決は「全テナント」ではなく契約違反。ユーザー判定より前に落とす\n assertTenantId(tenantId, \"createAuthzAdminServer(requirePermission.tenantId)\")\n if (userId == null) {\n throw new AuthzDeniedError({ appKey: \"\", resourceKey: query.resourceKey, action: query.action, reason: \"unresolved-user\", tenantId, userId: null })\n }\n return { userId, tenantId, permission }\n }\n\n /**\n * Layout guard: `requireUser` (302) → `requirePermission(read)` (403), run exactly once, returning the\n * guarded context together with the self-permission map derived from that **same** evaluation.\n * レイアウトガード。1 回だけ実行し、ガード済み文脈と同じ判定から導かれた自己権限マップを返す処理\n * (表示用の追加問い合わせは行わない)。\n *\n * @param request The incoming request. 受信したリクエスト。\n * @returns The guarded context and the admin resource's permission view. ガード済み文脈と管理リソースの権限ビュー。\n */\n async function layoutGuard(request: Request): Promise<AuthzAdminContext & { myPermissions: Record<string, PermissionView | undefined> }> {\n await opts.requireUser(request)\n try {\n const { userId, tenantId, permission } = await requirePermission(request, { resourceKey: RES, action: \"read\" })\n return { userId, tenantId, myPermissions: { [RES]: permission } }\n } catch (e) {\n if (e instanceof AuthzDeniedError) throw new Response(`Forbidden: ${RES}`, { status: 403 })\n throw e\n }\n }\n\n // ---- データ取得(ガード無し)。ガード済みの文脈(テナント + userId)を受け取る。 ----\n // 全タブが同じ引数形になっているのは、スプラット結線 (app.ts) がセグメントで引いた関数を\n // そのまま呼べるようにするため。ここが関数ごとに違う形だと、渡し忘れが型で見えなくなる。\n /**\n * Roles tab data: the tenant's roles plus the actor's own admin-backing role ids.\n * ロールタブのデータ (当該テナントのロールと、実行者の管理権限を支えるロール ID)。\n *\n * @param ctx The guarded tenant and acting user. ガード済みのテナントと実行ユーザー。\n * @returns The roles tab payload. ロールタブの表示データ。\n */\n const rolesData = async (ctx: AuthzAdminContext): Promise<AuthzRolesData> => {\n const [roles, self] = await Promise.all([store.listRoles(ctx.tenantId), store.getSelfAdminContext(ctx.tenantId, Number(ctx.userId))])\n return { roles, selfAdminRoleIds: self.selfAdminRoleIds, tenantId: ctx.tenantId, labels }\n }\n /**\n * Resources tab data: the tenant's resources.\n * リソースタブのデータ (当該テナントのリソース)。\n *\n * @param ctx The guarded tenant and acting user. ガード済みのテナントと実行ユーザー。\n * @returns The resources tab payload. リソースタブの表示データ。\n */\n const resourcesData = async (ctx: AuthzAdminContext): Promise<AuthzResourcesData> => ({ resources: await store.listResources(ctx.tenantId), tenantId: ctx.tenantId, labels })\n /**\n * Grants tab data: the tenant's grants, plus its roles and resources as form choices.\n * 権限付与タブのデータ (当該テナントの付与と、フォームの選択肢となるロール・リソース)。\n *\n * @param ctx The guarded tenant and acting user. ガード済みのテナントと実行ユーザー。\n * @returns The grants tab payload. 権限付与タブの表示データ。\n */\n const grantsData = async (ctx: AuthzAdminContext): Promise<AuthzGrantsData> => {\n const [grants, roles, resources] = await Promise.all([store.listGrants(ctx.tenantId), store.listRoles(ctx.tenantId), store.listResources(ctx.tenantId)])\n return { grants, roles, resources, tenantId: ctx.tenantId, labels }\n }\n /**\n * User-roles tab data: the tenant's assignments, its roles and assignable users, and the actor's\n * own admin-backing role ids.\n * ユーザー割当タブのデータ (当該テナントの割当・ロール・割当候補ユーザーと、実行者の管理権限を支えるロール ID)。\n *\n * @param ctx The guarded tenant and acting user. ガード済みのテナントと実行ユーザー。\n * @returns The user-roles tab payload. ユーザー割当タブの表示データ。\n */\n const userRolesData = async (ctx: AuthzAdminContext): Promise<AuthzUserRolesData> => {\n const [assignments, roles, users, self] = await Promise.all([store.listUserRoles(ctx.tenantId), store.listRoles(ctx.tenantId), store.listUsers(ctx.tenantId), store.getSelfAdminContext(ctx.tenantId, Number(ctx.userId))])\n return { assignments, roles, users, actorUserId: self.actorUserId, selfAdminRoleIds: self.selfAdminRoleIds, tenantId: ctx.tenantId, labels }\n }\n /**\n * Group-roles tab data: the tenant's group assignments, plus its roles and groups as form choices.\n * グループ割当タブのデータ (当該テナントのグループ割当と、フォームの選択肢となるロール・グループ)。\n *\n * @param ctx The guarded tenant and acting user. ガード済みのテナントと実行ユーザー。\n * @returns The group-roles tab payload. グループ割当タブの表示データ。\n */\n const groupRolesData = async (ctx: AuthzAdminContext): Promise<AuthzGroupRolesData> => {\n const [groupRoles, roles, groups] = await Promise.all([store.listGroupRoles(ctx.tenantId), store.listRoles(ctx.tenantId), store.listGroups(ctx.tenantId)])\n return { groupRoles, roles, groups, tenantId: ctx.tenantId, labels }\n }\n /**\n * Graph tab data: every listing of the tenant, loaded in parallel for the relationship view.\n * グラフタブのデータ (関係図のために当該テナントの全一覧を並列取得)。\n *\n * @param ctx The guarded tenant and acting user. ガード済みのテナントと実行ユーザー。\n * @returns The graph tab payload. グラフタブの表示データ。\n */\n const graphData = async (ctx: AuthzAdminContext): Promise<AuthzGraphData> => {\n const [users, roles, resources, grants, assignments, groupRoles, groups] = await Promise.all([\n store.listUsers(ctx.tenantId),\n store.listRoles(ctx.tenantId),\n store.listResources(ctx.tenantId),\n store.listGrants(ctx.tenantId),\n store.listUserRoles(ctx.tenantId),\n store.listGroupRoles(ctx.tenantId),\n store.listGroups(ctx.tenantId),\n ])\n return { users, roles, resources, grants, assignments, groupRoles, groups, tenantId: ctx.tenantId, labels }\n }\n\n return {\n /** 露出: スプラットマウント(app.ts)がガードを 1 回だけ行うために使う。 */\n labels,\n resourceKey: RES,\n layoutGuard,\n\n /** /authz レイアウト: requireUser(302) → requirePermission(read, 403)。自己権限はガードの戻り値から得る。 */\n layout: {\n /**\n * Layout loader: guard once, then hand the shell the tenant and the self-permission map.\n * レイアウトの loader。ガードを 1 回だけ行い、テナントと自己権限マップを外枠へ渡す処理。\n *\n * @param args The route arguments (request only). ルート引数 (request のみ)。\n * @returns The layout payload. レイアウトの表示データ。\n * @throws {Response} `302` when unauthenticated, `403` when the read is denied. 未認証は 302、拒否は 403。\n */\n async loader({ request }: Req): Promise<AuthzLayoutData> {\n const { tenantId, myPermissions } = await layoutGuard(request)\n return { myPermissions, tenantId, labels, adminResourceKey: RES }\n },\n },\n\n /** /authz index → roles へ。 */\n index: {\n /**\n * Index loader: the admin root has no view of its own, so it redirects to the roles tab.\n * インデックスの loader。管理ルートは固有の表示を持たないためロールタブへ転送する処理。\n *\n * @returns Never returns; always throws the redirect. 常に転送を送出 (正常復帰しない)。\n * @throws {Response} The `/authz/roles` redirect. `/authz/roles` への転送。\n */\n loader() {\n throw redirect(\"/authz/roles\")\n },\n },\n\n roles: {\n data: rolesData,\n /**\n * Roles tab loader: guard for `read`, then load the tab within the guarded tenant.\n * ロールタブの loader。`read` をガードし、ガード済みテナントの範囲で読み込む処理。\n *\n * @param args The route arguments (request only). ルート引数 (request のみ)。\n * @returns The roles tab payload. ロールタブの表示データ。\n * @throws {AuthzDeniedError} When the read is denied. 読み取りが拒否された場合。\n */\n async loader({ request }: Req): Promise<AuthzRolesData> {\n const ctx = await requirePermission(request, { resourceKey: RES, action: \"read\" })\n return rolesData(ctx)\n },\n /**\n * Roles tab action: create / save / toggle / delete, each guarded for its own verb.\n * ロールタブの action。作成・保存・有効切替・削除を、それぞれの動詞でガードして実行する処理。\n *\n * `create` and `save` additionally reconcile the submitted `tenantId` with the guarded one\n * before the store is called, and every id is re-loaded through `ownedRole`.\n * `create`/`save` は送信された `tenantId` をガード側と突合し、id は必ず `ownedRole` で引き直す形。\n *\n * @param args The route arguments (request only). ルート引数 (request のみ)。\n * @returns `{ ok: true }`, or a `400` payload on a validation failure. 成功、または検証失敗時の 400 応答。\n * @throws {Response} `403` when the guard denies the verb. 動詞のガードが拒否した場合。\n */\n async action({ request }: Req) {\n const form = await request.formData()\n const intent = String(form.get(\"_action\"))\n try {\n switch (intent) {\n case \"create\": {\n const ctx = await requirePermission(request, { resourceKey: RES, action: \"create\" })\n const tenantId = String(form.get(\"tenantId\") ?? \"\").trim()\n const roleKey = String(form.get(\"roleKey\") ?? \"\").trim()\n const name = String(form.get(\"name\") ?? \"\").trim()\n if (!(tenantId && roleKey && name)) return data({ error: \"tenant_id と role_key と name は必須です\" }, { status: 400 })\n assertPayloadTenant(ctx, \"create\", tenantId)\n await store.createRole(ctx.tenantId, { tenantId, roleKey, name, description: String(form.get(\"description\") ?? \"\").trim() || null }, String(ctx.userId))\n return { ok: true }\n }\n case \"save\": {\n const ctx = await requirePermission(request, { resourceKey: RES, action: \"update\" })\n const tenantId = String(form.get(\"tenantId\") ?? \"\").trim()\n const roleKey = String(form.get(\"roleKey\") ?? \"\").trim()\n const name = String(form.get(\"name\") ?? \"\").trim()\n if (!(tenantId && roleKey && name)) return data({ error: \"tenantId と roleKey と name は必須です\" }, { status: 400 })\n assertPayloadTenant(ctx, \"update\", tenantId)\n const role = await ownedRole(ctx, \"update\", Number(form.get(\"id\")))\n // 予約ロールの鍵は動かせない。`@authenticated` を `@anonymous` へ改名できると、\n // 「サインイン済みの誰か」に与えた権限が、そのまま **未認証の訪問者** の権限へ\n // 化ける (逆向きの改名も、匿名向けの付与を全ログイン者へ広げる)\n if (isReservedRoleKey(role.roleKey) || isReservedRoleKey(roleKey)) {\n if (role.roleKey !== roleKey) return data({ error: \"予約ロールの role_key は変更できません\" }, { status: 400 })\n }\n await store.updateRole(ctx.tenantId, role.id, { tenantId, roleKey, name, description: String(form.get(\"description\") ?? \"\").trim() || null }, String(ctx.userId))\n return { ok: true }\n }\n case \"toggle\": {\n const ctx = await requirePermission(request, { resourceKey: RES, action: \"update\" })\n const role = await ownedRole(ctx, \"update\", Number(form.get(\"id\")))\n await store.setRoleActive(ctx.tenantId, role.id, form.get(\"isActive\") === \"true\", String(ctx.userId))\n return { ok: true }\n }\n case \"delete\": {\n const ctx = await requirePermission(request, { resourceKey: RES, action: \"delete\" })\n const role = await ownedRole(ctx, \"delete\", Number(form.get(\"id\")))\n await store.deleteRole(ctx.tenantId, role.id)\n return { ok: true }\n }\n default:\n return data({ error: \"unknown action\" }, { status: 400 })\n }\n } catch (e) {\n return toActionError(e)\n }\n },\n },\n\n resources: {\n data: resourcesData,\n /**\n * Resources tab loader: guard for `read`, then load the tab within the guarded tenant.\n * リソースタブの loader。`read` をガードし、ガード済みテナントの範囲で読み込む処理。\n *\n * @param args The route arguments (request only). ルート引数 (request のみ)。\n * @returns The resources tab payload. リソースタブの表示データ。\n * @throws {AuthzDeniedError} When the read is denied. 読み取りが拒否された場合。\n */\n async loader({ request }: Req): Promise<AuthzResourcesData> {\n const ctx = await requirePermission(request, { resourceKey: RES, action: \"read\" })\n return resourcesData(ctx)\n },\n /**\n * Resources tab action: create / save / delete, each guarded for its own verb.\n * リソースタブの action。作成・保存・削除を、それぞれの動詞でガードして実行する処理。\n *\n * `create` reconciles the submitted `tenantId` with the guarded one; `save` and `delete`\n * re-load the id through `ownedResource` instead of trusting the form.\n * `create` は送信 `tenantId` を突合し、`save`/`delete` は `ownedResource` で id を引き直す形。\n *\n * @param args The route arguments (request only). ルート引数 (request のみ)。\n * @returns `{ ok: true }`, or a `400` payload on a validation failure. 成功、または検証失敗時の 400 応答。\n * @throws {Response} `403` when the guard denies the verb. 動詞のガードが拒否した場合。\n */\n async action({ request }: Req) {\n const form = await request.formData()\n const intent = String(form.get(\"_action\"))\n try {\n switch (intent) {\n case \"create\": {\n const ctx = await requirePermission(request, { resourceKey: RES, action: \"create\" })\n const tenantId = String(form.get(\"tenantId\") ?? \"\").trim()\n const appKey = String(form.get(\"appKey\") ?? \"\").trim()\n const resourceKey = String(form.get(\"resourceKey\") ?? \"\").trim()\n const name = String(form.get(\"name\") ?? \"\").trim()\n if (!(tenantId && appKey && resourceKey && name)) return data({ error: \"tenant_id / app_key / resource_key / name は必須です\" }, { status: 400 })\n assertPayloadTenant(ctx, \"create\", tenantId)\n await store.createResource(ctx.tenantId, { tenantId, appKey, resourceKey, name, description: String(form.get(\"description\") ?? \"\").trim() || null }, String(ctx.userId))\n return { ok: true }\n }\n case \"save\": {\n const ctx = await requirePermission(request, { resourceKey: RES, action: \"update\" })\n const name = String(form.get(\"name\") ?? \"\").trim()\n if (!name) return data({ error: \"name は必須です\" }, { status: 400 })\n const resource = await ownedResource(ctx, \"update\", Number(form.get(\"id\")))\n await store.updateResource(ctx.tenantId, resource.id, { name, description: String(form.get(\"description\") ?? \"\").trim() || null }, String(ctx.userId))\n return { ok: true }\n }\n case \"delete\": {\n const ctx = await requirePermission(request, { resourceKey: RES, action: \"delete\" })\n const resource = await ownedResource(ctx, \"delete\", Number(form.get(\"id\")))\n await store.deleteResource(ctx.tenantId, resource.id)\n return { ok: true }\n }\n default:\n return data({ error: \"unknown action\" }, { status: 400 })\n }\n } catch (e) {\n return toActionError(e)\n }\n },\n },\n\n grants: {\n data: grantsData,\n /**\n * Grants tab loader: guard for `read`, then load the tab within the guarded tenant.\n * 権限付与タブの loader。`read` をガードし、ガード済みテナントの範囲で読み込む処理。\n *\n * @param args The route arguments (request only). ルート引数 (request のみ)。\n * @returns The grants tab payload. 権限付与タブの表示データ。\n * @throws {AuthzDeniedError} When the read is denied. 読み取りが拒否された場合。\n */\n async loader({ request }: Req): Promise<AuthzGrantsData> {\n const ctx = await requirePermission(request, { resourceKey: RES, action: \"read\" })\n return grantsData(ctx)\n },\n /**\n * Grants tab action: create / save / delete, each guarded for its own verb.\n * 権限付与タブの action。作成・保存・削除を、それぞれの動詞でガードして実行する処理。\n *\n * A grant links two rows, so `create` proves **both** the role and the resource belong to\n * the guarded tenant; `save` and `delete` prove the grant itself through `assertOwnedGrant`.\n * 付与は 2 行を結ぶため `create` はロール・リソース双方の帰属を証明し、`save`/`delete` は\n * `assertOwnedGrant` で付与自体の帰属を証明する形。\n *\n * @param args The route arguments (request only). ルート引数 (request のみ)。\n * @returns `{ ok: true }`, or a `400` payload on a validation failure. 成功、または検証失敗時の 400 応答。\n * @throws {Response} `403` when the guard denies the verb. 動詞のガードが拒否した場合。\n */\n async action({ request }: Req) {\n const form = await request.formData()\n const intent = String(form.get(\"_action\"))\n try {\n switch (intent) {\n case \"create\": {\n const ctx = await requirePermission(request, { resourceKey: RES, action: \"create\" })\n const tenantId = String(form.get(\"tenantId\") ?? \"\").trim()\n const roleId = Number(form.get(\"roleId\"))\n const resourceId = Number(form.get(\"resourceId\"))\n if (!(tenantId && roleId && resourceId)) return data({ error: \"tenant_id とロールとリソースを選択してください\" }, { status: 400 })\n const rawAction = String(form.get(\"action\") ?? \"\")\n if (!(AUTHZ_ACTIONS as readonly string[]).includes(rawAction)) return data({ error: \"無効な action です\" }, { status: 400 })\n const action = rawAction as AuthzAction\n assertPayloadTenant(ctx, \"create\", tenantId)\n // 付与は 2 つの行を結び付ける。どちらか片方でも他テナントなら越境の橋になる\n const role = await ownedRole(ctx, \"create\", roleId)\n const resource = await ownedResource(ctx, \"create\", resourceId)\n await store.createGrant(ctx.tenantId, { tenantId, roleId: role.id, resourceId: resource.id, action, rowScope: String(form.get(\"rowScope\") ?? \"\"), columnScope: String(form.get(\"columnScope\") ?? \"\") }, String(ctx.userId))\n return { ok: true }\n }\n case \"save\": {\n const ctx = await requirePermission(request, { resourceKey: RES, action: \"update\" })\n const rawAction = String(form.get(\"action\") ?? \"\")\n if (!(AUTHZ_ACTIONS as readonly string[]).includes(rawAction)) return data({ error: \"無効な action です\" }, { status: 400 })\n const action = rawAction as AuthzAction\n const grantId = Number(form.get(\"id\"))\n await assertOwnedGrant(ctx, \"update\", grantId)\n await store.updateGrant(ctx.tenantId, grantId, { action, rowScope: String(form.get(\"rowScope\") ?? \"\"), columnScope: String(form.get(\"columnScope\") ?? \"\") }, String(ctx.userId))\n return { ok: true }\n }\n case \"delete\": {\n const ctx = await requirePermission(request, { resourceKey: RES, action: \"delete\" })\n const grantId = Number(form.get(\"id\"))\n await assertOwnedGrant(ctx, \"delete\", grantId)\n await store.deleteGrant(ctx.tenantId, grantId)\n return { ok: true }\n }\n default:\n return data({ error: \"unknown action\" }, { status: 400 })\n }\n } catch (e) {\n return toActionError(e)\n }\n },\n },\n\n userRoles: {\n data: userRolesData,\n /**\n * User-roles tab loader: guard for `read`, then load the tab within the guarded tenant.\n * ユーザー割当タブの loader。`read` をガードし、ガード済みテナントの範囲で読み込む処理。\n *\n * @param args The route arguments (request only). ルート引数 (request のみ)。\n * @returns The user-roles tab payload. ユーザー割当タブの表示データ。\n * @throws {AuthzDeniedError} When the read is denied. 読み取りが拒否された場合。\n */\n async loader({ request }: Req): Promise<AuthzUserRolesData> {\n const ctx = await requirePermission(request, { resourceKey: RES, action: \"read\" })\n return userRolesData(ctx)\n },\n /**\n * User-roles tab action: assign / remove, each guarded for its own verb.\n * ユーザー割当タブの action。割当・解除を、それぞれの動詞でガードして実行する処理。\n *\n * The assignment row carries no tenant of its own, so both ends are proven — the user\n * through `assertOwnedUser` and the role through `ownedRole` — before the store is called.\n * 割当行はテナントを持たないため、ユーザー側 (`assertOwnedUser`) とロール側 (`ownedRole`) の\n * 両端の帰属を証明してからストアを呼ぶ形。\n *\n * @param args The route arguments (request only). ルート引数 (request のみ)。\n * @returns `{ ok: true }`, or a `400` payload on a validation failure. 成功、または検証失敗時の 400 応答。\n * @throws {Response} `403` when the guard denies the verb. 動詞のガードが拒否した場合。\n */\n async action({ request }: Req) {\n const form = await request.formData()\n const intent = String(form.get(\"_action\"))\n try {\n switch (intent) {\n case \"assign\": {\n const ctx = await requirePermission(request, { resourceKey: RES, action: \"create\" })\n const userId = Number(form.get(\"userId\"))\n const roleId = Number(form.get(\"roleId\"))\n if (!(userId && roleId)) return data({ error: \"ユーザーとロールを選択してください\" }, { status: 400 })\n // 割当行 (TDUserRole) はテナント列を持たない。両端の帰属を確かめて初めてテナント内に閉じる\n await assertOwnedUser(ctx, \"create\", userId)\n const role = await ownedRole(ctx, \"create\", roleId)\n await store.assignUserRole(ctx.tenantId, userId, role.id, String(ctx.userId))\n return { ok: true }\n }\n case \"remove\": {\n const ctx = await requirePermission(request, { resourceKey: RES, action: \"delete\" })\n const userId = Number(form.get(\"userId\"))\n await assertOwnedUser(ctx, \"delete\", userId)\n const role = await ownedRole(ctx, \"delete\", Number(form.get(\"roleId\")))\n await store.removeUserRole(ctx.tenantId, userId, role.id)\n return { ok: true }\n }\n default:\n return data({ error: \"unknown action\" }, { status: 400 })\n }\n } catch (e) {\n return toActionError(e)\n }\n },\n },\n\n groupRoles: {\n data: groupRolesData,\n /**\n * Group-roles tab loader: guard for `read`, then load the tab within the guarded tenant.\n * グループ割当タブの loader。`read` をガードし、ガード済みテナントの範囲で読み込む処理。\n *\n * @param args The route arguments (request only). ルート引数 (request のみ)。\n * @returns The group-roles tab payload. グループ割当タブの表示データ。\n * @throws {AuthzDeniedError} When the read is denied. 読み取りが拒否された場合。\n */\n async loader({ request }: Req): Promise<AuthzGroupRolesData> {\n const ctx = await requirePermission(request, { resourceKey: RES, action: \"read\" })\n return groupRolesData(ctx)\n },\n /**\n * Group-roles tab action: assign / remove, each guarded for its own verb.\n * グループ割当タブの action。割当・解除を、それぞれの動詞でガードして実行する処理。\n *\n * As with user roles the link row carries no tenant, so both the group (`ownedGroup`) and\n * the role (`ownedRole`) are proven to belong to the guarded tenant first.\n * ユーザー割当と同様に割当行はテナントを持たないため、グループ側・ロール側双方の帰属を先に証明する形。\n *\n * @param args The route arguments (request only). ルート引数 (request のみ)。\n * @returns `{ ok: true }`, or a `400` payload on a validation failure. 成功、または検証失敗時の 400 応答。\n * @throws {Response} `403` when the guard denies the verb. 動詞のガードが拒否した場合。\n */\n async action({ request }: Req) {\n const form = await request.formData()\n const intent = String(form.get(\"_action\"))\n try {\n switch (intent) {\n case \"assign\": {\n const ctx = await requirePermission(request, { resourceKey: RES, action: \"create\" })\n const groupId = Number(form.get(\"groupId\"))\n const roleId = Number(form.get(\"roleId\"))\n if (!(groupId && roleId)) return data({ error: \"グループとロールを選択してください\" }, { status: 400 })\n const group = await ownedGroup(ctx, \"create\", groupId)\n const role = await ownedRole(ctx, \"create\", roleId)\n await store.assignGroupRole(ctx.tenantId, group.id, role.id, String(ctx.userId))\n return { ok: true }\n }\n case \"remove\": {\n const ctx = await requirePermission(request, { resourceKey: RES, action: \"delete\" })\n const group = await ownedGroup(ctx, \"delete\", Number(form.get(\"groupId\")))\n const role = await ownedRole(ctx, \"delete\", Number(form.get(\"roleId\")))\n await store.removeGroupRole(ctx.tenantId, group.id, role.id)\n return { ok: true }\n }\n default:\n return data({ error: \"unknown action\" }, { status: 400 })\n }\n } catch (e) {\n return toActionError(e)\n }\n },\n },\n\n graph: {\n data: graphData,\n /**\n * Graph tab loader: guard for `read`, then load every listing of the guarded tenant.\n * グラフタブの loader。`read` をガードし、ガード済みテナントの全一覧を読み込む処理。\n *\n * @param args The route arguments (request only). ルート引数 (request のみ)。\n * @returns The graph tab payload. グラフタブの表示データ。\n * @throws {AuthzDeniedError} When the read is denied. 読み取りが拒否された場合。\n */\n async loader({ request }: Req): Promise<AuthzGraphData> {\n const ctx = await requirePermission(request, { resourceKey: RES, action: \"read\" })\n return graphData(ctx)\n },\n },\n }\n}\n","/**\n * The label contract of the authorization admin UI: its string shape, the English defaults, and the\n * per-section merge that resolves a host's partial overrides against them.\n * 認可管理 UI のラベル契約 (文言の型・英語既定値・部分上書きを解決するセクション単位マージ)。\n *\n * A host overrides whole sections through `createAuthzAdminRoutes(..., { labels })`; sections it\n * leaves out keep the defaults, so a partial translation never blanks a screen.\n * アプリ側は `createAuthzAdminRoutes(..., { labels })` でセクション単位に上書きする形\n * (未指定セクションは既定値のまま残るため、部分翻訳でも画面が空にならない)。\n */\n\n/**\n * Authz admin UI strings (i18n). Defaults are English; override per-section via `createAuthzAdminRoutes(..., { labels })`.\n * 認可管理 UI の文言 (i18n)。既定は英語で、セクション単位に上書きできる形。\n */\nexport type AuthzAdminLabels = {\n title: string\n myPermission: string\n tabs: { roles: string; resources: string; role_permissions: string; user_roles: string; group_roles: string; graph: string }\n /**\n * Strings shared by every tab, including the tenant column / field every tenant-scoped table shows.\n * 全タブ共通の文言 (テナント列・テナント入力欄を含む)。\n */\n common: { add: string; save: string; delete: string; remove: string; assign: string; grant: string; select: string; active: string; inactive: string; optional: string; tenant: string; tenantHint: string; unknownTenant: string }\n roles: { heading: string; note: string; roleKey: string; name: string; description: string; activeHeader: string; empty: string; newHeading: string; hintRoleKey: string }\n resources: { heading: string; note: string; appKey: string; resourceKey: string; name: string; description: string; empty: string; newHeading: string; hintAppKey: string; hintResourceKey: string; hintDailyReport: string }\n grants: {\n heading: string\n note: string\n role: string\n resource: string\n action: string\n rowScope: string\n columnScope: string\n empty: string\n newHeading: string\n rowScopeHint: string\n columnScopeHint: string\n rowScopePlaceholder: string\n columnScopePlaceholder: string\n nullRows: string\n nullCols: string\n selectPlaceholder: string\n }\n userRoles: { heading: string; note: string; user: string; role: string; empty: string; newHeading: string; selectPlaceholder: string }\n groupRoles: { heading: string; note: string; group: string; role: string; empty: string; newHeading: string; selectPlaceholder: string }\n error: { forbiddenTitle: string; forbiddenBody: string; genericTitlePrefix: string; genericTitle: string; genericBody: string }\n warn: { selfRoleDisable: string; selfRoleDelete: string; selfAssignmentRemove: string }\n}\n\nexport const defaultAuthzAdminLabels: AuthzAdminLabels = {\n title: \"Authorization (RBAC)\",\n myPermission: \"Your permissions\",\n tabs: { roles: \"Roles\", resources: \"Resources\", role_permissions: \"Permissions\", user_roles: \"User assignments\", group_roles: \"Group assignments\", graph: \"Graph\" },\n common: {\n add: \"Add\",\n save: \"Save\",\n delete: \"Delete\",\n remove: \"Remove\",\n assign: \"Assign\",\n grant: \"Grant\",\n select: \"Select…\",\n active: \"○ Enabled\",\n inactive: \"× Disabled\",\n optional: \"optional\",\n tenant: \"tenant\",\n tenantHint: \"must match the tenant you are signed in to\",\n unknownTenant: \"—\",\n },\n roles: {\n heading: \"Roles (TMRole)\",\n note: \"Edit name / description inline and click Save (role_key is an immutable identifier).\",\n roleKey: \"role_key\",\n name: \"name\",\n description: \"description\",\n activeHeader: \"Active\",\n empty: \"No roles\",\n newHeading: \"New role\",\n hintRoleKey: \"e.g. admin\",\n },\n resources: {\n heading: \"Resources (TMResource)\",\n note: \"Edit name / description inline and click Save (app_key / resource_key are immutable identifiers).\",\n appKey: \"app_key\",\n resourceKey: \"resource_key\",\n name: \"name\",\n description: \"description\",\n empty: \"No resources\",\n newHeading: \"New resource\",\n hintAppKey: \"e.g. quants\",\n hintResourceKey: \"e.g. daily_report\",\n hintDailyReport: \"optional\",\n },\n grants: {\n heading: \"Permissions (TDRolePermission)\",\n note: \"Empty row_scope / column_scope = all rows / all columns (NULL). JSON must satisfy the row_scope / column_scope contract.\",\n role: \"role\",\n resource: \"resource\",\n action: \"action\",\n rowScope: \"row_scope\",\n columnScope: \"column_scope\",\n empty: \"No permissions\",\n newHeading: \"Grant a permission\",\n rowScopeHint: \"JSON / empty = all rows\",\n columnScopeHint: \"JSON / empty = all columns\",\n rowScopePlaceholder: \"empty = all rows (NULL)\",\n columnScopePlaceholder: \"empty = all columns (NULL)\",\n nullRows: \"NULL (all rows)\",\n nullCols: \"NULL (all columns)\",\n selectPlaceholder: \"Select…\",\n },\n userRoles: { heading: \"User assignments (TDUserRole)\", note: \"Assign active roles to specific users.\", user: \"user\", role: \"role\", empty: \"No assignments\", newHeading: \"Assign a role\", selectPlaceholder: \"Select…\" },\n groupRoles: { heading: \"Group assignments (TDGroupRole)\", note: \"Assign active roles to user groups.\", group: \"group\", role: \"role\", empty: \"No group assignments\", newHeading: \"Assign a role to group\", selectPlaceholder: \"Select…\" },\n error: {\n forbiddenTitle: \"Access denied\",\n forbiddenBody: \"You don't have permission to view this screen. Ask an administrator to grant access.\",\n genericTitlePrefix: \"Error\",\n genericTitle: \"An error occurred\",\n genericBody: \"An unexpected error occurred. Please try again later.\",\n },\n warn: {\n selfRoleDisable: \"This role backs your own authorization-admin access. Disabling it may lock you out of this screen. Continue?\",\n selfRoleDelete: \"This role backs your own authorization-admin access. Deleting it may lock you out of this screen. Continue?\",\n selfAssignmentRemove: \"This assignment backs your own authorization-admin access. Removing it may lock you out of this screen. Continue?\",\n },\n}\n\nexport type PartialAuthzAdminLabels = {\n title?: string\n myPermission?: string\n tabs?: Partial<AuthzAdminLabels[\"tabs\"]>\n common?: Partial<AuthzAdminLabels[\"common\"]>\n roles?: Partial<AuthzAdminLabels[\"roles\"]>\n resources?: Partial<AuthzAdminLabels[\"resources\"]>\n grants?: Partial<AuthzAdminLabels[\"grants\"]>\n userRoles?: Partial<AuthzAdminLabels[\"userRoles\"]>\n groupRoles?: Partial<AuthzAdminLabels[\"groupRoles\"]>\n error?: Partial<AuthzAdminLabels[\"error\"]>\n warn?: Partial<AuthzAdminLabels[\"warn\"]>\n}\n\n/**\n * Merge partial overrides onto the English defaults, one section at a time.\n * 部分的な上書きを英語の既定値へセクション単位で統合する処理。\n *\n * The merge is shallow **per section**, not a whole-object replace: a host that overrides only\n * `grants.note` keeps every other `grants` string instead of losing them.\n * 統合はセクション単位の浅いマージであり全体置換ではない形\n * (`grants.note` だけを上書きしても、同セクションの他文言は失われない)。\n *\n * @param over Partial overrides, or `undefined` to keep the defaults. 部分的な上書き (`undefined` なら既定値のまま)。\n * @returns The fully resolved label set. 解決済みの全文言。\n */\nexport function resolveLabels(over?: PartialAuthzAdminLabels): AuthzAdminLabels {\n const d = defaultAuthzAdminLabels\n if (!over) return d\n return {\n title: over.title ?? d.title,\n myPermission: over.myPermission ?? d.myPermission,\n tabs: { ...d.tabs, ...over.tabs },\n common: { ...d.common, ...over.common },\n roles: { ...d.roles, ...over.roles },\n resources: { ...d.resources, ...over.resources },\n grants: { ...d.grants, ...over.grants },\n userRoles: { ...d.userRoles, ...over.userRoles },\n groupRoles: { ...d.groupRoles, ...over.groupRoles },\n error: { ...d.error, ...over.error },\n warn: { ...d.warn, ...over.warn },\n }\n}\n","/**\n * Presentation primitives and the inline style map shared by every authorization admin UI tab.\n * 認可管理 UI 共有のプレゼンテーション部品・スタイル。\n */\nimport type { CSSProperties, ReactNode } from \"react\"\n\n/**\n * Inline styles shared by the admin tabs, keyed by role.\n * 管理タブ共通のインラインスタイル (役割名をキーとする形)。\n *\n * `satisfies` is used rather than a `Record<string, CSSProperties>` annotation: the annotation\n * erases the key set, so a typo such as `ui.dangerBtn` type-checks and silently renders unstyled.\n * `Record<string, CSSProperties>` の注釈ではなく `satisfies` を用いる形 (注釈はキー集合を消し、\n * `ui.dangerBtn` のような綴り誤りが型検査を素通りして無装飾描画になるため)。\n */\nexport const ui = {\n page: { padding: \"1.25rem\", maxWidth: 1280, margin: \"0 auto\", color: \"#0f172a\" },\n tableContainer: { width: \"100%\", overflowX: \"auto\", marginBottom: \"1rem\", WebkitOverflowScrolling: \"touch\" },\n table: { borderCollapse: \"collapse\", width: \"100%\", fontSize: \"0.9rem\", tableLayout: \"fixed\" },\n th: { textAlign: \"left\", borderBottom: \"2px solid #e2e8f0\", padding: \"0.45rem 0.65rem\", fontSize: \"0.78rem\", color: \"#475569\", fontWeight: 600, textTransform: \"uppercase\", letterSpacing: \"0.03em\" },\n td: { borderBottom: \"1px solid #f1f5f9\", padding: \"0.5rem 0.65rem\", verticalAlign: \"middle\", wordBreak: \"break-all\" },\n input: { padding: \"0.4rem 0.55rem\", border: \"1px solid #cbd5e1\", borderRadius: 6, fontSize: \"0.9rem\", background: \"#fff\", width: \"100%\", boxSizing: \"border-box\" },\n select: { padding: \"0.4rem 0.55rem\", border: \"1px solid #cbd5e1\", borderRadius: 6, fontSize: \"0.9rem\", background: \"#fff\", width: \"100%\", boxSizing: \"border-box\" },\n textarea: { padding: \"0.4rem 0.55rem\", border: \"1px solid #cbd5e1\", borderRadius: 6, fontSize: \"0.82rem\", fontFamily: \"ui-monospace, SFMono-Regular, Menlo, monospace\", minHeight: \"3.25rem\", resize: \"vertical\", width: \"100%\", boxSizing: \"border-box\" },\n button: { padding: \"0.3rem 0.7rem\", border: \"1px solid #cbd5e1\", borderRadius: 6, background: \"#fff\", cursor: \"pointer\", fontSize: \"0.82rem\", color: \"#334155\" },\n save: { padding: \"0.3rem 0.7rem\", border: \"1px solid #2563eb\", borderRadius: 6, background: \"#eff6ff\", cursor: \"pointer\", fontSize: \"0.82rem\", color: \"#2563eb\", fontWeight: 600 },\n danger: { padding: \"0.3rem 0.7rem\", border: \"1px solid #fecaca\", borderRadius: 6, background: \"#fff\", cursor: \"pointer\", fontSize: \"0.82rem\", color: \"#dc2626\" },\n primary: { padding: \"0.5rem 1.25rem\", border: \"1px solid #2563eb\", borderRadius: 6, background: \"#2563eb\", color: \"#fff\", cursor: \"pointer\", fontSize: \"0.9rem\", fontWeight: 600, alignSelf: \"flex-start\" },\n card: { display: \"flex\", flexDirection: \"column\", gap: \"0.85rem\", maxWidth: 600, marginTop: \"0.75rem\", padding: \"1rem 1.1rem\", border: \"1px solid #e2e8f0\", borderRadius: 10, background: \"#f8fafc\" },\n code: { fontFamily: \"ui-monospace, SFMono-Regular, Menlo, monospace\", fontSize: \"0.78rem\", color: \"#334155\", wordBreak: \"break-all\" },\n muted: { color: \"#94a3b8\" },\n h2: { fontSize: \"1.05rem\", margin: \"0 0 0.35rem\", fontWeight: 700 },\n h3: { fontSize: \"0.92rem\", margin: \"1.25rem 0 0\", color: \"#334155\", fontWeight: 600 },\n note: { color: \"#64748b\", fontSize: \"0.82rem\", margin: \"0 0 0.75rem\" },\n empty: { color: \"#94a3b8\", padding: \"0.75rem 0.65rem\" },\n} satisfies Record<string, CSSProperties>\n\n/**\n * Stack a label above its control, keeping the two from overlapping and the tabs consistent.\n * ラベルを上、コントロールを下に縦積みするフィールド (重なり防止・レイアウト統一)。\n *\n * A `<div>` rather than a `<label>`: the control is expected to carry its own `aria-label`.\n * `<label>` ではなく `<div>` で構成する形 (コントロール側が `aria-label` を持つ前提のため)。\n *\n * @param props The label text, an optional hint, and the control to wrap. ラベル文言・任意の補足・包むコントロール。\n * @returns The stacked field. 縦積みされたフィールド。\n */\nexport function Field({ label, hint, children }: { label: string; hint?: string; children: ReactNode }) {\n return (\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: \"0.3rem\" }}>\n <span style={{ fontSize: \"0.8rem\", color: \"#475569\", fontWeight: 600 }}>\n {label}\n {hint ? <span style={{ fontWeight: 400, color: \"#94a3b8\" }}> — {hint}</span> : null}\n </span>\n {children}\n </div>\n )\n}\n\n/**\n * Render an error message as an assertive alert, or nothing when there is no message.\n * エラーメッセージを `role=\"alert\"` で表示するコンポーネント (メッセージが無ければ何も描画しない形)。\n *\n * @param props The message to show, if any. 表示するメッセージ (省略可)。\n * @returns The alert element, or `null`. アラート要素、または `null`。\n */\nexport function ErrorAlert({ message }: { message?: string }) {\n if (!message) return null\n return (\n <p role=\"alert\" style={{ color: \"#dc2626\", background: \"#fef2f2\", border: \"1px solid #fecaca\", borderRadius: 8, padding: \"0.55rem 0.8rem\", fontSize: \"0.85rem\", margin: \"0.25rem 0\" }}>\n {message}\n </p>\n )\n}\n","/**\n * Client-safe presentation components of the authorization admin UI.\n * Every view reads its rows, labels and self-admin hints from the loader payload through\n * `useLoaderData`, and depends on no database or store — the server wiring lives in `admin/server.ts`.\n * 認可管理 UI のプレゼンテーション部品 (client-safe)。\n * loader データ (行 + ラベル + 自己管理者ヒント) を `useLoaderData` で受け取り描画する形。\n * DB / ストアには一切依存しない (サーバ結線は `admin/server.ts`)。\n *\n * **Known deviations of {@link AuthzGroupRolesView}** from the conventions the other tabs follow:\n * its `group` / `role` columns carry no percentage width, its action column is sized in `px`, its\n * table is not wrapped in `ui.tableContainer`, and its removal confirmation text is hard-coded in\n * Japanese instead of coming from `labels.warn`. They are recorded in the design specification\n * rather than silently changed here, because the spec compares the tabs against each other.\n * **{@link AuthzGroupRolesView} の既知の逸脱**: `group` / `role` 列の幅指定なし・操作列の `px` 指定・\n * `ui.tableContainer` 非適用・削除確認文言のハードコード (`labels.warn` 由来ではない) の 4 点\n * (仕様書に記録のうえ据え置く形)。\n */\nimport { AUTHZ_ACTIONS, type AuthzAssignment, type AuthzGrant, type AuthzGroupRoleAssignment, type AuthzGroupSummary, type AuthzResource, type AuthzRole, type AuthzUser } from \"@aiquants/authz-core\"\nimport { SelectBox } from \"@aiquants/select-box\"\nimport { useState } from \"react\"\nimport { Form, isRouteErrorResponse, NavLink, Outlet, useActionData, useLoaderData, useRouteError } from \"react-router\"\nimport { type MyPermissionGroupItem, MyPermissionGroupStatus } from \"../MyPermissionGroupStatus\"\nimport type { PermissionView } from \"../server\"\nimport { type AuthzAdminLabels, type PartialAuthzAdminLabels, resolveLabels } from \"./labels\"\nimport { ErrorAlert, Field, ui } from \"./ui\"\n\ntype WithLabels = { labels: AuthzAdminLabels }\n/**\n * Loader payload of any tenant-scoped tab: the tenant every row on screen was loaded for.\n * テナント単位タブの loader ペイロード共通部 (画面上の全行を読み込んだテナント)。\n */\ntype WithTenant = { tenantId: string }\ntype ActionError = { error?: string } | undefined\n/**\n * Read the error message out of an action payload, if the action reported one.\n * action の戻り値からエラーメッセージを取り出す処理。\n *\n * @param d The `useActionData()` payload of the tab. 当該タブの `useActionData()` の戻り値。\n * @returns The message, or `undefined` when the action did not fail. メッセージ (失敗していなければ `undefined`)。\n */\nconst actionError = (d: ActionError) => d?.error\n\n/**\n * Render one table cell showing which tenant a row belongs to.\n * 行の所属テナントを表示するテーブルセルを描画するコンポーネント。\n *\n * @param props The tenant to show and the `data-testid` to expose it under. 表示するテナントと露出する `data-testid`。\n * @returns The tenant cell element. テナントセル要素。\n */\nfunction TenantCell({ tenantId, testId }: { tenantId: string; testId: string }) {\n return (\n <td style={ui.td}>\n <span style={ui.code} data-testid={testId}>\n {tenantId}\n </span>\n </td>\n )\n}\n\n/**\n * Resolve the tenant a join row (grant / assignment) inherits from the role it references.\n * 結合行 (付与・割当) が参照先ロールから受け継ぐテナントを解決する処理。\n *\n * `TDRolePermission` / `TDUserRole` / `TDGroupRole` carry no tenant column of their own, so the only\n * real tenant available is the one projected onto the referenced role row. An unresolvable role means\n * the two listings disagree — that is shown as \"unknown\" rather than assumed to be the current tenant,\n * because assuming would paint a foreign row as local.\n * これらの結合表はテナント列を持たないため、参照先ロール行の実テナントが唯一の出所。解決できない場合は\n * 「現在のテナント」と仮定せず不明として表示する (仮定は越境行を自テナントに見せるため)。\n *\n * @param roles Roles loaded for the same tenant. 同一テナントで読み込んだロール群。\n * @param roleId The role the join row references. 結合行が参照するロール id。\n * @param labels Resolved UI strings. 解決済みの UI 文言。\n * @returns The role's tenant, or the \"unknown\" marker. ロールのテナント、または不明マーカー。\n */\nfunction tenantOfRole(roles: AuthzRole[], roleId: number, labels: AuthzAdminLabels): string {\n const role = roles.find((r) => r.id === roleId)\n return role ? role.tenantId : labels.common.unknownTenant\n}\n\n// ============================ Layout ============================\n\nexport type AuthzLayoutData = WithLabels & WithTenant & { myPermissions: Record<string, PermissionView | undefined>; adminResourceKey: string; basePath?: string }\n\nexport type AuthzLayoutShellProps = {\n children: React.ReactNode\n renderHeader?: (props: { title: string; annotation: React.ReactNode }) => React.ReactNode\n}\n\n/**\n * Render the shared chrome of the admin UI: the header (title + \"your permissions\") and the tabs.\n * 認可管理 UI 共通の外枠 (ヘッダ = タイトル + あなたの権限、およびタブ) を描画するコンポーネント。\n *\n * @param props The tab body to render and an optional header renderer. 描画する中身 (splat なら children、ネストなら Outlet) と任意のヘッダ描画関数。\n * @returns The framed admin page. 外枠つきの管理画面。\n */\nfunction AuthzLayoutShell({ children, renderHeader }: AuthzLayoutShellProps) {\n const { myPermissions, labels, adminResourceKey, basePath, tenantId } = useLoaderData() as AuthzLayoutData\n const base = basePath ?? \"/authz\"\n const [variant, setVariant] = useState<\"pills\" | \"popover\">(\"pills\")\n\n const tabs: ReadonlyArray<readonly [string, string]> = [\n [\"roles\", labels.tabs.roles],\n [\"resources\", labels.tabs.resources],\n [\"role_permissions\", labels.tabs.role_permissions],\n [\"user_roles\", labels.tabs.user_roles],\n [\"group_roles\", labels.tabs.group_roles],\n [\"graph\", labels.tabs.graph],\n ]\n\n const permissionItems: MyPermissionGroupItem[] = Object.entries(myPermissions || {}).map(([key, perm]) => ({\n resourceKey: key,\n resourceName: perm?.resourceKey || key,\n permission: perm,\n expectedResourceKey: key,\n }))\n\n const items =\n permissionItems.length > 0\n ? permissionItems\n : [\n {\n resourceKey: adminResourceKey,\n resourceName: adminResourceKey,\n permission: myPermissions?.[adminResourceKey],\n expectedResourceKey: adminResourceKey,\n },\n ]\n\n const annotation = (\n <div style={{ display: \"flex\", alignItems: \"center\", gap: \"0.4rem\" }}>\n <span data-testid=\"authz-tenant-badge\" style={{ ...ui.code, border: \"1px solid #cbd5e1\", borderRadius: 4, padding: \"1px 6px\", background: \"#f8fafc\" }}>\n {labels.common.tenant}: {tenantId}\n </span>\n <MyPermissionGroupStatus prefixLabel={labels.myPermission} items={items} variant={variant} size=\"xs\" />\n <div style={{ display: \"inline-flex\", borderRadius: \"4px\", border: \"1px solid #cbd5e1\", padding: \"1px\", backgroundColor: \"#f8fafc\", fontSize: \"0.7rem\" }}>\n <button\n type=\"button\"\n onClick={() => setVariant(\"pills\")}\n data-testid=\"switch-variant-pills\"\n style={{\n padding: \"1px 5px\",\n borderRadius: \"3px\",\n border: \"none\",\n cursor: \"pointer\",\n fontSize: \"0.7rem\",\n lineHeight: \"1.2\",\n fontWeight: variant === \"pills\" ? 600 : 400,\n backgroundColor: variant === \"pills\" ? \"#ffffff\" : \"transparent\",\n color: variant === \"pills\" ? \"#1e293b\" : \"#64748b\",\n boxShadow: variant === \"pills\" ? \"0 1px 2px rgba(0,0,0,0.05)\" : \"none\",\n }}>\n Pills\n </button>\n <button\n type=\"button\"\n onClick={() => setVariant(\"popover\")}\n data-testid=\"switch-variant-popover\"\n style={{\n padding: \"1px 5px\",\n borderRadius: \"3px\",\n border: \"none\",\n cursor: \"pointer\",\n fontSize: \"0.7rem\",\n lineHeight: \"1.2\",\n fontWeight: variant === \"popover\" ? 600 : 400,\n backgroundColor: variant === \"popover\" ? \"#ffffff\" : \"transparent\",\n color: variant === \"popover\" ? \"#1e293b\" : \"#64748b\",\n boxShadow: variant === \"popover\" ? \"0 1px 2px rgba(0,0,0,0.05)\" : \"none\",\n }}>\n Popover\n </button>\n </div>\n </div>\n )\n\n return (\n <div className=\"authz-admin\" style={ui.page}>\n {renderHeader ? (\n renderHeader({ title: labels.title, annotation })\n ) : (\n <header style={{ display: \"flex\", alignItems: \"center\", justifyContent: \"space-between\", gap: \"0.75rem\", flexWrap: \"wrap\", margin: \"0 0 0.4rem\" }}>\n <h1 style={{ fontSize: \"1.15rem\", margin: 0, fontWeight: 700, color: \"#1e293b\" }}>{labels.title}</h1>\n {annotation}\n </header>\n )}\n <nav style={{ display: \"flex\", gap: \"0.25rem\", borderBottom: \"1px solid #e2e8f0\", margin: \"0.6rem 0 1rem\" }}>\n {tabs.map(([key, label]) => (\n <NavLink\n key={key}\n to={`${base}/${key}`}\n style={({ isActive }) => ({\n padding: \"0.5rem 0.9rem\",\n textDecoration: \"none\",\n color: isActive ? \"#2563eb\" : \"#475569\",\n borderBottom: isActive ? \"2px solid #2563eb\" : \"2px solid transparent\",\n marginBottom: -1,\n fontWeight: isActive ? 700 : 500,\n fontSize: \"0.92rem\",\n })}>\n {label}\n </NavLink>\n ))}\n </nav>\n {children}\n </div>\n )\n}\n\nexport type AuthzLayoutProps = {\n renderHeader?: (props: { title: string; annotation: React.ReactNode }) => React.ReactNode\n}\n\n/**\n * Layout route for a nested route configuration; the active tab is rendered through `Outlet`.\n * ネストルート構成用のレイアウト (子タブは `Outlet` で描画)。`routes.ts` をネスト構成にする場合の入口。\n *\n * @param props Optional header renderer. 任意のヘッダ描画関数。\n * @returns The framed admin page hosting the nested route. ネストされた子ルートを収めた管理画面。\n */\nexport function AuthzLayout({ renderHeader }: AuthzLayoutProps = {}) {\n return (\n <AuthzLayoutShell renderHeader={renderHeader}>\n <Outlet />\n </AuthzLayoutShell>\n )\n}\n\n/**\n * Build the admin UI's error boundary with labels baked in.\n * ラベルを埋め込んだ認可管理 UI のエラー境界を生成する処理。\n *\n * An error boundary cannot read loader data, so its wording cannot come from the loader the way\n * every tab's does; the host injects its own locale here instead.\n * エラー境界は loader データを読めないため、他タブのようにラベルを loader から得られない形\n * (アプリ側が自分のロケールをここで注入する)。\n *\n * @param over Partial label overrides; omitted sections keep the English defaults. 部分的なラベル上書き (未指定は英語の既定値)。\n * @returns An error boundary component bound to those labels. そのラベルを束縛したエラー境界コンポーネント。\n */\nexport function makeAuthzErrorBoundary(over?: PartialAuthzAdminLabels): () => React.ReactElement {\n const L = resolveLabels(over).error\n /**\n * Render the caught routing error, distinguishing a 403 from any other failure.\n * 捕捉したルーティングエラーを描画するコンポーネント (403 とその他を区別する形)。\n *\n * @returns The error panel. エラー表示パネル。\n */\n return function AuthzErrorBoundary() {\n const error = useRouteError()\n const routeError = isRouteErrorResponse(error) ? error : null\n // 認可拒否は Response ではなく `status` を持つ素の例外として届く (ガードが送出する\n // `AuthzDeniedError`)。Response だけを見ていると、拒否された利用者に「エラーが発生\n // しました」という汎用文言が出て、権限の話だと判らない\n const is403 = routeError?.status === 403 || (typeof error === \"object\" && error !== null && (error as { status?: unknown }).status === 403)\n const title = is403 ? L.forbiddenTitle : routeError ? `${L.genericTitlePrefix} (${routeError.status})` : L.genericTitle\n const message = is403 ? L.forbiddenBody : routeError ? (typeof routeError.data === \"string\" && routeError.data ? routeError.data : routeError.statusText) : L.genericBody\n return (\n <div style={ui.page}>\n <div role=\"alert\" style={{ maxWidth: 480, margin: \"3rem auto\", padding: \"1.5rem\", border: \"1px solid #fecaca\", borderRadius: 12, background: \"#fef2f2\" }}>\n <h1 style={{ fontSize: \"1.1rem\", fontWeight: 700, color: \"#b91c1c\", margin: \"0 0 0.5rem\" }}>{title}</h1>\n <p style={{ color: \"#7f1d1d\", fontSize: \"0.9rem\", margin: 0, lineHeight: 1.7 }}>{message}</p>\n </div>\n </div>\n )\n }\n}\n\n/**\n * The error boundary bound to the package's English default labels.\n * 英語 (既定ラベル) のエラー境界。i18n する場合は {@link makeAuthzErrorBoundary} を使う形。\n */\nexport const AuthzErrorBoundary = makeAuthzErrorBoundary()\n\n/**\n * Warn before a destructive click that would remove one of the actor's own administrator paths.\n * 実行者自身の管理権限を支える対象への破壊操作について、実行前に警告する処理。\n *\n * @param active Whether the target really supports the actor's admin status. 対象が実行者の管理権限を支えているかどうか。\n * @param message Confirmation text to show. 表示する確認文言。\n * @returns A click handler that cancels the submit when the user declines. キャンセル時に送信を中止するクリックハンドラ。\n */\nconst confirmGuard = (active: boolean, message: string) => (e: React.MouseEvent) => {\n if (active && !window.confirm(message)) e.preventDefault()\n}\n\n// ============================ Roles ============================\n\nexport type AuthzRolesData = WithLabels & WithTenant & { roles: AuthzRole[]; selfAdminRoleIds?: number[] }\n\n/**\n * Roles tab: lists the tenant's roles and offers create / rename / enable / delete.\n * ロールタブ。当該テナントのロール一覧と、作成・改名・有効切替・削除の操作を提供するコンポーネント。\n *\n * Reads {@link AuthzRolesData}; the tenant shown in every row is the one the guard resolved, which\n * the loader also echoes as `tenantId`.\n * {@link AuthzRolesData} を読む形。表示するテナントはガードが解決したもの (loader が `tenantId` として同梱)。\n *\n * @returns The roles tab. ロールタブ。\n */\nexport function AuthzRolesView() {\n const { roles, selfAdminRoleIds, labels, tenantId } = useLoaderData() as AuthzRolesData\n const L = labels.roles\n const C = labels.common\n const selfAdmin = new Set(selfAdminRoleIds ?? [])\n return (\n <section>\n <h2 style={ui.h2}>{L.heading}</h2>\n <ErrorAlert message={actionError(useActionData() as ActionError)} />\n <p style={ui.note}>{L.note}</p>\n <div style={ui.tableContainer}>\n <table style={ui.table}>\n <thead>\n <tr>\n <th style={{ ...ui.th, width: \"12%\" }}>{C.tenant}</th>\n <th style={{ ...ui.th, width: \"18%\" }}>{L.roleKey}</th>\n <th style={{ ...ui.th, width: \"22%\" }}>{L.name}</th>\n <th style={{ ...ui.th, width: \"28%\" }}>{L.description}</th>\n <th style={{ ...ui.th, width: \"10%\" }}>{L.activeHeader}</th>\n <th style={{ ...ui.th, width: \"10%\" }} />\n </tr>\n </thead>\n <tbody>\n {roles.map((r) => (\n <tr key={r.id}>\n <TenantCell tenantId={r.tenantId} testId={`tenant-role-${r.id}`} />\n <td style={ui.td}>\n {/* 保存フォームにも所属テナントを載せる (サーバはガード側テナントと突き合わせ、不一致は 403) */}\n <input type=\"hidden\" name=\"tenantId\" value={r.tenantId} form={`save-role-${r.id}`} />\n {r.roleKey === \"@authenticated\" || r.roleKey === \"@anonymous\" ? (\n <>\n {/* 鍵は動かせないが、フォームは載せないと送信されない。欄を出さないだけだと\n サーバが必須項目の欠落として弾き、名前も説明も **一切保存できなくなる** */}\n <input type=\"hidden\" name=\"roleKey\" value={r.roleKey} form={`save-role-${r.id}`} />\n <span style={ui.code}>{r.roleKey}</span>\n </>\n ) : (\n <input name=\"roleKey\" defaultValue={r.roleKey} form={`save-role-${r.id}`} aria-label={`roleKey-${r.id}`} required style={ui.input} />\n )}\n </td>\n <td style={ui.td}>\n <input name=\"name\" defaultValue={r.name} form={`save-role-${r.id}`} aria-label={`name-${r.id}`} required style={ui.input} />\n </td>\n <td style={ui.td}>\n <input name=\"description\" defaultValue={r.description ?? \"\"} form={`save-role-${r.id}`} aria-label={`description-${r.id}`} style={ui.input} />\n </td>\n <td style={ui.td}>\n <Form method=\"post\" style={{ display: \"inline\" }}>\n <input type=\"hidden\" name=\"_action\" value=\"toggle\" />\n <input type=\"hidden\" name=\"id\" value={r.id} />\n <input type=\"hidden\" name=\"isActive\" value={String(!r.isActive)} />\n <button type=\"submit\" style={ui.button} onClick={r.isActive ? confirmGuard(selfAdmin.has(r.id), labels.warn.selfRoleDisable) : undefined}>\n {r.isActive ? C.active : C.inactive}\n </button>\n </Form>\n </td>\n <td style={{ ...ui.td, whiteSpace: \"nowrap\" }}>\n <Form method=\"post\" id={`save-role-${r.id}`} style={{ display: \"inline\" }}>\n <input type=\"hidden\" name=\"_action\" value=\"save\" />\n <input type=\"hidden\" name=\"id\" value={r.id} />\n <button type=\"submit\" style={ui.save}>\n {C.save}\n </button>\n </Form>\n <Form method=\"post\" style={{ display: \"inline\", marginLeft: \"0.4rem\" }}>\n <input type=\"hidden\" name=\"_action\" value=\"delete\" />\n <input type=\"hidden\" name=\"id\" value={r.id} />\n <button type=\"submit\" style={ui.danger} onClick={confirmGuard(selfAdmin.has(r.id), labels.warn.selfRoleDelete)}>\n {C.delete}\n </button>\n </Form>\n </td>\n </tr>\n ))}\n {roles.length === 0 ? (\n <tr>\n <td style={ui.empty} colSpan={6}>\n {L.empty}\n </td>\n </tr>\n ) : null}\n </tbody>\n </table>\n </div>\n\n <h3 style={ui.h3}>{L.newHeading}</h3>\n <Form method=\"post\" style={ui.card}>\n <input type=\"hidden\" name=\"_action\" value=\"create\" />\n <Field label={C.tenant} hint={C.tenantHint}>\n <input name=\"tenantId\" aria-label=\"tenant_id\" defaultValue={tenantId} required style={ui.input} />\n </Field>\n <Field label={L.roleKey} hint={L.hintRoleKey}>\n <input name=\"roleKey\" aria-label=\"role_key\" required style={ui.input} />\n </Field>\n <Field label={L.name}>\n <input name=\"name\" aria-label=\"name\" required style={ui.input} />\n </Field>\n <Field label={L.description} hint={C.optional}>\n <input name=\"description\" aria-label=\"description\" style={ui.input} />\n </Field>\n <button type=\"submit\" style={ui.primary}>\n {C.add}\n </button>\n </Form>\n </section>\n )\n}\n\n// ============================ Resources ============================\n\nexport type AuthzResourcesData = WithLabels & WithTenant & { resources: AuthzResource[] }\n\n/**\n * Resources tab: lists the tenant's resources and offers create / rename / delete.\n * リソースタブ。当該テナントのリソース一覧と、作成・改名・削除の操作を提供するコンポーネント。\n *\n * Reads {@link AuthzResourcesData}; the tenant shown is the guard-resolved one.\n * {@link AuthzResourcesData} を読む形 (表示するテナントはガードが解決したもの)。\n *\n * @returns The resources tab. リソースタブ。\n */\nexport function AuthzResourcesView() {\n const { resources, labels, tenantId } = useLoaderData() as AuthzResourcesData\n const L = labels.resources\n const C = labels.common\n return (\n <section>\n <h2 style={ui.h2}>{L.heading}</h2>\n <ErrorAlert message={actionError(useActionData() as ActionError)} />\n <p style={ui.note}>{L.note}</p>\n <div style={ui.tableContainer}>\n <table style={ui.table}>\n <thead>\n <tr>\n <th style={{ ...ui.th, width: \"12%\" }}>{C.tenant}</th>\n <th style={{ ...ui.th, width: \"13%\" }}>{L.appKey}</th>\n <th style={{ ...ui.th, width: \"18%\" }}>{L.resourceKey}</th>\n <th style={{ ...ui.th, width: \"22%\" }}>{L.name}</th>\n <th style={{ ...ui.th, width: \"25%\" }}>{L.description}</th>\n <th style={{ ...ui.th, width: \"10%\" }} />\n </tr>\n </thead>\n <tbody>\n {resources.map((r) => (\n <tr key={r.id}>\n <TenantCell tenantId={r.tenantId} testId={`tenant-resource-${r.id}`} />\n <td style={ui.td}>\n <span style={ui.code}>{r.appKey}</span>\n </td>\n <td style={ui.td}>\n <span style={ui.code}>{r.resourceKey}</span>\n </td>\n <td style={ui.td}>\n <input name=\"name\" defaultValue={r.name} form={`save-res-${r.id}`} aria-label={`name-${r.id}`} required style={ui.input} />\n </td>\n <td style={ui.td}>\n <input name=\"description\" defaultValue={r.description ?? \"\"} form={`save-res-${r.id}`} aria-label={`description-${r.id}`} style={ui.input} />\n </td>\n <td style={{ ...ui.td, whiteSpace: \"nowrap\" }}>\n <Form method=\"post\" id={`save-res-${r.id}`} style={{ display: \"inline\" }}>\n <input type=\"hidden\" name=\"_action\" value=\"save\" />\n <input type=\"hidden\" name=\"id\" value={r.id} />\n <button type=\"submit\" style={ui.save}>\n {C.save}\n </button>\n </Form>\n <Form method=\"post\" style={{ display: \"inline\", marginLeft: \"0.4rem\" }}>\n <input type=\"hidden\" name=\"_action\" value=\"delete\" />\n <input type=\"hidden\" name=\"id\" value={r.id} />\n <button type=\"submit\" style={ui.danger}>\n {C.delete}\n </button>\n </Form>\n </td>\n </tr>\n ))}\n {resources.length === 0 ? (\n <tr>\n <td style={ui.empty} colSpan={6}>\n {L.empty}\n </td>\n </tr>\n ) : null}\n </tbody>\n </table>\n </div>\n\n <h3 style={ui.h3}>{L.newHeading}</h3>\n <Form method=\"post\" style={ui.card}>\n <input type=\"hidden\" name=\"_action\" value=\"create\" />\n <Field label={C.tenant} hint={C.tenantHint}>\n <input name=\"tenantId\" aria-label=\"tenant_id\" defaultValue={tenantId} required style={ui.input} />\n </Field>\n <Field label={L.appKey} hint={L.hintAppKey}>\n <input name=\"appKey\" aria-label=\"app_key\" required style={ui.input} />\n </Field>\n <Field label={L.resourceKey} hint={L.hintResourceKey}>\n <input name=\"resourceKey\" aria-label=\"resource_key\" required style={ui.input} />\n </Field>\n <Field label={L.name}>\n <input name=\"name\" aria-label=\"name\" required style={ui.input} />\n </Field>\n <Field label={L.description} hint={C.optional}>\n <input name=\"description\" aria-label=\"description\" style={ui.input} />\n </Field>\n <button type=\"submit\" style={ui.primary}>\n {C.add}\n </button>\n </Form>\n </section>\n )\n}\n\n// ============================ Grants (role_permissions) ============================\n\n/**\n * Grants tab payload. `roles` / `resources` are the **full** rows, not just the id + key pairs the\n * select boxes need: a grant row carries no tenant of its own, so the referenced role's projected\n * tenant is what the tenant column shows.\n * 付与タブのペイロード。`roles` / `resources` は全項目を持つ行 (付与行にテナント列が無く、参照先ロールの実テナントを表示するため)。\n */\nexport type AuthzGrantsData = WithLabels & WithTenant & { grants: AuthzGrant[]; roles: AuthzRole[]; resources: AuthzResource[] }\n\n/**\n * One editable grant row (its own component so the action select can hold local state).\n * 編集可能な付与行 1 件 (action の選択状態を持つため独立コンポーネント)。\n *\n * @param props The grant row, the tenant it belongs to, and the resolved labels. 付与行・所属テナント・解決済み文言。\n * @returns The table row element. テーブル行要素。\n */\nfunction GrantRow({ g, tenantId, labels }: { g: AuthzGrant; tenantId: string; labels: AuthzAdminLabels }) {\n const C = labels.common\n const [action, setAction] = useState<{ label: string; value: string }>({ label: g.action, value: g.action })\n\n return (\n <tr>\n <TenantCell tenantId={tenantId} testId={`tenant-grant-${g.id}`} />\n <td style={ui.td}>\n <span style={ui.code}>{g.roleKey}</span>\n </td>\n <td style={ui.td}>\n <span style={ui.code}>\n {g.appKey}:{g.resourceKey}\n </span>\n </td>\n <td style={ui.td}>\n {g.roleKey === \"@anonymous\" ? (\n <span style={ui.code}>{g.action}</span>\n ) : (\n <>\n <input type=\"hidden\" name=\"action\" value={action.value} form={`save-grant-${g.id}`} />\n <SelectBox\n options={AUTHZ_ACTIONS.map((a) => ({ label: a, value: a }))}\n value={action}\n onChange={(val) => {\n if (val) setAction(val as { label: string; value: string })\n }}\n />\n </>\n )}\n </td>\n <td style={ui.td}>\n <textarea name=\"rowScope\" defaultValue={g.rowScope ?? \"\"} form={`save-grant-${g.id}`} aria-label={`row_scope-${g.id}`} rows={2} placeholder={labels.grants.rowScopePlaceholder} style={ui.textarea} />\n </td>\n <td style={ui.td}>\n <textarea name=\"columnScope\" defaultValue={g.columnScope ?? \"\"} form={`save-grant-${g.id}`} aria-label={`column_scope-${g.id}`} rows={2} placeholder={labels.grants.columnScopePlaceholder} style={ui.textarea} />\n </td>\n <td style={{ ...ui.td, whiteSpace: \"nowrap\" }}>\n <Form method=\"post\" id={`save-grant-${g.id}`} style={{ display: \"inline\" }}>\n <input type=\"hidden\" name=\"_action\" value=\"save\" />\n <input type=\"hidden\" name=\"id\" value={g.id} />\n <button type=\"submit\" style={ui.save}>\n {C.save}\n </button>\n </Form>\n <Form method=\"post\" style={{ display: \"inline\", marginLeft: \"0.4rem\" }}>\n <input type=\"hidden\" name=\"_action\" value=\"delete\" />\n <input type=\"hidden\" name=\"id\" value={g.id} />\n <button type=\"submit\" style={ui.danger}>\n {C.delete}\n </button>\n </Form>\n </td>\n </tr>\n )\n}\n\n/**\n * Permissions tab: lists the tenant's grants and offers grant / re-scope / revoke.\n * 権限付与タブ。当該テナントの付与一覧と、付与・スコープ変更・削除の操作を提供するコンポーネント。\n *\n * Reads {@link AuthzGrantsData}; its role and resource pickers are already narrowed to the\n * guard-resolved tenant by the loader, so a cross-tenant pair cannot be selected here.\n * {@link AuthzGrantsData} を読む形。ロール・リソースの選択肢は loader が解決済みテナントへ絞ってあるため、\n * 越境した組み合わせは選べない。\n *\n * @returns The permissions tab. 権限付与タブ。\n */\nexport function AuthzGrantsView() {\n const { grants, roles, resources, labels, tenantId } = useLoaderData() as AuthzGrantsData\n const L = labels.grants\n const C = labels.common\n\n const roleOptions = roles.map((r) => ({ label: r.roleKey, value: r.id }))\n const resourceOptions = resources.map((r) => ({ label: `${r.appKey}:${r.resourceKey}`, value: r.id }))\n const actionOptions = AUTHZ_ACTIONS.map((a) => ({ label: a, value: a }))\n\n const [selectedRole, setSelectedRole] = useState<{ label: string; value: number } | undefined>(undefined)\n const [selectedResource, setSelectedResource] = useState<{ label: string; value: number } | undefined>(undefined)\n const [selectedAction, setSelectedAction] = useState<{ label: string; value: string } | undefined>({ label: \"read\", value: \"read\" })\n\n return (\n <section>\n <h2 style={ui.h2}>{L.heading}</h2>\n <p style={ui.note}>{L.note}</p>\n <ErrorAlert message={actionError(useActionData() as ActionError)} />\n <div style={ui.tableContainer}>\n <table style={ui.table}>\n <thead>\n <tr>\n <th style={{ ...ui.th, width: \"10%\" }}>{C.tenant}</th>\n <th style={{ ...ui.th, width: \"13%\" }}>{L.role}</th>\n <th style={{ ...ui.th, width: \"17%\" }}>{L.resource}</th>\n <th style={{ ...ui.th, width: \"13%\" }}>{L.action}</th>\n <th style={{ ...ui.th, width: \"19%\" }}>{L.rowScope}</th>\n <th style={{ ...ui.th, width: \"18%\" }}>{L.columnScope}</th>\n <th style={{ ...ui.th, width: \"10%\" }} />\n </tr>\n </thead>\n <tbody>\n {grants.map((g) => (\n <GrantRow key={g.id} g={g} tenantId={tenantOfRole(roles, g.roleId, labels)} labels={labels} />\n ))}\n {grants.length === 0 ? (\n <tr>\n <td style={ui.empty} colSpan={7}>\n {L.empty}\n </td>\n </tr>\n ) : null}\n </tbody>\n </table>\n </div>\n\n <h3 style={ui.h3}>{L.newHeading}</h3>\n <Form method=\"post\" style={ui.card}>\n <input type=\"hidden\" name=\"_action\" value=\"create\" />\n <input type=\"hidden\" name=\"roleId\" value={selectedRole?.value ?? \"\"} />\n <input type=\"hidden\" name=\"resourceId\" value={selectedResource?.value ?? \"\"} />\n <input type=\"hidden\" name=\"action\" value={selectedAction?.value ?? \"\"} />\n\n <Field label={C.tenant} hint={C.tenantHint}>\n <input name=\"tenantId\" aria-label=\"tenant_id\" defaultValue={tenantId} required style={ui.input} />\n </Field>\n <Field label={L.role}>\n <SelectBox options={roleOptions} value={selectedRole} onChange={(val) => setSelectedRole((val || undefined) as { label: string; value: number } | undefined)} placeholder={L.selectPlaceholder} />\n </Field>\n <Field label={L.resource}>\n <SelectBox options={resourceOptions} value={selectedResource} onChange={(val) => setSelectedResource((val || undefined) as { label: string; value: number } | undefined)} placeholder={L.selectPlaceholder} />\n </Field>\n <Field label={L.action}>\n <SelectBox options={actionOptions} value={selectedAction} onChange={(val) => setSelectedAction((val || undefined) as { label: string; value: string } | undefined)} />\n </Field>\n <Field label={L.rowScope} hint={L.rowScopeHint}>\n <textarea name=\"rowScope\" aria-label=\"row_scope\" rows={2} style={ui.textarea} placeholder='{\"filters\":[{\"field\":\"部門CD\",\"op\":\"in\",\"values\":[\"A\"]}]}' />\n </Field>\n <Field label={L.columnScope} hint={L.columnScopeHint}>\n <textarea name=\"columnScope\" aria-label=\"column_scope\" rows={2} style={ui.textarea} placeholder='{\"mode\":\"deny\",\"columns\":[\"金額\"]}' />\n </Field>\n <button type=\"submit\" style={ui.primary}>\n {C.grant}\n </button>\n </Form>\n </section>\n )\n}\n\n// ============================ User roles ============================\n\n/**\n * User-assignment tab payload. `roles` holds the **full** role rows because `TDUserRole` has no tenant\n * column: the assignment's tenant is the one projected onto the role it points at.\n * ユーザー割当タブのペイロード。`TDUserRole` にテナント列が無いため、`roles` は参照先ロールの実テナントを含む全項目行。\n */\nexport type AuthzUserRolesData = WithLabels & WithTenant & { assignments: AuthzAssignment[]; roles: AuthzRole[]; users: AuthzUser[]; actorUserId?: number; selfAdminRoleIds?: number[] }\n\n/**\n * User-roles tab: lists the tenant's user assignments and offers assign / unassign.\n * ユーザー割当タブ。当該テナントのユーザー×ロール割当一覧と、割当・解除の操作を提供するコンポーネント。\n *\n * Reads {@link AuthzUserRolesData}; removing an assignment that supports the actor's own admin\n * status is confirmed first through {@link confirmGuard}.\n * {@link AuthzUserRolesData} を読む形 (実行者自身の管理権限を支える割当の解除は {@link confirmGuard} で確認)。\n *\n * @returns The user-roles tab. ユーザー割当タブ。\n */\nexport function AuthzUserRolesView() {\n const { assignments, roles, users, actorUserId, selfAdminRoleIds, labels } = useLoaderData() as AuthzUserRolesData\n const L = labels.userRoles\n const C = labels.common\n const selfAdmin = new Set(selfAdminRoleIds ?? [])\n\n const userOptions = users.map((u) => ({ label: `${u.email}(${u.displayName})`, value: u.id }))\n const roleOptions = roles.filter((r) => r.roleKey !== \"@authenticated\" && r.roleKey !== \"@anonymous\").map((r) => ({ label: r.roleKey, value: r.id }))\n\n const [selectedUser, setSelectedUser] = useState<{ label: string; value: number } | undefined>(undefined)\n const [selectedRole, setSelectedRole] = useState<{ label: string; value: number } | undefined>(undefined)\n\n return (\n <section>\n <h2 style={ui.h2}>{L.heading}</h2>\n <ErrorAlert message={actionError(useActionData() as ActionError)} />\n <p style={ui.note}>{L.note}</p>\n <div style={ui.tableContainer}>\n <table style={ui.table}>\n <thead>\n <tr>\n <th style={{ ...ui.th, width: \"12%\" }}>{C.tenant}</th>\n <th style={{ ...ui.th, width: \"40%\" }}>{L.user}</th>\n <th style={{ ...ui.th, width: \"38%\" }}>{L.role}</th>\n <th style={{ ...ui.th, width: \"10%\" }} />\n </tr>\n </thead>\n <tbody>\n {assignments.map((a) => (\n <tr key={`${a.userId}-${a.roleId}`}>\n <TenantCell tenantId={tenantOfRole(roles, a.roleId, labels)} testId={`tenant-assignment-${a.userId}-${a.roleId}`} />\n <td style={ui.td}>\n {a.email}\n <span style={ui.muted}>({a.displayName})</span>\n </td>\n <td style={ui.td}>{a.roleKey}</td>\n <td style={ui.td}>\n <Form method=\"post\" style={{ display: \"inline\" }}>\n <input type=\"hidden\" name=\"_action\" value=\"remove\" />\n <input type=\"hidden\" name=\"userId\" value={a.userId} />\n <input type=\"hidden\" name=\"roleId\" value={a.roleId} />\n <button type=\"submit\" style={ui.danger} onClick={confirmGuard(a.userId === actorUserId && selfAdmin.has(a.roleId), labels.warn.selfAssignmentRemove)}>\n {C.remove}\n </button>\n </Form>\n </td>\n </tr>\n ))}\n {assignments.length === 0 ? (\n <tr>\n <td style={ui.empty} colSpan={4}>\n {L.empty}\n </td>\n </tr>\n ) : null}\n </tbody>\n </table>\n </div>\n\n <h3 style={ui.h3}>{L.newHeading}</h3>\n <Form method=\"post\" style={ui.card}>\n <input type=\"hidden\" name=\"_action\" value=\"assign\" />\n <input type=\"hidden\" name=\"userId\" value={selectedUser?.value ?? \"\"} />\n <input type=\"hidden\" name=\"roleId\" value={selectedRole?.value ?? \"\"} />\n <Field label={L.user}>\n <SelectBox options={userOptions} value={selectedUser} onChange={(val) => setSelectedUser((val || undefined) as { label: string; value: number } | undefined)} placeholder={L.selectPlaceholder} />\n </Field>\n <Field label={L.role}>\n <SelectBox options={roleOptions} value={selectedRole} onChange={(val) => setSelectedRole((val || undefined) as { label: string; value: number } | undefined)} placeholder={L.selectPlaceholder} />\n </Field>\n <button type=\"submit\" style={ui.primary}>\n {C.assign}\n </button>\n </Form>\n </section>\n )\n}\n\n// ============================ Group roles ============================\n\nexport type AuthzGroupRolesData = WithLabels &\n WithTenant & {\n groupRoles: AuthzGroupRoleAssignment[]\n roles: AuthzRole[]\n groups: AuthzGroupSummary[]\n }\n\n/**\n * Group-roles tab: lists the tenant's group assignments and offers assign / unassign.\n * グループ割当タブ。当該テナントのグループ×ロール割当一覧と、割当・解除の操作を提供するコンポーネント。\n *\n * Reads {@link AuthzGroupRolesData}. See the module docstring for its known layout deviations from\n * the other tabs (hard-coded confirmation text, no tenant column, error alert placed differently).\n * {@link AuthzGroupRolesData} を読む形。他タブとの既知の逸脱 (確認文言のハードコード・テナント列の非表示・\n * エラー表示位置の相違) はモジュール冒頭の記載を参照。\n *\n * @returns The group-roles tab. グループ割当タブ。\n */\nexport function AuthzGroupRolesView() {\n const { groupRoles, roles, groups, labels } = useLoaderData() as AuthzGroupRolesData\n const actionData = useActionData() as ActionError\n const err = actionError(actionData)\n const L = labels.groupRoles\n const C = labels.common\n\n const activeRoles = roles.filter((r) => r.isActive && r.roleKey !== \"@authenticated\" && r.roleKey !== \"@anonymous\")\n\n const groupOptions = groups.map((g) => ({ label: `${g.name} (${g.groupKey})`, value: g.id }))\n const roleOptions = activeRoles.map((r) => ({ label: `${r.name} (${r.roleKey})`, value: r.id }))\n\n const [selectedGroup, setSelectedGroup] = useState<{ label: string; value: number } | undefined>(groupOptions[0])\n const [selectedRole, setSelectedRole] = useState<{ label: string; value: number } | undefined>(roleOptions[0])\n\n return (\n <section style={{ display: \"flex\", flexDirection: \"column\", gap: \"1.25rem\" }}>\n <div>\n <h2 style={ui.h2}>{L.heading}</h2>\n <p style={ui.note}>{L.note}</p>\n </div>\n {err && <ErrorAlert message={err} />}\n\n <table style={ui.table}>\n <thead>\n <tr>\n <th style={{ ...ui.th, width: \"12%\" }}>{C.tenant}</th>\n <th style={ui.th}>{L.group}</th>\n <th style={ui.th}>{L.role}</th>\n <th style={{ ...ui.th, width: \"100px\" }} />\n </tr>\n </thead>\n <tbody>\n {groupRoles.length === 0 ? (\n <tr>\n <td colSpan={4} style={{ ...ui.td, textAlign: \"center\", color: \"#64748b\" }}>\n {L.empty}\n </td>\n </tr>\n ) : (\n groupRoles.map((a) => (\n <tr key={`${a.groupId}-${a.roleId}`}>\n <TenantCell tenantId={tenantOfRole(roles, a.roleId, labels)} testId={`tenant-group-role-${a.groupId}-${a.roleId}`} />\n <td style={ui.td}>\n <strong>{a.groupName}</strong> <span style={{ color: \"#64748b\", fontSize: \"0.85rem\" }}>({a.groupKey})</span>\n </td>\n <td style={ui.td}>\n <code>{a.roleKey}</code>\n </td>\n <td style={{ ...ui.td, textAlign: \"right\" }}>\n <Form method=\"post\" onSubmit={(e) => !confirm(\"このグループロール割当を解除しますか?\") && e.preventDefault()}>\n <input type=\"hidden\" name=\"_action\" value=\"remove\" />\n <input type=\"hidden\" name=\"groupId\" value={a.groupId} />\n <input type=\"hidden\" name=\"roleId\" value={a.roleId} />\n <button type=\"submit\" style={ui.danger}>\n {C.remove}\n </button>\n </Form>\n </td>\n </tr>\n ))\n )}\n </tbody>\n </table>\n\n <Form method=\"post\" style={ui.card}>\n <h3 style={ui.h3}>{L.newHeading}</h3>\n <input type=\"hidden\" name=\"_action\" value=\"assign\" />\n <input type=\"hidden\" name=\"groupId\" value={selectedGroup?.value ?? \"\"} />\n <input type=\"hidden\" name=\"roleId\" value={selectedRole?.value ?? \"\"} />\n <Field label={L.group}>\n <SelectBox options={groupOptions} value={selectedGroup} onChange={(val) => setSelectedGroup((val || undefined) as { label: string; value: number } | undefined)} placeholder={L.selectPlaceholder} />\n </Field>\n <Field label={L.role}>\n <SelectBox options={roleOptions} value={selectedRole} onChange={(val) => setSelectedRole((val || undefined) as { label: string; value: number } | undefined)} placeholder={L.selectPlaceholder} />\n </Field>\n <button type=\"submit\" style={ui.primary}>\n {C.assign}\n </button>\n </Form>\n </section>\n )\n}\n\n// ============================ Graph view ============================\n\nexport type AuthzGraphData = WithLabels &\n WithTenant & {\n users: AuthzUser[]\n roles: AuthzRole[]\n resources: AuthzResource[]\n grants: AuthzGrant[]\n assignments: AuthzAssignment[]\n groupRoles?: AuthzGroupRoleAssignment[]\n groups?: AuthzGroupSummary[]\n }\n\nconst ACTION_COLORS: Record<string, { bg: string; text: string; border: string }> = {\n read: { bg: \"#ecfdf5\", text: \"#047857\", border: \"#a7f3d0\" },\n create: { bg: \"#eff6ff\", text: \"#1d4ed8\", border: \"#bfdbfe\" },\n update: { bg: \"#fffbeb\", text: \"#b45309\", border: \"#fde68a\" },\n delete: { bg: \"#fef2f2\", text: \"#b91c1c\", border: \"#fecaca\" },\n}\n\nexport type AuthzNodeType = \"user\" | \"group\" | \"role\" | \"resource\"\n\n/**\n * Draw the user → group → role → resource authorization graph as an SVG topology map.\n * ユーザー → グループ → ロール → リソースの認可関係を SVG のトポロジ図として描画するコンポーネント。\n *\n * Hovering or selecting a node dims everything not reachable from it, so a single grant path can be\n * followed end to end.\n * ノードのホバー・選択で、到達しない要素を減光する形 (1 本の付与経路を端から端まで追える)。\n *\n * @param props The graph rows to draw, plus the selected node and its click handler. 描画対象の行と、選択ノード・そのクリックハンドラ。\n * @returns The topology map. トポロジ図。\n */\nfunction AuthzTopologyMap({\n users,\n roles,\n resources,\n grants,\n assignments,\n groupRoles = [],\n groups = [],\n selectedNode,\n onNodeClick,\n}: {\n users: AuthzUser[]\n roles: AuthzRole[]\n resources: AuthzResource[]\n grants: AuthzGrant[]\n assignments: AuthzAssignment[]\n groupRoles?: AuthzGroupRoleAssignment[]\n groups?: AuthzGroupSummary[]\n selectedNode: { type: AuthzNodeType; id: number } | null\n onNodeClick: (type: AuthzNodeType, id: number) => void\n}) {\n const [hoveredNode, setHoveredNode] = useState<{ type: AuthzNodeType; id: number } | null>(null)\n\n const connectedUserIds = new Set(assignments.map((a) => a.userId))\n const connectedGroupIds = new Set(groupRoles.map((g) => g.groupId))\n const connectedRoleIds = new Set([...assignments.map((a) => a.roleId), ...groupRoles.map((g) => g.roleId), ...grants.map((g) => g.roleId)])\n const connectedResourceIds = new Set(grants.map((g) => g.resourceId))\n\n const activeUsers = users.filter((u) => connectedUserIds.has(u.id))\n const activeGroups = groups.filter((g) => connectedGroupIds.has(g.id))\n const activeRoles = roles.filter((r) => connectedRoleIds.has(r.id))\n const activeResources = resources.filter((res) => connectedResourceIds.has(res.id))\n\n /**\n * Actions actually granted on a resource, or `[\"none\"]` when it carries no grant at all.\n * 当該リソースに実際に付与されているアクション (付与が皆無なら `[\"none\"]`)。\n *\n * @param resId Resource id. リソース ID。\n * @returns The distinct granted actions. 重複を除いた付与済みアクション。\n */\n const getResourceActions = (resId: number) => {\n const resGrants = grants.filter((g) => g.resourceId === resId)\n const uniqueActions = Array.from(new Set(resGrants.map((g) => g.action)))\n return uniqueActions.length > 0 ? uniqueActions : [\"none\"]\n }\n\n /**\n * Pixel height of a resource card, which grows with the number of actions it lists.\n * リソースカードの高さ (px)。列挙するアクション数に応じて伸びる形。\n *\n * @param resId Resource id. リソース ID。\n * @returns The card height in pixels. カードの高さ (px)。\n */\n const getResourceCardHeight = (resId: number) => {\n const actions = getResourceActions(resId)\n return 8 + 24 + actions.length * 24 + 6\n }\n\n const resourceCardGap = 16\n let cumulativeResY = 0\n const resourceYMap = new Map<number, { top: number; height: number; actionYMap: Map<string, number> }>()\n\n activeResources.forEach((res) => {\n const actions = getResourceActions(res.id)\n const height = getResourceCardHeight(res.id)\n const top = cumulativeResY\n const actionYMap = new Map<string, number>()\n\n actions.forEach((act, actIdx) => {\n const actY = top + 8 + 24 + actIdx * 24 + 12\n actionYMap.set(act, actY)\n })\n\n resourceYMap.set(res.id, { top, height, actionYMap })\n cumulativeResY += height + resourceCardGap\n })\n\n const totalResourceHeight = cumulativeResY > 0 ? cumulativeResY - resourceCardGap : 0\n\n const stdCardHeight = 52\n const stdCardGap = 18\n const stdItemHeight = stdCardHeight + stdCardGap\n\n const totalRoleHeight = activeRoles.length > 0 ? activeRoles.length * stdItemHeight - stdCardGap : 0\n const totalGroupHeight = activeGroups.length > 0 ? activeGroups.length * stdItemHeight - stdCardGap : 0\n const totalUserHeight = activeUsers.length > 0 ? activeUsers.length * stdItemHeight - stdCardGap : 0\n\n const maxColumnHeight = Math.max(totalResourceHeight, totalRoleHeight, totalGroupHeight, totalUserHeight, 320)\n const svgHeight = maxColumnHeight + 60\n\n const resourceStartTop = (svgHeight - totalResourceHeight) / 2\n const roleStartTop = (svgHeight - totalRoleHeight) / 2\n const groupStartTop = (svgHeight - totalGroupHeight) / 2\n const userStartTop = (svgHeight - totalUserHeight) / 2\n\n /**\n * Absolute y of an action row inside a resource card, used as a link endpoint.\n * リソースカード内のアクション行の絶対 y 座標 (リンクの端点)。\n *\n * @param resId Resource id. リソース ID。\n * @param action Action row to locate. 位置を求めるアクション。\n * @returns The y coordinate in SVG units. SVG 座標系の y 値。\n */\n const getResourceActionCenterY = (resId: number, action: string) => {\n const info = resourceYMap.get(resId)\n if (!info) return svgHeight / 2\n const relativeActY = info.actionYMap.get(action) ?? info.height / 2\n return resourceStartTop + relativeActY\n }\n\n /**\n * Absolute y of a role card's centre in the role column.\n * ロール列における当該ロールカード中心の絶対 y 座標。\n *\n * @param roleIndex Index within the visible roles. 表示中ロール内での位置。\n * @returns The y coordinate in SVG units. SVG 座標系の y 値。\n */\n const getRoleCenterY = (roleIndex: number) => {\n return roleStartTop + roleIndex * stdItemHeight + stdCardHeight / 2\n }\n\n /**\n * Absolute y of a group card's centre in the group column.\n * グループ列における当該グループカード中心の絶対 y 座標。\n *\n * @param groupIndex Index within the visible groups. 表示中グループ内での位置。\n * @returns The y coordinate in SVG units. SVG 座標系の y 値。\n */\n const getGroupCenterY = (groupIndex: number) => {\n return groupStartTop + groupIndex * stdItemHeight + stdCardHeight / 2\n }\n\n /**\n * Absolute y of a user card's centre in the user column.\n * ユーザー列における当該ユーザーカード中心の絶対 y 座標。\n *\n * @param userIndex Index within the visible users. 表示中ユーザー内での位置。\n * @returns The y coordinate in SVG units. SVG 座標系の y 値。\n */\n const getUserCenterY = (userIndex: number) => {\n return userStartTop + userIndex * stdItemHeight + stdCardHeight / 2\n }\n\n const resourceX = 140\n const roleX = 420\n const groupX = 700\n const userX = 980\n\n const activeNode = hoveredNode || selectedNode\n\n /**\n * Whether a user stays highlighted under the hovered / selected node.\n * ホバー・選択中のノードのもとで当該ユーザーを強調表示し続けるかどうかの判定。\n *\n * @param uId User id. ユーザー ID。\n * @returns `true` when the user is reachable from the active node. 到達可能なら `true`。\n */\n const isUserActive = (uId: number) => {\n if (!activeNode) return true\n if (activeNode.type === \"user\") return activeNode.id === uId\n if (activeNode.type === \"role\") return assignments.some((a) => a.userId === uId && a.roleId === activeNode.id)\n if (activeNode.type === \"group\") return false\n if (activeNode.type === \"resource\") {\n const roleIds = grants.filter((g) => g.resourceId === activeNode.id).map((g) => g.roleId)\n return assignments.some((a) => a.userId === uId && roleIds.includes(a.roleId))\n }\n return true\n }\n\n /**\n * Whether a group stays highlighted under the hovered / selected node.\n * ホバー・選択中のノードのもとで当該グループを強調表示し続けるかどうかの判定。\n *\n * @param gId Group id. グループ ID。\n * @returns `true` when the group is reachable from the active node. 到達可能なら `true`。\n */\n const isGroupActive = (gId: number) => {\n if (!activeNode) return true\n if (activeNode.type === \"group\") return activeNode.id === gId\n if (activeNode.type === \"role\") return groupRoles.some((gr) => gr.groupId === gId && gr.roleId === activeNode.id)\n if (activeNode.type === \"user\") return false\n if (activeNode.type === \"resource\") {\n const roleIds = grants.filter((g) => g.resourceId === activeNode.id).map((g) => g.roleId)\n return groupRoles.some((gr) => gr.groupId === gId && roleIds.includes(gr.roleId))\n }\n return true\n }\n\n /**\n * Whether a role stays highlighted under the hovered / selected node.\n * ホバー・選択中のノードのもとで当該ロールを強調表示し続けるかどうかの判定。\n *\n * @param rId Role id. ロール ID。\n * @returns `true` when the role is reachable from the active node. 到達可能なら `true`。\n */\n const isRoleActive = (rId: number) => {\n if (!activeNode) return true\n if (activeNode.type === \"role\") return activeNode.id === rId\n if (activeNode.type === \"user\") return assignments.some((a) => a.userId === activeNode.id && a.roleId === rId)\n if (activeNode.type === \"group\") return groupRoles.some((gr) => gr.groupId === activeNode.id && gr.roleId === rId)\n if (activeNode.type === \"resource\") return grants.some((g) => g.resourceId === activeNode.id && g.roleId === rId)\n return true\n }\n\n /**\n * Whether a resource stays highlighted under the hovered / selected node.\n * ホバー・選択中のノードのもとで当該リソースを強調表示し続けるかどうかの判定。\n *\n * @param resId Resource id. リソース ID。\n * @returns `true` when the resource is reachable from the active node. 到達可能なら `true`。\n */\n const isResourceActive = (resId: number) => {\n if (!activeNode) return true\n if (activeNode.type === \"resource\") return activeNode.id === resId\n if (activeNode.type === \"role\") return grants.some((g) => g.roleId === activeNode.id && g.resourceId === resId)\n if (activeNode.type === \"group\") {\n const groupRoleIds = groupRoles.filter((gr) => gr.groupId === activeNode.id).map((gr) => gr.roleId)\n return grants.some((g) => groupRoleIds.includes(g.roleId) && g.resourceId === resId)\n }\n if (activeNode.type === \"user\") {\n const userRoleIds = assignments.filter((a) => a.userId === activeNode.id).map((a) => a.roleId)\n return grants.some((g) => userRoleIds.includes(g.roleId) && g.resourceId === resId)\n }\n return true\n }\n\n /**\n * Whether the user → role edge stays highlighted under the hovered / selected node.\n * ホバー・選択中のノードのもとで、ユーザー → ロールの辺を強調表示し続けるかどうかの判定。\n *\n * @param uId User id. ユーザー ID。\n * @param rId Role id. ロール ID。\n * @returns `true` when the edge lies on a path from the active node. 経路上にあれば `true`。\n */\n const isUserRoleLinkActive = (uId: number, rId: number) => {\n if (!activeNode) return true\n if (activeNode.type === \"user\") return activeNode.id === uId && assignments.some((a) => a.userId === uId && a.roleId === rId)\n if (activeNode.type === \"role\") return activeNode.id === rId && assignments.some((a) => a.userId === uId && a.roleId === rId)\n if (activeNode.type === \"resource\") {\n const roleIds = grants.filter((g) => g.resourceId === activeNode.id).map((g) => g.roleId)\n return roleIds.includes(rId) && assignments.some((a) => a.userId === uId && a.roleId === rId)\n }\n if (activeNode.type === \"group\") return false\n return true\n }\n\n /**\n * Whether the group → role edge stays highlighted under the hovered / selected node.\n * ホバー・選択中のノードのもとで、グループ → ロールの辺を強調表示し続けるかどうかの判定。\n *\n * @param gId Group id. グループ ID。\n * @param rId Role id. ロール ID。\n * @returns `true` when the edge lies on a path from the active node. 経路上にあれば `true`。\n */\n const isGroupRoleLinkActive = (gId: number, rId: number) => {\n if (!activeNode) return true\n if (activeNode.type === \"group\") return activeNode.id === gId && groupRoles.some((gr) => gr.groupId === gId && gr.roleId === rId)\n if (activeNode.type === \"role\") return activeNode.id === rId && groupRoles.some((gr) => gr.groupId === gId && gr.roleId === rId)\n if (activeNode.type === \"resource\") {\n const roleIds = grants.filter((g) => g.resourceId === activeNode.id).map((g) => g.roleId)\n return roleIds.includes(rId) && groupRoles.some((gr) => gr.groupId === gId && gr.roleId === rId)\n }\n if (activeNode.type === \"user\") return false\n return true\n }\n\n /**\n * Whether the role → resource edge stays highlighted under the hovered / selected node.\n * ホバー・選択中のノードのもとで、ロール → リソースの辺を強調表示し続けるかどうかの判定。\n *\n * @param rId Role id. ロール ID。\n * @param resId Resource id. リソース ID。\n * @returns `true` when the edge lies on a path from the active node. 経路上にあれば `true`。\n */\n const isRoleResourceLinkActive = (rId: number, resId: number) => {\n if (!activeNode) return true\n if (activeNode.type === \"role\") return activeNode.id === rId && grants.some((g) => g.roleId === rId && g.resourceId === resId)\n if (activeNode.type === \"resource\") return activeNode.id === resId && grants.some((g) => g.roleId === rId && g.resourceId === resId)\n if (activeNode.type === \"group\") {\n const groupRoleIds = groupRoles.filter((gr) => gr.groupId === activeNode.id).map((gr) => gr.roleId)\n return groupRoleIds.includes(rId) && grants.some((g) => g.roleId === rId && g.resourceId === resId)\n }\n if (activeNode.type === \"user\") {\n const userRoleIds = assignments.filter((a) => a.userId === activeNode.id).map((a) => a.roleId)\n return userRoleIds.includes(rId) && grants.some((g) => g.roleId === rId && g.resourceId === resId)\n }\n return true\n }\n\n const userRoleLinks: Array<{ uIdx: number; rIdx: number; active: boolean; key: string; offset: number }> = []\n assignments.forEach((a) => {\n const uIdx = activeUsers.findIndex((u) => u.id === a.userId)\n const rIdx = activeRoles.findIndex((r) => r.id === a.roleId)\n if (uIdx !== -1 && rIdx !== -1) {\n const roleAssignments = assignments.filter((item) => item.roleId === a.roleId)\n const assignIdx = roleAssignments.findIndex((item) => item.userId === a.userId && item.roleId === a.roleId)\n const totalAssigns = roleAssignments.length\n const offset = totalAssigns > 1 ? Math.min(Math.max((assignIdx - (totalAssigns - 1) / 2) * 4, -16), 16) : 0\n\n userRoleLinks.push({\n uIdx,\n rIdx,\n active: isUserRoleLinkActive(a.userId, a.roleId),\n key: `ur-${a.userId}-${a.roleId}`,\n offset,\n })\n }\n })\n\n const groupRoleLinks: Array<{ gIdx: number; rIdx: number; active: boolean; key: string; offset: number }> = []\n groupRoles.forEach((gr) => {\n const gIdx = activeGroups.findIndex((g) => g.id === gr.groupId)\n const rIdx = activeRoles.findIndex((r) => r.id === gr.roleId)\n if (gIdx !== -1 && rIdx !== -1) {\n const gRoleAssigns = groupRoles.filter((item) => item.roleId === gr.roleId)\n const assignIdx = gRoleAssigns.findIndex((item) => item.groupId === gr.groupId && item.roleId === gr.roleId)\n const totalAssigns = gRoleAssigns.length\n const offset = totalAssigns > 1 ? Math.min(Math.max((assignIdx - (totalAssigns - 1) / 2) * 4, -16), 16) : 0\n\n groupRoleLinks.push({\n gIdx,\n rIdx,\n active: isGroupRoleLinkActive(gr.groupId, gr.roleId),\n key: `gr-${gr.groupId}-${gr.roleId}`,\n offset,\n })\n }\n })\n\n const roleResourceLinks: Array<{ rIdx: number; resIdx: number; active: boolean; key: string; action: string; color: string; offset: number }> = []\n grants.forEach((g) => {\n const rIdx = activeRoles.findIndex((r) => r.id === g.roleId)\n const resIdx = activeResources.findIndex((res) => res.id === g.resourceId)\n if (rIdx !== -1 && resIdx !== -1) {\n const sameResourceActionGrants = grants.filter((item) => item.resourceId === g.resourceId && item.action === g.action)\n const actionRoleIdx = sameResourceActionGrants.findIndex((item) => item.id === g.id)\n const totalSameGrants = sameResourceActionGrants.length\n const offset = totalSameGrants > 1 ? Math.min(Math.max((actionRoleIdx - (totalSameGrants - 1) / 2) * 4, -12), 12) : 0\n\n const colorConf = ACTION_COLORS[g.action]\n const color = colorConf ? colorConf.text : \"#6366f1\"\n\n roleResourceLinks.push({\n rIdx,\n resIdx,\n active: isRoleResourceLinkActive(g.roleId, g.resourceId),\n key: `rr-grant-${g.id}`,\n action: g.action,\n color,\n offset,\n })\n }\n })\n\n return (\n <div data-testid=\"authz-topology-map\" style={{ width: \"100%\", overflowX: \"auto\", backgroundColor: \"#ffffff\", borderRadius: \"10px\", border: \"1px solid #e2e8f0\", padding: \"1rem\", boxSizing: \"border-box\" }}>\n <div style={{ position: \"relative\", minWidth: \"1160px\", height: `${svgHeight}px` }}>\n <svg style={{ position: \"absolute\", top: 0, left: 0, width: \"100%\", height: \"100%\", pointerEvents: \"none\", zIndex: 1 }}>\n <title>Topology Connections</title>\n\n {roleResourceLinks.map((link) => {\n const grantItem = grants.find((g) => `rr-grant-${g.id}` === link.key)\n if (!grantItem) return null\n\n const y1 = getResourceActionCenterY(grantItem.resourceId, grantItem.action) + link.offset\n const y2 = getRoleCenterY(link.rIdx)\n const cX1 = resourceX + 135\n const cX2 = roleX - 135\n return (\n <path\n key={link.key}\n d={`M ${resourceX + 90} ${y1} C ${cX1} ${y1}, ${cX2} ${y2}, ${roleX - 90} ${y2}`}\n fill=\"none\"\n stroke={link.active ? link.color : \"#cbd5e1\"}\n strokeWidth={link.active && activeNode ? 3 : 2}\n strokeOpacity={link.active ? (activeNode ? 1.0 : 0.75) : 0.12}\n style={{ transition: \"all 0.2s ease\" }}\n />\n )\n })}\n\n {groupRoleLinks.map((link) => {\n const y1 = getRoleCenterY(link.rIdx) + link.offset\n const y2 = getGroupCenterY(link.gIdx)\n const cX1 = roleX + 135\n const cX2 = groupX - 135\n return (\n <path\n key={link.key}\n data-testid={`topo-link-group-role-${link.key}`}\n data-active={link.active ? \"true\" : \"false\"}\n d={`M ${roleX + 90} ${y1} C ${cX1} ${y1}, ${cX2} ${y2}, ${groupX - 90} ${y2}`}\n fill=\"none\"\n stroke={link.active ? \"#8b5cf6\" : \"#cbd5e1\"}\n strokeWidth={link.active && activeNode ? 3 : 2}\n strokeDasharray=\"4 2\"\n strokeOpacity={link.active ? (activeNode ? 1.0 : 0.75) : 0.12}\n style={{ transition: \"all 0.2s ease\" }}\n />\n )\n })}\n\n {userRoleLinks.map((link) => {\n const y1 = getRoleCenterY(link.rIdx) + link.offset\n const y2 = getUserCenterY(link.uIdx)\n const cX1 = roleX + 135\n const cX2 = userX - 135\n return (\n <path\n key={link.key}\n data-testid={`topo-link-user-role-${link.key}`}\n data-active={link.active ? \"true\" : \"false\"}\n d={`M ${roleX + 90} ${y1} C ${cX1} ${y1}, ${cX2} ${y2}, ${userX - 90} ${y2}`}\n fill=\"none\"\n stroke={link.active ? \"#4f46e5\" : \"#cbd5e1\"}\n strokeWidth={link.active && activeNode ? 3 : 2}\n strokeOpacity={link.active ? (activeNode ? 1.0 : 0.75) : 0.12}\n style={{ transition: \"all 0.2s ease\" }}\n />\n )\n })}\n </svg>\n\n <div style={{ position: \"relative\", zIndex: 2, width: \"100%\", height: \"100%\" }}>\n {activeResources.map((res) => {\n const isSelected = selectedNode?.type === \"resource\" && selectedNode.id === res.id\n const active = isResourceActive(res.id)\n const cardInfo = resourceYMap.get(res.id)\n if (!cardInfo) return null\n\n const cardTop = resourceStartTop + cardInfo.top\n const actions = getResourceActions(res.id)\n\n return (\n <div\n key={res.id}\n style={{\n position: \"absolute\",\n left: `${resourceX - 90}px`,\n top: `${cardTop}px`,\n width: \"180px\",\n height: `${cardInfo.height}px`,\n borderRadius: \"8px\",\n border: isSelected ? \"2px solid #10b981\" : \"1px solid #a7f3d0\",\n backgroundColor: isSelected ? \"#d1fae5\" : active ? \"#ffffff\" : \"#f8fafc\",\n opacity: active ? 1 : 0.35,\n boxShadow: isSelected ? \"0 4px 12px rgba(16, 185, 129, 0.2)\" : \"0 1px 3px rgba(0,0,0,0.05)\",\n transition: \"all 0.15s ease\",\n padding: \"8px\",\n boxSizing: \"border-box\",\n display: \"flex\",\n flexDirection: \"column\",\n }}>\n <button\n type=\"button\"\n onClick={() => onNodeClick(\"resource\", res.id)}\n onMouseEnter={() => setHoveredNode({ type: \"resource\", id: res.id })}\n onMouseLeave={() => setHoveredNode(null)}\n data-testid={`topo-node-resource-${res.id}`}\n aria-label={`リソース: ${res.name}`}\n aria-pressed={isSelected}\n style={{\n background: \"none\",\n border: \"none\",\n padding: 0,\n margin: 0,\n cursor: \"pointer\",\n textAlign: \"left\",\n width: \"100%\",\n display: \"flex\",\n alignItems: \"center\",\n gap: \"6px\",\n marginBottom: \"6px\",\n height: \"18px\",\n }}>\n <span style={{ fontSize: \"1rem\", flexShrink: 0 }}>🔑</span>\n <span style={{ fontWeight: 600, fontSize: \"0.78rem\", color: \"#047857\", overflow: \"hidden\", textOverflow: \"ellipsis\", whiteSpace: \"nowrap\" }}>{res.name}</span>\n </button>\n\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: \"2px\" }}>\n {actions.map((action) => {\n if (action === \"none\") {\n return (\n <div key=\"none\" style={{ fontSize: \"0.68rem\", color: \"#94a3b8\", height: \"22px\", display: \"flex\", alignItems: \"center\", padding: \"0 6px\" }}>\n 権限設定なし\n </div>\n )\n }\n\n const colorConf = ACTION_COLORS[action] ?? { bg: \"#ecfdf5\", text: \"#047857\", border: \"#a7f3d0\" }\n return (\n <div\n key={action}\n style={{\n height: \"22px\",\n boxSizing: \"border-box\",\n padding: \"0 8px\",\n borderRadius: \"4px\",\n backgroundColor: colorConf.bg,\n color: colorConf.text,\n border: `1px solid ${colorConf.border}`,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"space-between\",\n fontSize: \"0.68rem\",\n fontWeight: 600,\n }}>\n <span>{action}</span>\n <span style={{ fontSize: \"0.6rem\", opacity: 0.6 }}>●</span>\n </div>\n )\n })}\n </div>\n </div>\n )\n })}\n\n {activeRoles.map((r, i) => {\n const isSelected = selectedNode?.type === \"role\" && selectedNode.id === r.id\n const active = isRoleActive(r.id)\n const y = getRoleCenterY(i)\n return (\n <button\n type=\"button\"\n key={r.id}\n onClick={() => onNodeClick(\"role\", r.id)}\n onMouseEnter={() => setHoveredNode({ type: \"role\", id: r.id })}\n onMouseLeave={() => setHoveredNode(null)}\n data-testid={`topo-node-role-${r.id}`}\n aria-label={`ロール: ${r.name}`}\n aria-pressed={isSelected}\n style={{\n position: \"absolute\",\n left: `${roleX - 90}px`,\n top: `${y - 26}px`,\n width: \"180px\",\n height: \"52px\",\n borderRadius: \"8px\",\n border: isSelected ? \"2px solid #6366f1\" : \"1px solid #c7d2fe\",\n backgroundColor: isSelected ? \"#e0e7ff\" : active ? \"#ffffff\" : \"#f8fafc\",\n opacity: active ? 1 : 0.35,\n cursor: \"pointer\",\n display: \"flex\",\n alignItems: \"center\",\n padding: \"0 10px\",\n gap: \"8px\",\n boxShadow: isSelected ? \"0 4px 12px rgba(99, 102, 241, 0.2)\" : \"0 1px 3px rgba(0,0,0,0.05)\",\n transition: \"all 0.15s ease\",\n textAlign: \"left\",\n boxSizing: \"border-box\",\n }}>\n <span style={{ fontSize: \"1.1rem\", flexShrink: 0 }}>🛡️</span>\n <div style={{ overflow: \"hidden\", textOverflow: \"ellipsis\", whiteSpace: \"nowrap\", flex: 1, minWidth: 0 }}>\n <div style={{ fontWeight: 600, fontSize: \"0.78rem\", color: \"#4338ca\", textOverflow: \"ellipsis\", overflow: \"hidden\" }}>{r.name}</div>\n <div style={{ fontSize: \"0.68rem\", color: \"#64748b\", fontFamily: \"monospace\", textOverflow: \"ellipsis\", overflow: \"hidden\" }}>{r.roleKey}</div>\n </div>\n </button>\n )\n })}\n\n {activeGroups.map((g, i) => {\n const isSelected = selectedNode?.type === \"group\" && selectedNode.id === g.id\n const active = isGroupActive(g.id)\n const y = getGroupCenterY(i)\n return (\n <button\n type=\"button\"\n key={g.id}\n onClick={() => onNodeClick(\"group\", g.id)}\n onMouseEnter={() => setHoveredNode({ type: \"group\", id: g.id })}\n onMouseLeave={() => setHoveredNode(null)}\n data-testid={`topo-node-group-${g.id}`}\n aria-label={`グループ: ${g.name}`}\n aria-pressed={isSelected}\n style={{\n position: \"absolute\",\n left: `${groupX - 90}px`,\n top: `${y - 26}px`,\n width: \"180px\",\n height: \"52px\",\n borderRadius: \"8px\",\n border: isSelected ? \"2px solid #8b5cf6\" : \"1px solid #ddd6fe\",\n backgroundColor: isSelected ? \"#f3e8ff\" : active ? \"#ffffff\" : \"#f8fafc\",\n opacity: active ? 1 : 0.35,\n cursor: \"pointer\",\n display: \"flex\",\n alignItems: \"center\",\n padding: \"0 10px\",\n gap: \"8px\",\n boxShadow: isSelected ? \"0 4px 12px rgba(139, 92, 246, 0.2)\" : \"0 1px 3px rgba(0,0,0,0.05)\",\n transition: \"all 0.15s ease\",\n textAlign: \"left\",\n boxSizing: \"border-box\",\n }}>\n <span style={{ fontSize: \"1.1rem\", flexShrink: 0 }}>👥</span>\n <div style={{ overflow: \"hidden\", textOverflow: \"ellipsis\", whiteSpace: \"nowrap\", flex: 1, minWidth: 0 }}>\n <div style={{ fontWeight: 600, fontSize: \"0.78rem\", color: \"#6d28d9\", textOverflow: \"ellipsis\", overflow: \"hidden\" }}>{g.name}</div>\n <div style={{ fontSize: \"0.68rem\", color: \"#64748b\", fontFamily: \"monospace\", textOverflow: \"ellipsis\", overflow: \"hidden\" }}>{g.groupKey}</div>\n </div>\n </button>\n )\n })}\n\n {activeUsers.map((u, i) => {\n const isSelected = selectedNode?.type === \"user\" && selectedNode.id === u.id\n const active = isUserActive(u.id)\n const y = getUserCenterY(i)\n return (\n <button\n type=\"button\"\n key={u.id}\n onClick={() => onNodeClick(\"user\", u.id)}\n onMouseEnter={() => setHoveredNode({ type: \"user\", id: u.id })}\n onMouseLeave={() => setHoveredNode(null)}\n data-testid={`topo-node-user-${u.id}`}\n aria-label={`ユーザー: ${u.displayName}`}\n aria-pressed={isSelected}\n style={{\n position: \"absolute\",\n left: `${userX - 90}px`,\n top: `${y - 26}px`,\n width: \"180px\",\n height: \"52px\",\n borderRadius: \"8px\",\n border: isSelected ? \"2px solid #0284c7\" : \"1px solid #bae6fd\",\n backgroundColor: isSelected ? \"#e0f2fe\" : active ? \"#ffffff\" : \"#f8fafc\",\n opacity: active ? 1 : 0.35,\n cursor: \"pointer\",\n display: \"flex\",\n alignItems: \"center\",\n padding: \"0 10px\",\n gap: \"8px\",\n boxShadow: isSelected ? \"0 4px 12px rgba(2, 132, 199, 0.2)\" : \"0 1px 3px rgba(0,0,0,0.05)\",\n transition: \"all 0.15s ease\",\n textAlign: \"left\",\n boxSizing: \"border-box\",\n }}>\n <span style={{ fontSize: \"1.1rem\", flexShrink: 0 }}>👤</span>\n <div style={{ overflow: \"hidden\", textOverflow: \"ellipsis\", whiteSpace: \"nowrap\", flex: 1, minWidth: 0 }}>\n <div style={{ fontWeight: 600, fontSize: \"0.78rem\", color: \"#0369a1\", textOverflow: \"ellipsis\", overflow: \"hidden\" }}>{u.displayName}</div>\n <div style={{ fontSize: \"0.68rem\", color: \"#64748b\", textOverflow: \"ellipsis\", overflow: \"hidden\" }}>{u.email}</div>\n </div>\n </button>\n )\n })}\n </div>\n </div>\n </div>\n )\n}\n\n/**\n * Graph tab: the authorization relations of the tenant, as a topology map or as linked columns.\n * グラフタブ。当該テナントの認可関係を、トポロジ図または連動カラム表示で俯瞰するコンポーネント。\n *\n * Reads {@link AuthzGraphData}. Selecting a node filters the other three columns to what that node\n * actually reaches, and the search box narrows every column at once.\n * {@link AuthzGraphData} を読む形 (ノード選択で他 3 カラムを到達範囲に絞り、検索は全カラムへ同時適用)。\n *\n * @returns The graph tab. グラフタブ。\n */\nexport function AuthzGraphView() {\n const { users, roles, resources, grants, assignments, groupRoles = [], groups = [] } = useLoaderData() as AuthzGraphData\n const [selectedNode, setSelectedNode] = useState<{ type: AuthzNodeType; id: number } | null>(null)\n const [searchQuery, setSearchQuery] = useState(\"\")\n const [viewMode, setViewMode] = useState<\"topology\" | \"columns\">(\"topology\")\n\n const roleMap = new Map(roles.map((r) => [r.id, r]))\n const resourceMap = new Map(resources.map((r) => [r.id, r]))\n const groupMap = new Map(groups.map((g) => [g.id, g]))\n const userMap = new Map(users.map((u) => [u.id, u]))\n\n /**\n * Whether a user is related to the selected node, in the graph tab's column view.\n * グラフタブのカラム表示において、当該ユーザーが選択ノードと関係するかどうかの判定。\n *\n * @param userId User id. ユーザー ID。\n * @returns `true` when nothing is selected, or the user is reachable. 未選択、または到達可能なら `true`。\n */\n const isUserRelated = (userId: number): boolean => {\n if (!selectedNode) return true\n if (selectedNode.type === \"user\") return selectedNode.id === userId\n if (selectedNode.type === \"role\") return assignments.some((a) => a.userId === userId && a.roleId === selectedNode.id)\n if (selectedNode.type === \"group\") return false\n if (selectedNode.type === \"resource\") {\n const roleIdsForResource = grants.filter((g) => g.resourceId === selectedNode.id).map((g) => g.roleId)\n return assignments.some((a) => a.userId === userId && roleIdsForResource.includes(a.roleId))\n }\n return true\n }\n\n /**\n * Whether a group is related to the selected node, in the graph tab's column view.\n * グラフタブのカラム表示において、当該グループが選択ノードと関係するかどうかの判定。\n *\n * @param groupId Group id. グループ ID。\n * @returns `true` when nothing is selected, or the group is reachable. 未選択、または到達可能なら `true`。\n */\n const isGroupRelated = (groupId: number): boolean => {\n if (!selectedNode) return true\n if (selectedNode.type === \"group\") return selectedNode.id === groupId\n if (selectedNode.type === \"role\") return groupRoles.some((gr) => gr.groupId === groupId && gr.roleId === selectedNode.id)\n if (selectedNode.type === \"user\") return false\n if (selectedNode.type === \"resource\") {\n const roleIdsForResource = grants.filter((g) => g.resourceId === selectedNode.id).map((g) => g.roleId)\n return groupRoles.some((gr) => gr.groupId === groupId && roleIdsForResource.includes(gr.roleId))\n }\n return true\n }\n\n /**\n * Whether a role is related to the selected node, in the graph tab's column view.\n * グラフタブのカラム表示において、当該ロールが選択ノードと関係するかどうかの判定。\n *\n * @param roleId Role id. ロール ID。\n * @returns `true` when nothing is selected, or the role is reachable. 未選択、または到達可能なら `true`。\n */\n const isRoleRelated = (roleId: number): boolean => {\n if (!selectedNode) return true\n if (selectedNode.type === \"role\") return selectedNode.id === roleId\n if (selectedNode.type === \"user\") return assignments.some((a) => a.userId === selectedNode.id && a.roleId === roleId)\n if (selectedNode.type === \"group\") return groupRoles.some((gr) => gr.groupId === selectedNode.id && gr.roleId === roleId)\n if (selectedNode.type === \"resource\") return grants.some((g) => g.resourceId === selectedNode.id && g.roleId === roleId)\n return true\n }\n\n /**\n * Whether a resource is related to the selected node, in the graph tab's column view.\n * グラフタブのカラム表示において、当該リソースが選択ノードと関係するかどうかの判定。\n *\n * @param resourceId Resource id. リソース ID。\n * @returns `true` when nothing is selected, or the resource is reachable. 未選択、または到達可能なら `true`。\n */\n const isResourceRelated = (resourceId: number): boolean => {\n if (!selectedNode) return true\n if (selectedNode.type === \"resource\") return selectedNode.id === resourceId\n if (selectedNode.type === \"role\") return grants.some((g) => g.roleId === selectedNode.id && g.resourceId === resourceId)\n if (selectedNode.type === \"group\") {\n const groupRoleIds = groupRoles.filter((gr) => gr.groupId === selectedNode.id).map((gr) => gr.roleId)\n return grants.some((g) => groupRoleIds.includes(g.roleId) && g.resourceId === resourceId)\n }\n if (selectedNode.type === \"user\") {\n const userRoleIds = assignments.filter((a) => a.userId === selectedNode.id).map((a) => a.roleId)\n return grants.some((g) => userRoleIds.includes(g.roleId) && g.resourceId === resourceId)\n }\n return true\n }\n\n const query = searchQuery.trim().toLowerCase()\n const filteredUsers = users.filter((u) => !query || u.displayName.toLowerCase().includes(query) || u.email?.toLowerCase().includes(query))\n const filteredGroups = groups.filter((g) => !query || g.name.toLowerCase().includes(query) || g.groupKey.toLowerCase().includes(query))\n const filteredRoles = roles.filter((r) => !query || r.name.toLowerCase().includes(query) || r.roleKey.toLowerCase().includes(query))\n const filteredResources = resources.filter((r) => !query || r.name.toLowerCase().includes(query) || r.resourceKey.toLowerCase().includes(query) || r.appKey.toLowerCase().includes(query))\n\n const selectedUserObj = selectedNode?.type === \"user\" ? userMap.get(selectedNode.id) : null\n const selectedGroupObj = selectedNode?.type === \"group\" ? groupMap.get(selectedNode.id) : null\n const selectedRoleObj = selectedNode?.type === \"role\" ? roleMap.get(selectedNode.id) : null\n const selectedResourceObj = selectedNode?.type === \"resource\" ? resourceMap.get(selectedNode.id) : null\n\n /**\n * Toggle the graph selection: clicking the selected node again clears the selection.\n * グラフの選択を切り替える処理 (選択中のノードを再クリックすると選択解除)。\n *\n * @param type Node kind that was clicked. クリックされたノード種別。\n * @param id Node id that was clicked. クリックされたノード ID。\n * @returns Nothing. 戻り値なし。\n */\n const handleNodeClick = (type: AuthzNodeType, id: number) => {\n if (selectedNode && selectedNode.type === type && selectedNode.id === id) {\n setSelectedNode(null)\n } else {\n setSelectedNode({ type, id })\n }\n }\n\n return (\n <section data-testid=\"authz-graph-view\" style={{ display: \"flex\", flexDirection: \"column\", gap: \"1rem\", width: \"100%\" }}>\n <div style={{ display: \"flex\", justifyContent: \"space-between\", alignItems: \"center\", flexWrap: \"wrap\", gap: \"0.75rem\", backgroundColor: \"#f8fafc\", padding: \"0.75rem 1rem\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\", width: \"100%\", boxSizing: \"border-box\" }}>\n <div style={{ display: \"flex\", alignItems: \"center\", gap: \"0.75rem\", flexWrap: \"wrap\" }}>\n <div style={{ display: \"inline-flex\", borderRadius: \"6px\", border: \"1px solid #cbd5e1\", padding: \"2px\", backgroundColor: \"#ffffff\" }}>\n <button\n type=\"button\"\n onClick={() => setViewMode(\"topology\")}\n data-testid=\"switch-mode-topology\"\n style={{\n padding: \"3px 10px\",\n borderRadius: \"4px\",\n border: \"none\",\n cursor: \"pointer\",\n fontSize: \"0.75rem\",\n fontWeight: viewMode === \"topology\" ? 600 : 400,\n backgroundColor: viewMode === \"topology\" ? \"#eff6ff\" : \"transparent\",\n color: viewMode === \"topology\" ? \"#0284c7\" : \"#64748b\",\n boxShadow: viewMode === \"topology\" ? \"0 1px 2px rgba(0,0,0,0.08)\" : \"none\",\n }}>\n 🌐 トポロジマップ (Topology)\n </button>\n <button\n type=\"button\"\n onClick={() => setViewMode(\"columns\")}\n data-testid=\"switch-mode-columns\"\n style={{\n padding: \"3px 10px\",\n borderRadius: \"4px\",\n border: \"none\",\n cursor: \"pointer\",\n fontSize: \"0.75rem\",\n fontWeight: viewMode === \"columns\" ? 600 : 400,\n backgroundColor: viewMode === \"columns\" ? \"#eff6ff\" : \"transparent\",\n color: viewMode === \"columns\" ? \"#0284c7\" : \"#64748b\",\n boxShadow: viewMode === \"columns\" ? \"0 1px 2px rgba(0,0,0,0.08)\" : \"none\",\n }}>\n 📋 カラム一覧 (Columns)\n </button>\n </div>\n <div style={{ display: \"flex\", gap: \"0.4rem\", fontSize: \"0.75rem\", flexWrap: \"wrap\" }}>\n <span style={{ backgroundColor: \"#e0f2fe\", color: \"#0369a1\", padding: \"2px 8px\", borderRadius: \"12px\", fontWeight: 600 }}>👤 ユーザー: {users.length}</span>\n <span style={{ backgroundColor: \"#f3e8ff\", color: \"#6d28d9\", padding: \"2px 8px\", borderRadius: \"12px\", fontWeight: 600 }}>👥 グループ: {groups.length}</span>\n <span style={{ backgroundColor: \"#e0e7ff\", color: \"#4338ca\", padding: \"2px 8px\", borderRadius: \"12px\", fontWeight: 600 }}>🛡️ ロール: {roles.length}</span>\n <span style={{ backgroundColor: \"#d1fae5\", color: \"#047857\", padding: \"2px 8px\", borderRadius: \"12px\", fontWeight: 600 }}>🔑 リソース: {resources.length}</span>\n <span style={{ backgroundColor: \"#fef3c7\", color: \"#b45309\", padding: \"2px 8px\", borderRadius: \"12px\", fontWeight: 600 }}>⚡️ 権限設定: {grants.length}</span>\n </div>\n </div>\n <div style={{ display: \"flex\", alignItems: \"center\", gap: \"0.5rem\" }}>\n <input\n type=\"text\"\n placeholder=\"キーワードで絞り込み...\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n data-testid=\"graph-search-input\"\n style={{ padding: \"5px 10px\", borderRadius: \"6px\", border: \"1px solid #cbd5e1\", fontSize: \"0.8rem\", width: \"200px\" }}\n />\n </div>\n </div>\n\n {/* 常時高さ固定のステータスバー (高さ38px固定でレイアウトシフトを防止) */}\n <div\n style={{\n minHeight: \"38px\",\n height: \"38px\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"space-between\",\n backgroundColor: selectedNode ? \"#eff6ff\" : \"#f8fafc\",\n border: `1px solid ${selectedNode ? \"#bfdbfe\" : \"#e2e8f0\"}`,\n padding: \"0 0.8rem\",\n borderRadius: \"6px\",\n fontSize: \"0.8rem\",\n color: selectedNode ? \"#1e40af\" : \"#64748b\",\n width: \"100%\",\n boxSizing: \"border-box\",\n transition: \"all 0.15s ease\",\n }}>\n <div style={{ display: \"flex\", alignItems: \"center\", gap: \"0.4rem\", overflow: \"hidden\", textOverflow: \"ellipsis\", whiteSpace: \"nowrap\" }}>\n {selectedNode ? (\n <>\n <span>🎯</span>\n <strong>選択中のノード:</strong>\n <span>\n {selectedUserObj && `ユーザー「${selectedUserObj.displayName} (${selectedUserObj.email})」に関連するパスを表示中`}\n {selectedGroupObj && `グループ「${selectedGroupObj.name} (${selectedGroupObj.groupKey})」に関連するパスを表示中`}\n {selectedRoleObj && `ロール「${selectedRoleObj.name} (${selectedRoleObj.roleKey})」に関連するパスを表示中`}\n {selectedResourceObj && `リソース「${selectedResourceObj.name} (${selectedResourceObj.appKey}:${selectedResourceObj.resourceKey})」に関連するパスを表示中`}\n </span>\n </>\n ) : (\n <>\n <span>💡</span>\n <span>\n <strong>ヒント:</strong> ノード(リソース・ロール・グループ・ユーザー)をクリックすると、関連する全パスがハイライト表示されます\n </span>\n </>\n )}\n </div>\n {selectedNode && (\n <button\n type=\"button\"\n onClick={() => setSelectedNode(null)}\n data-testid=\"graph-reset-selection\"\n style={{\n padding: \"3px 8px\",\n borderRadius: \"4px\",\n border: \"1px solid #93c5fd\",\n backgroundColor: \"#ffffff\",\n fontSize: \"0.72rem\",\n cursor: \"pointer\",\n color: \"#1d4ed8\",\n fontWeight: 600,\n whiteSpace: \"nowrap\",\n boxShadow: \"0 1px 2px rgba(0,0,0,0.05)\",\n }}>\n ✕ 選択解除\n </button>\n )}\n </div>\n\n {viewMode === \"topology\" ? (\n <AuthzTopologyMap users={filteredUsers} roles={filteredRoles} resources={filteredResources} grants={grants} assignments={assignments} groupRoles={groupRoles} groups={filteredGroups} selectedNode={selectedNode} onNodeClick={handleNodeClick} />\n ) : (\n <div style={{ display: \"grid\", gridTemplateColumns: \"repeat(4, minmax(0, 1fr))\", gap: \"1rem\", alignItems: \"start\", width: \"100%\", boxSizing: \"border-box\" }}>\n {/* Column 1: Resources */}\n <div style={{ backgroundColor: \"#ffffff\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\", padding: \"0.75rem\" }}>\n <div style={{ fontWeight: 700, fontSize: \"0.85rem\", color: \"#047857\", marginBottom: \"0.6rem\", display: \"flex\", alignItems: \"center\", gap: \"0.3rem\", borderBottom: \"2px solid #a7f3d0\", paddingBottom: \"0.4rem\" }}>\n <span>🔑</span> リソース ({filteredResources.length})\n </div>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: \"0.5rem\", maxHeight: \"480px\", overflowY: \"auto\", paddingRight: \"4px\" }}>\n {filteredResources.length === 0 && <div style={{ fontSize: \"0.8rem\", color: \"#94a3b8\", textAlign: \"center\", padding: \"1rem\" }}>対象のリソースがありません</div>}\n {filteredResources.map((res) => {\n const isSelected = selectedNode?.type === \"resource\" && selectedNode.id === res.id\n const active = isResourceRelated(res.id)\n const resourceGrants = grants.filter((g) => g.resourceId === res.id)\n\n return (\n <button\n type=\"button\"\n key={res.id}\n onClick={() => handleNodeClick(\"resource\", res.id)}\n data-testid={`graph-node-resource-${res.id}`}\n style={{\n textAlign: \"left\",\n width: \"100%\",\n padding: \"0.6rem\",\n borderRadius: \"6px\",\n border: isSelected ? \"2px solid #10b981\" : \"1px solid #e2e8f0\",\n backgroundColor: isSelected ? \"#d1fae5\" : active ? \"#ffffff\" : \"#f8fafc\",\n opacity: active ? 1 : 0.4,\n cursor: \"pointer\",\n transition: \"all 0.15s ease\",\n boxShadow: isSelected ? \"0 2px 4px rgba(16, 185, 129, 0.15)\" : \"none\",\n }}>\n <div style={{ fontWeight: 600, fontSize: \"0.82rem\", color: \"#0f172a\" }}>{res.name}</div>\n <div style={{ fontSize: \"0.72rem\", color: \"#64748b\", fontFamily: \"monospace\" }}>\n {res.appKey}:{res.resourceKey}\n </div>\n <div style={{ display: \"flex\", flexWrap: \"wrap\", gap: \"0.25rem\", marginTop: \"0.4rem\" }}>\n {resourceGrants.map((g) => {\n const role = roleMap.get(g.roleId)\n const actStyle = ACTION_COLORS[g.action] ?? { bg: \"#f1f5f9\", text: \"#475569\", border: \"#cbd5e1\" }\n return (\n <span\n key={g.id}\n style={{\n fontSize: \"0.68rem\",\n padding: \"1px 6px\",\n borderRadius: \"4px\",\n backgroundColor: actStyle.bg,\n color: actStyle.text,\n border: `1px solid ${actStyle.border}`,\n }}>\n {role?.roleKey ?? `Role#${g.roleId}`}: <strong>{g.action}</strong>\n </span>\n )\n })}\n {resourceGrants.length === 0 && <span style={{ fontSize: \"0.68rem\", color: \"#94a3b8\" }}>権限設定なし</span>}\n </div>\n </button>\n )\n })}\n </div>\n </div>\n\n {/* Column 2: Roles */}\n <div style={{ backgroundColor: \"#ffffff\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\", padding: \"0.75rem\" }}>\n <div style={{ fontWeight: 700, fontSize: \"0.85rem\", color: \"#4338ca\", marginBottom: \"0.6rem\", display: \"flex\", alignItems: \"center\", gap: \"0.3rem\", borderBottom: \"2px solid #c7d2fe\", paddingBottom: \"0.4rem\" }}>\n <span>🛡️</span> ロール ({filteredRoles.length})\n </div>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: \"0.5rem\", maxHeight: \"480px\", overflowY: \"auto\", paddingRight: \"4px\" }}>\n {filteredRoles.length === 0 && <div style={{ fontSize: \"0.8rem\", color: \"#94a3b8\", textAlign: \"center\", padding: \"1rem\" }}>対象のロールがありません</div>}\n {filteredRoles.map((r) => {\n const isSelected = selectedNode?.type === \"role\" && selectedNode.id === r.id\n const active = isRoleRelated(r.id)\n const roleGrants = grants.filter((g) => g.roleId === r.id)\n const roleAssignments = assignments.filter((a) => a.roleId === r.id)\n const roleGroupAssignments = groupRoles.filter((gr) => gr.roleId === r.id)\n\n return (\n <button\n type=\"button\"\n key={r.id}\n onClick={() => handleNodeClick(\"role\", r.id)}\n data-testid={`graph-node-role-${r.id}`}\n style={{\n textAlign: \"left\",\n width: \"100%\",\n padding: \"0.6rem\",\n borderRadius: \"6px\",\n border: isSelected ? \"2px solid #6366f1\" : \"1px solid #e2e8f0\",\n backgroundColor: isSelected ? \"#e0e7ff\" : active ? \"#ffffff\" : \"#f8fafc\",\n opacity: active ? 1 : 0.4,\n cursor: \"pointer\",\n transition: \"all 0.15s ease\",\n boxShadow: isSelected ? \"0 2px 4px rgba(99, 102, 241, 0.15)\" : \"none\",\n }}>\n <div style={{ fontWeight: 600, fontSize: \"0.82rem\", color: \"#0f172a\" }}>{r.name}</div>\n <div style={{ fontSize: \"0.72rem\", color: \"#64748b\", fontFamily: \"monospace\" }}>{r.roleKey}</div>\n <div style={{ fontSize: \"0.7rem\", color: \"#475569\", marginTop: \"0.3rem\", display: \"flex\", gap: \"0.5rem\" }}>\n <span>権限: {roleGrants.length}</span>\n <span>グループ: {roleGroupAssignments.length}</span>\n <span>ユーザー: {roleAssignments.length}</span>\n </div>\n </button>\n )\n })}\n </div>\n </div>\n\n {/* Column 3: Groups */}\n <div style={{ backgroundColor: \"#ffffff\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\", padding: \"0.75rem\" }}>\n <div style={{ fontWeight: 700, fontSize: \"0.85rem\", color: \"#6d28d9\", marginBottom: \"0.6rem\", display: \"flex\", alignItems: \"center\", gap: \"0.3rem\", borderBottom: \"2px solid #ddd6fe\", paddingBottom: \"0.4rem\" }}>\n <span>👥</span> グループ ({filteredGroups.length})\n </div>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: \"0.5rem\", maxHeight: \"480px\", overflowY: \"auto\", paddingRight: \"4px\" }}>\n {filteredGroups.length === 0 && <div style={{ fontSize: \"0.8rem\", color: \"#94a3b8\", textAlign: \"center\", padding: \"1rem\" }}>対象のグループがありません</div>}\n {filteredGroups.map((g) => {\n const isSelected = selectedNode?.type === \"group\" && selectedNode.id === g.id\n const active = isGroupRelated(g.id)\n const assignedRoles = groupRoles.filter((gr) => gr.groupId === g.id)\n\n return (\n <button\n type=\"button\"\n key={g.id}\n onClick={() => handleNodeClick(\"group\", g.id)}\n data-testid={`graph-node-group-${g.id}`}\n style={{\n textAlign: \"left\",\n width: \"100%\",\n padding: \"0.6rem\",\n borderRadius: \"6px\",\n border: isSelected ? \"2px solid #8b5cf6\" : \"1px solid #e2e8f0\",\n backgroundColor: isSelected ? \"#f3e8ff\" : active ? \"#ffffff\" : \"#f8fafc\",\n opacity: active ? 1 : 0.4,\n cursor: \"pointer\",\n transition: \"all 0.15s ease\",\n boxShadow: isSelected ? \"0 2px 4px rgba(139, 92, 246, 0.15)\" : \"none\",\n }}>\n <div style={{ fontWeight: 600, fontSize: \"0.82rem\", color: \"#0f172a\" }}>{g.name}</div>\n <div style={{ fontSize: \"0.72rem\", color: \"#64748b\", fontFamily: \"monospace\" }}>{g.groupKey}</div>\n <div style={{ display: \"flex\", flexWrap: \"wrap\", gap: \"0.25rem\", marginTop: \"0.4rem\" }}>\n {assignedRoles.map((gr) => {\n const role = roleMap.get(gr.roleId)\n return (\n <span\n key={`${gr.groupId}-${gr.roleId}`}\n style={{\n fontSize: \"0.68rem\",\n padding: \"1px 6px\",\n borderRadius: \"4px\",\n backgroundColor: \"#f3e8ff\",\n color: \"#6d28d9\",\n border: \"1px solid #ddd6fe\",\n fontWeight: 600,\n }}>\n 🛡️ {role?.name ?? gr.roleKey}\n </span>\n )\n })}\n {assignedRoles.length === 0 && <span style={{ fontSize: \"0.68rem\", color: \"#94a3b8\" }}>ロール未割り当て</span>}\n </div>\n </button>\n )\n })}\n </div>\n </div>\n\n {/* Column 4: Users */}\n <div style={{ backgroundColor: \"#ffffff\", borderRadius: \"8px\", border: \"1px solid #e2e8f0\", padding: \"0.75rem\" }}>\n <div style={{ fontWeight: 700, fontSize: \"0.85rem\", color: \"#0369a1\", marginBottom: \"0.6rem\", display: \"flex\", alignItems: \"center\", gap: \"0.3rem\", borderBottom: \"2px solid #bae6fd\", paddingBottom: \"0.4rem\" }}>\n <span>👤</span> ユーザー ({filteredUsers.length})\n </div>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: \"0.5rem\", maxHeight: \"480px\", overflowY: \"auto\", paddingRight: \"4px\" }}>\n {filteredUsers.length === 0 && <div style={{ fontSize: \"0.8rem\", color: \"#94a3b8\", textAlign: \"center\", padding: \"1rem\" }}>対象のユーザーがありません</div>}\n {filteredUsers.map((u) => {\n const isSelected = selectedNode?.type === \"user\" && selectedNode.id === u.id\n const active = isUserRelated(u.id)\n const userAssignments = assignments.filter((a) => a.userId === u.id)\n\n return (\n <button\n type=\"button\"\n key={u.id}\n onClick={() => handleNodeClick(\"user\", u.id)}\n data-testid={`graph-node-user-${u.id}`}\n style={{\n textAlign: \"left\",\n width: \"100%\",\n padding: \"0.6rem\",\n borderRadius: \"6px\",\n border: isSelected ? \"2px solid #0284c7\" : \"1px solid #e2e8f0\",\n backgroundColor: isSelected ? \"#e0f2fe\" : active ? \"#ffffff\" : \"#f8fafc\",\n opacity: active ? 1 : 0.4,\n cursor: \"pointer\",\n transition: \"all 0.15s ease\",\n boxShadow: isSelected ? \"0 2px 4px rgba(2, 132, 199, 0.15)\" : \"none\",\n }}>\n <div style={{ fontWeight: 600, fontSize: \"0.82rem\", color: \"#0f172a\" }}>{u.displayName}</div>\n <div style={{ fontSize: \"0.72rem\", color: \"#64748b\" }}>{u.email}</div>\n <div style={{ display: \"flex\", flexWrap: \"wrap\", gap: \"0.25rem\", marginTop: \"0.4rem\" }}>\n {userAssignments.map((a) => {\n const role = roleMap.get(a.roleId)\n return (\n <span\n key={`${a.userId}-${a.roleId}`}\n style={{\n fontSize: \"0.68rem\",\n padding: \"1px 6px\",\n borderRadius: \"4px\",\n backgroundColor: \"#e0f2fe\",\n color: \"#0369a1\",\n border: \"1px solid #bae6fd\",\n fontWeight: 600,\n }}>\n 🛡️ {role?.name ?? a.roleKey}\n </span>\n )\n })}\n {userAssignments.length === 0 && <span style={{ fontSize: \"0.68rem\", color: \"#94a3b8\" }}>直接ロールなし</span>}\n </div>\n </button>\n )\n })}\n </div>\n </div>\n </div>\n )}\n </section>\n )\n}\n\n// ============================ Single-file (splat) app view ============================\n\nconst SEGMENT_VIEWS: Record<string, () => React.ReactElement> = {\n roles: AuthzRolesView,\n resources: AuthzResourcesView,\n role_permissions: AuthzGrantsView,\n user_roles: AuthzUserRolesView,\n group_roles: AuthzGroupRolesView,\n graph: AuthzGraphView,\n}\n\nexport type AuthzAdminAppViewProps = {\n renderHeader?: (props: { title: string; annotation: React.ReactNode }) => React.ReactNode\n}\n\n/**\n * Render the whole admin UI from a single splat route (`authz/*`), without depending on any store.\n * スプラット (`authz/*`) 1 ルートで認可管理 UI 全体を描画するコンポーネント (store 非依存)。\n *\n * The loader's `segment` selects the tab; the shell and the tab read one merged loader payload\n * (the segment's rows plus `myPermissions` and `labels` at the top level).\n * loader が返す `segment` に応じてタブを選ぶ形 (外枠と各ビューは統合済みの同一 loader データを読む)。\n *\n * @param props Optional header renderer. 任意のヘッダ描画関数。\n * @returns The admin page for the requested segment. 要求セグメントに対応する管理画面。\n */\nexport function AuthzAdminAppView({ renderHeader }: AuthzAdminAppViewProps = {}) {\n const { segment } = useLoaderData() as { segment: string }\n const View = SEGMENT_VIEWS[segment] ?? AuthzRolesView\n return (\n <AuthzLayoutShell renderHeader={renderHeader}>\n <View />\n </AuthzLayoutShell>\n )\n}\n","/**\n * Multi-resource permission widget (inline pills or a popover card) for pages spanning several resources.\n * 複数リソースにまたがる画面向けの権限表示ウィジェット (インラインのタグ表示とポップオーバー表示)。\n */\nimport { type CSSProperties, useEffect, useRef, useState } from \"react\"\nimport { MyPermissionIndicator } from \"./MyPermissionIndicator\"\nimport type { PermissionView } from \"./server\"\n\nexport type MyPermissionGroupItem = {\n /** Expected or actual resource key */\n resourceKey?: string\n /** Human-readable resource display name (e.g. \"NI\", \"KT\", \"オリジナル\") */\n resourceName: string\n /** Permission view data for this resource */\n permission: PermissionView | null | undefined\n /** Expected resource key to detect mismatches with permission.resourceKey during development */\n expectedResourceKey?: string\n /** Nesting level for hierarchical rendering (0: root, 1: child, etc.) */\n level?: number\n /** Optional child items for hierarchical rendering */\n children?: MyPermissionGroupItem[]\n}\n\n/**\n * Flattens hierarchical items tree into a single array with computed nesting levels.\n * 木構造またはフラット配列のアイテムに level 属性を付与してフラット化する処理。\n */\nexport function flattenGroupItems(items: MyPermissionGroupItem[], currentLevel = 0): MyPermissionGroupItem[] {\n const result: MyPermissionGroupItem[] = []\n for (const item of items) {\n const itemLevel = item.level ?? currentLevel\n result.push({ ...item, level: itemLevel })\n if (item.children && item.children.length > 0) {\n result.push(...flattenGroupItems(item.children, itemLevel + 1))\n }\n }\n return result\n}\n\nexport type MyPermissionGroupStatusProps = {\n /** List of resources and their permissions */\n items: MyPermissionGroupItem[]\n /** Prefix label shown before group items or inside popover header. Default: \"あなたの権限\" */\n prefixLabel?: string\n /**\n * Display style variant:\n * - \"pills\": Inline parent label + compact status tags per item\n * - \"popover\": Compact single button trigger opening detailed popup card\n * Default: \"pills\"\n */\n variant?: \"pills\" | \"popover\"\n /**\n * Component size variant:\n * - \"2xs\": Ultra micro size for extremely dense UI\n * - \"xs\": Extra small size for compact headers\n * - \"sm\": Small size\n * - \"md\": Standard default size\n * - \"lg\": Larger size\n * Default: \"md\"\n */\n size?: \"2xs\" | \"xs\" | \"sm\" | \"md\" | \"lg\"\n labels?: {\n read?: string\n write?: string\n delete?: string\n noPermission?: string\n fullWrite?: string\n partialWrite?: string\n readOnly?: string\n partialRead?: string\n }\n className?: string\n style?: CSSProperties\n}\n\nconst SIZE_CONFIGS = {\n \"2xs\": {\n fontSize: \"0.6rem\",\n pillPadding: \"0.05rem 0.25rem\",\n pillGap: \"0.15rem\",\n wrapperGap: \"0.15rem\",\n triggerPadding: \"0.1rem 0.35rem\",\n triggerFontSize: \"0.62rem\",\n badgeFontSize: \"0.58rem\",\n },\n xs: {\n fontSize: \"0.65rem\",\n pillPadding: \"0.08rem 0.35rem\",\n pillGap: \"0.18rem\",\n wrapperGap: \"0.2rem\",\n triggerPadding: \"0.12rem 0.4rem\",\n triggerFontSize: \"0.68rem\",\n badgeFontSize: \"0.62rem\",\n },\n sm: {\n fontSize: \"0.72rem\",\n pillPadding: \"0.1rem 0.4rem\",\n pillGap: \"0.2rem\",\n wrapperGap: \"0.25rem\",\n triggerPadding: \"0.15rem 0.45rem\",\n triggerFontSize: \"0.72rem\",\n badgeFontSize: \"0.65rem\",\n },\n md: {\n fontSize: \"0.78rem\",\n pillPadding: \"0.15rem 0.5rem\",\n pillGap: \"0.3rem\",\n wrapperGap: \"0.35rem\",\n triggerPadding: \"0.2rem 0.6rem\",\n triggerFontSize: \"0.78rem\",\n badgeFontSize: \"0.7rem\",\n },\n lg: {\n fontSize: \"0.85rem\",\n pillPadding: \"0.25rem 0.65rem\",\n pillGap: \"0.4rem\",\n wrapperGap: \"0.45rem\",\n triggerPadding: \"0.25rem 0.75rem\",\n triggerFontSize: \"0.85rem\",\n badgeFontSize: \"0.75rem\",\n },\n} as const\n\n/**\n * Get human-readable summary text and style for a resource permission.\n * 権限状態から簡潔なサマリテキストとスタイルを判定 (カスタムラベル対応)。\n */\nexport function getPermissionSummary(\n permission: PermissionView | null | undefined,\n customLabels?: MyPermissionGroupStatusProps[\"labels\"],\n): {\n text: string\n color: string\n bgColor: string\n borderColor: string\n} {\n const txtNoPerm = customLabels?.noPermission ?? \"権限なし\"\n const txtFullWrite = customLabels?.fullWrite ?? \"編集可\"\n const txtPartialRead = customLabels?.partialRead ?? \"一部閲覧\"\n const txtReadOnly = customLabels?.readOnly ?? \"閲覧のみ\"\n\n if (!(permission && (permission.canRead || permission.canWrite || permission.canDelete))) {\n return { text: txtNoPerm, color: \"#64748b\", bgColor: \"#f1f5f9\", borderColor: \"#cbd5e1\" }\n }\n if (permission.canWrite) {\n return { text: txtFullWrite, color: \"#047857\", bgColor: \"#ecfdf5\", borderColor: \"#a7f3d0\" }\n }\n if (permission.canRead) {\n if (permission.readPartial) {\n return { text: txtPartialRead, color: \"#b45309\", bgColor: \"#fffbeb\", borderColor: \"#fde68a\" }\n }\n return { text: txtReadOnly, color: \"#1d4ed8\", bgColor: \"#eff6ff\", borderColor: \"#bfdbfe\" }\n }\n return { text: txtNoPerm, color: \"#64748b\", bgColor: \"#f1f5f9\", borderColor: \"#cbd5e1\" }\n}\n\n/**\n * Render compact status pill for a single resource item in \"pills\" mode.\n * 「pills」モード用: リソースごとのコンパクトなステータスタグを描画。\n */\nfunction GroupItemPill({ item, size = \"md\", labels }: { item: MyPermissionGroupItem; size?: \"2xs\" | \"xs\" | \"sm\" | \"md\" | \"lg\"; labels?: MyPermissionGroupStatusProps[\"labels\"] }) {\n const summary = getPermissionSummary(item.permission, labels)\n const resKey = item.permission?.resourceKey || item.resourceKey || item.expectedResourceKey\n const resName = item.resourceName || resKey\n const hasMismatch = Boolean(item.expectedResourceKey && item.permission?.resourceKey && item.permission.resourceKey !== item.expectedResourceKey)\n const cfg = SIZE_CONFIGS[size]\n\n const tooltipText = `対象: ${resName}${resKey ? ` (${resKey})` : \"\"}${item.permission ? ` | ${labels?.read ?? \"閲覧\"}:${item.permission.canRead ? \"○\" : \"×\"} ${labels?.write ?? \"編集\"}:${item.permission.canWrite ? \"○\" : \"×\"} ${labels?.delete ?? \"削除\"}:${item.permission.canDelete ? \"○\" : \"×\"}` : \"\"}`\n\n return (\n <span\n title={tooltipText}\n data-testid={`permission-pill-${item.resourceName}`}\n data-mismatch={hasMismatch ? \"true\" : \"false\"}\n style={{\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: cfg.pillGap,\n fontSize: cfg.fontSize,\n padding: cfg.pillPadding,\n borderRadius: \"9999px\",\n backgroundColor: hasMismatch ? \"#fee2e2\" : summary.bgColor,\n color: hasMismatch ? \"#991b1b\" : summary.color,\n border: `1px solid ${hasMismatch ? \"#fca5a5\" : summary.borderColor}`,\n fontWeight: 600,\n lineHeight: \"1.2\",\n }}>\n <span>{resName}:</span>\n <span>{summary.text}</span>\n {hasMismatch && <span title={`expected \"${item.expectedResourceKey}\", actual \"${item.permission?.resourceKey}\"`}>⚠️</span>}\n </span>\n )\n}\n\n/**\n * Multi-resource \"Your Permissions\" status component.\n * Supports inline compact pills (Proposal 1) and dropdown popover card (Proposal 2).\n * 複数リソースの権限状態を共通レイアウト(pills/popover)で綺麗に整列・表示するコンポーネント。\n */\nexport function MyPermissionGroupStatus({ items, prefixLabel = \"あなたの権限\", variant = \"pills\", size = \"md\", labels, className = \"authz-permission-group-status\", style }: MyPermissionGroupStatusProps) {\n const [isOpen, setIsOpen] = useState(false)\n const popoverRef = useRef<HTMLSpanElement>(null)\n const cfg = SIZE_CONFIGS[size]\n\n // 外側クリックまたは Escape キーでポップオーバーを閉じる\n useEffect(() => {\n if (variant !== \"popover\" || !isOpen) return\n\n /**\n * Close the popover when the click landed outside it.\n * ポップオーバーの外側がクリックされた場合に閉じる処理。\n *\n * @param event The document-level mouse event. ドキュメント全体のマウスイベント。\n * @returns Nothing. 戻り値なし。\n */\n function handleClickOutside(event: MouseEvent) {\n if (popoverRef.current && !popoverRef.current.contains(event.target as Node)) {\n setIsOpen(false)\n }\n }\n /**\n * Close the popover when Escape is pressed.\n * Escape キー押下でポップオーバーを閉じる処理。\n *\n * @param event The document-level keyboard event. ドキュメント全体のキーボードイベント。\n * @returns Nothing. 戻り値なし。\n */\n function handleKeyDown(event: KeyboardEvent) {\n if (event.key === \"Escape\") {\n setIsOpen(false)\n }\n }\n\n document.addEventListener(\"mousedown\", handleClickOutside)\n document.addEventListener(\"keydown\", handleKeyDown)\n return () => {\n document.removeEventListener(\"mousedown\", handleClickOutside)\n document.removeEventListener(\"keydown\", handleKeyDown)\n }\n }, [variant, isOpen])\n\n if (variant === \"popover\") {\n // トリガーの総括文言。**権限が無い/データが無いときに許可を名乗らない** ことが要件。\n // 以前は項目 0 件で `0 === 0` が成立して「編集可」、全項目が権限なしでも「閲覧のみ」と表示していた。\n // 子要素 (`children`) も対象に含める。トップレベルだけを数えると、書き込み可の親の下に\n // 権限なしの子がぶら下がっていても「編集可」と名乗ってしまう。\n const allItems = flattenGroupItems(items)\n const writeCount = allItems.filter((i) => i.permission?.canWrite).length\n const readCount = allItems.filter((i) => i.permission?.canRead).length\n const totalCount = allItems.length\n const summaryLabel = totalCount === 0 || (writeCount === 0 && readCount === 0) ? (labels?.noPermission ?? \"権限なし\") : writeCount === totalCount ? (labels?.fullWrite ?? \"編集可\") : writeCount > 0 ? (labels?.partialWrite ?? \"一部編集可\") : (labels?.readOnly ?? \"閲覧のみ\")\n\n return (\n <span ref={popoverRef} className={className} data-testid=\"my-permission-group-popover\" style={{ position: \"relative\", display: \"inline-block\", ...style }}>\n <button\n type=\"button\"\n onClick={() => setIsOpen(!isOpen)}\n data-testid=\"permission-popover-trigger\"\n style={{\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: cfg.wrapperGap,\n fontSize: cfg.triggerFontSize,\n padding: cfg.triggerPadding,\n borderRadius: \"6px\",\n backgroundColor: \"#f8fafc\",\n color: \"#334155\",\n border: \"1px solid #cbd5e1\",\n fontWeight: 600,\n cursor: \"pointer\",\n }}>\n <span>🔒 {prefixLabel}</span>\n <span style={{ fontSize: cfg.badgeFontSize, color: \"#64748b\" }}>({summaryLabel})</span>\n <span style={{ fontSize: \"0.65rem\", marginLeft: \"0.1rem\" }}>{isOpen ? \"▲\" : \"▾\"}</span>\n </button>\n\n {isOpen && (\n <div\n data-testid=\"permission-popover-card\"\n style={{\n position: \"absolute\",\n top: \"calc(100% + 6px)\",\n right: 0,\n zIndex: 50,\n minWidth: \"280px\",\n whiteSpace: \"nowrap\",\n padding: \"0.75rem 0.85rem\",\n backgroundColor: \"#ffffff\",\n borderRadius: \"8px\",\n boxShadow: \"0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)\",\n border: \"1px solid #e2e8f0\",\n fontSize: \"0.82rem\",\n }}>\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"space-between\",\n marginBottom: \"0.5rem\",\n paddingBottom: \"0.4rem\",\n borderBottom: \"1px solid #f1f5f9\",\n fontWeight: 700,\n color: \"#1e293b\",\n whiteSpace: \"nowrap\",\n }}>\n <span>🔒 {prefixLabel}</span>\n <button\n type=\"button\"\n onClick={() => setIsOpen(false)}\n style={{\n border: \"none\",\n background: \"none\",\n cursor: \"pointer\",\n color: \"#94a3b8\",\n fontSize: \"0.85rem\",\n padding: \"0 0.2rem\",\n }}>\n ✕\n </button>\n </div>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: \"0.4rem\" }}>\n {flattenGroupItems(items).map((item) => {\n const level = item.level ?? 0\n const isChild = level > 0\n return (\n <div\n key={item.resourceName}\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"space-between\",\n gap: \"1.25rem\",\n whiteSpace: \"nowrap\",\n paddingLeft: isChild ? `${level * 0.85}rem` : \"0\",\n }}>\n <div style={{ display: \"inline-flex\", alignItems: \"center\", whiteSpace: \"nowrap\" }}>\n {isChild && (\n <span\n aria-hidden=\"true\"\n style={{\n display: \"inline-block\",\n width: \"10px\",\n height: \"10px\",\n borderLeft: \"1.5px solid #cbd5e1\",\n borderBottom: \"1.5px solid #cbd5e1\",\n borderBottomLeftRadius: \"3px\",\n marginRight: \"6px\",\n marginTop: \"-4px\",\n flexShrink: 0,\n }}\n />\n )}\n <span\n style={{\n fontWeight: isChild ? 500 : 600,\n color: isChild ? \"#64748b\" : \"#1e293b\",\n fontSize: isChild ? \"0.78rem\" : \"0.82rem\",\n whiteSpace: \"nowrap\",\n }}>\n {item.resourceName}:\n </span>\n </div>\n <MyPermissionIndicator permission={item.permission} resourceName={item.resourceName} expectedResourceKey={item.expectedResourceKey} labels={labels} />\n </div>\n )\n })}\n </div>\n </div>\n )}\n </span>\n )\n }\n\n // 既定の \"pills\" バリアント\n return (\n <span\n className={className}\n data-testid=\"my-permission-group-pills\"\n style={{\n display: \"inline-flex\",\n alignItems: \"center\",\n flexWrap: \"wrap\",\n gap: cfg.pillGap,\n fontSize: cfg.fontSize,\n color: \"#475569\",\n ...style,\n }}>\n {prefixLabel && <span style={{ fontWeight: 600, color: \"#334155\" }}>{prefixLabel}:</span>}\n <span style={{ display: \"inline-flex\", alignItems: \"center\", flexWrap: \"wrap\", gap: cfg.pillGap }}>\n {items.map((item) => (\n <GroupItemPill key={item.resourceName} item={item} size={size} labels={labels} />\n ))}\n </span>\n </span>\n )\n}\n","/**\n * Display-state derivation for a {@link PermissionView} (pure, no React state involved).\n * {@link PermissionView} から表示状態を導出するモジュール (React の状態は一切持たない純粋関数)。\n */\nimport type { PermissionView } from \"./server\"\n\nexport type PermState = \"full\" | \"partial\" | \"none\"\n\n/**\n * Display-oriented reading of a {@link PermissionView}: the same `can*` booleans (safe defaults when the\n * view is absent) plus the ○△× tri-state used by the indicator components.\n * {@link PermissionView} の表示向け読み取り結果。同名の `can*` 真偽値と ○△× の三値状態を併せ持つ形。\n */\nexport type UsePermission = {\n canRead: boolean\n canCreate: boolean\n canUpdate: boolean\n canDelete: boolean\n /** create ∪ update */\n canWrite: boolean\n /** read: none(×) / partial(△, row|col restricted) / full(○). */\n readState: PermState\n /** write = create ∪ update: full(○ both) / partial(△ one) / none(×). */\n writeState: PermState\n deleteState: PermState\n}\n\n/**\n * Derive ○△× display states from a {@link PermissionView}. Pure (safe to call in render);\n * named `usePermission` for ergonomic call-sites.\n * {@link PermissionView} から ○△× の表示状態を導出する純粋関数 (レンダー内から安全に呼べる形)。\n *\n * @returns The permission booleans plus their display states. 権限の真偽値と表示状態の組。\n */\nexport function usePermission(view: PermissionView | null | undefined): UsePermission {\n const readState: PermState = !view?.canRead ? \"none\" : view.readPartial ? \"partial\" : \"full\"\n const writeState: PermState = view?.canCreate && view?.canUpdate ? \"full\" : view?.canCreate || view?.canUpdate ? \"partial\" : \"none\"\n const deleteState: PermState = view?.canDelete ? \"full\" : \"none\"\n return {\n canRead: !!view?.canRead,\n canCreate: !!view?.canCreate,\n canUpdate: !!view?.canUpdate,\n canDelete: !!view?.canDelete,\n canWrite: !!view?.canWrite,\n readState,\n writeState,\n deleteState,\n }\n}\n","/**\n * Badge components that show the acting user their own permissions for one resource.\n * 操作ユーザー自身の権限を 1 リソース分バッジ表示するコンポーネント群。\n */\nimport type { PermissionView } from \"./server\"\nimport { type PermState, usePermission } from \"./usePermission\"\n\nexport type MyPermissionIndicatorProps<K extends string = string> = {\n permission: PermissionView<K> | null | undefined\n /** Human-readable resource display name (e.g. \"文書データ\"). Falls back to permission.resourceKey. */\n resourceName?: string\n /**\n * Expected resource key. Typed as `NoInfer<K>`, so when the permission carries a literal key\n * (`getMyPermissions` / `requirePermission` both do) a mismatch is a **compile error**; the runtime\n * detector below stays for dynamically-keyed and JavaScript callers.\n */\n expectedResourceKey?: NoInfer<K>\n /** Whether to include resource name/key tooltip. Default true. */\n showResourceTooltip?: boolean\n labels?: { read?: string; write?: string; delete?: string }\n /** Show a \"(部分)\" hint when read is partial. Default true. */\n showScopeSummary?: boolean\n className?: string\n}\n\nexport type MyPermissionStatusProps<K extends string = string> = MyPermissionIndicatorProps<K> & {\n /** Label prefix shown before the indicator badges. Default: \"あなたの権限\" */\n prefixLabel?: string\n /** Whether to display the prefix label. Default: true */\n showPrefixLabel?: boolean\n /** Additional inline styles for wrapper element. */\n style?: React.CSSProperties\n}\n\nconst STATE_STYLE: Record<PermState, { bg: string; text: string; border: string }> = {\n full: { bg: \"#ecfdf5\", text: \"#047857\", border: \"#a7f3d0\" },\n partial: { bg: \"#fffbeb\", text: \"#b45309\", border: \"#fde68a\" },\n none: { bg: \"#f8fafc\", text: \"#94a3b8\", border: \"#cbd5e1\" },\n}\n\n/**\n * Format human-readable resource tooltip text.\n * リソース表示名およびキーからツールチップ用テキストを生成。\n */\nexport function formatResourceTooltip(resourceKey?: string, resourceName?: string): string | undefined {\n const key = resourceKey?.trim()\n const name = resourceName?.trim()\n if (name && key && name !== key) {\n return `対象リソース: ${name} (${key})`\n }\n if (name || key) {\n return `対象リソース: ${name || key}`\n }\n return undefined\n}\n\n/**\n * Render icon according to permission state.\n * 権限状態に応じた SVG アイコンをレンダリング。\n */\nfunction PermIcon({ state }: { state: PermState }) {\n if (state === \"full\") {\n return (\n <svg style={{ width: \"0.85rem\", height: \"0.85rem\", flexShrink: 0 }} fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={3} aria-hidden=\"true\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M5 13l4 4L19 7\" />\n </svg>\n )\n }\n if (state === \"partial\") {\n return (\n <svg style={{ width: \"0.85rem\", height: \"0.85rem\", flexShrink: 0 }} fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2.5} aria-hidden=\"true\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\" />\n </svg>\n )\n }\n return (\n <svg style={{ width: \"0.85rem\", height: \"0.85rem\", flexShrink: 0 }} fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2.5} aria-hidden=\"true\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636\" />\n </svg>\n )\n}\n\n/**\n * Display permission indicator badges for read, write, and delete capabilities.\n * 閲覧・編集・削除の権限状態バッジ群を描画。\n */\nexport function MyPermissionIndicator<K extends string = string>({ permission, resourceName, expectedResourceKey, showResourceTooltip = true, labels, showScopeSummary = true, className = \"authz-permission-indicator\" }: MyPermissionIndicatorProps<K>) {\n const p = usePermission(permission)\n const l = { read: labels?.read ?? \"閲覧\", write: labels?.write ?? \"編集\", delete: labels?.delete ?? \"削除\" }\n const resKey = permission?.resourceKey\n const resName = resourceName || resKey\n const tooltipText = showResourceTooltip ? formatResourceTooltip(resKey, resourceName) : undefined\n\n const hasMismatch = Boolean(expectedResourceKey && resKey && resKey !== expectedResourceKey)\n\n if (hasMismatch && typeof console !== \"undefined\" && typeof console.error === \"function\") {\n console.error(`[Authz Warning] Resource key mismatch in MyPermissionIndicator! Expected: \"${expectedResourceKey}\", but received permission for: \"${resKey}\".`)\n }\n\n /**\n * Render one permission badge, coloured by its state and carrying the tooltip text.\n * 権限バッジ 1 個を描画する処理 (状態に応じた配色とツールチップ文言を持つ形)。\n *\n * @param key Which permission the badge stands for. バッジが表す権限。\n * @param state Granted / denied / unknown state to colour. 配色の基となる状態。\n * @param label Visible short label. 表示する短いラベル。\n * @returns The badge element. バッジ要素。\n */\n const renderBadge = (key: \"read\" | \"write\" | \"delete\", state: PermState, label: string) => {\n const style = STATE_STYLE[state]\n const badgeTitle = tooltipText ? `${label}: ${state}\\n${tooltipText}` : `${label}: ${state}`\n return (\n <span\n data-perm={key}\n data-state={state}\n title={badgeTitle}\n style={{\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: \"0.25rem\",\n padding: \"0.2rem 0.5rem\",\n borderRadius: \"100px\",\n fontSize: \"0.75rem\",\n fontWeight: 600,\n backgroundColor: style.bg,\n color: style.text,\n border: `1px solid ${style.border}`,\n lineHeight: 1,\n userSelect: \"none\",\n }}>\n <PermIcon state={state} />\n <span>{label}</span>\n </span>\n )\n }\n\n return (\n <span\n className={className}\n title={tooltipText}\n data-resource={resKey ?? \"\"}\n data-resource-name={resName ?? \"\"}\n data-mismatch={hasMismatch ? \"true\" : undefined}\n style={{\n display: \"inline-flex\",\n gap: \"0.35rem\",\n alignItems: \"center\",\n verticalAlign: \"middle\",\n }}>\n {renderBadge(\"read\", p.readState, l.read)}\n {renderBadge(\"write\", p.writeState, l.write)}\n {renderBadge(\"delete\", p.deleteState, l.delete)}\n {showScopeSummary && p.readState === \"partial\" ? (\n <span\n data-perm=\"scope\"\n title={tooltipText}\n style={{\n fontSize: \"0.7rem\",\n color: \"#b45309\",\n backgroundColor: \"#fef3c7\",\n padding: \"0.15rem 0.35rem\",\n borderRadius: \"4px\",\n fontWeight: 600,\n }}>\n 部分制限\n </span>\n ) : null}\n {hasMismatch ? (\n <span\n data-testid=\"resource-mismatch-warning\"\n title={`expected: \"${expectedResourceKey}\", actual: \"${resKey}\"`}\n style={{\n fontSize: \"0.7rem\",\n color: \"#991b1b\",\n backgroundColor: \"#fee2e2\",\n border: \"1px solid #fca5a5\",\n padding: \"0.15rem 0.35rem\",\n borderRadius: \"4px\",\n fontWeight: 700,\n }}>\n ⚠️ リソース不一致 ({resKey} ≠ {expectedResourceKey})\n </span>\n ) : null}\n </span>\n )\n}\n\n/**\n * Reusable \"Your Permissions\" status bar component suitable for any application page.\n * 「あなたの権限」ラベルと権限状態インジケータを束ねた再利用可能なコンポーネント。\n */\nexport function MyPermissionStatus<K extends string = string>({\n permission,\n resourceName,\n expectedResourceKey,\n prefixLabel = \"あなたの権限\",\n showPrefixLabel = true,\n showResourceTooltip = true,\n labels,\n showScopeSummary = true,\n className = \"authz-permission-status\",\n style,\n}: MyPermissionStatusProps<K>) {\n const resKey = permission?.resourceKey\n const resName = resourceName || resKey\n const tooltipText = showResourceTooltip ? formatResourceTooltip(resKey, resourceName) : undefined\n\n return (\n <span\n className={className}\n title={tooltipText}\n data-testid=\"my-permission-status\"\n data-resource={resKey ?? \"\"}\n data-resource-name={resName ?? \"\"}\n style={{\n fontSize: \"0.82rem\",\n color: \"#475569\",\n display: \"inline-flex\",\n alignItems: \"center\",\n gap: \"0.4rem\",\n ...style,\n }}>\n {showPrefixLabel && <span data-testid=\"prefix-label\">{prefixLabel}:</span>}\n <MyPermissionIndicator permission={permission} resourceName={resourceName} expectedResourceKey={expectedResourceKey} showResourceTooltip={showResourceTooltip} labels={labels} showScopeSummary={showScopeSummary} />\n </span>\n )\n}\n","/**\n * Resource definition and registry utilities for preventing page-resource mismatches.\n * ページとリソースの表示・権限食い違いを防止するためのリソースレジストリ定義。\n */\n\nexport type ResourceDefinition = {\n /** Unique resource key used in authorization rules (e.g. \"documents\") */\n key: string\n /** Human-readable display name (e.g. \"文書管理\") */\n name: string\n /** Optional description for administration UI */\n description?: string\n}\n\nexport type ResourceDefinitionInput = {\n /** Optional explicit resource key; defaults to the object property name */\n key?: string\n /** Human-readable display name (e.g. \"文書管理\") */\n name: string\n /** Optional description for administration UI */\n description?: string\n}\n\nexport type ResourceRegistry<T extends Record<string, ResourceDefinition>> = T\n\n/**\n * The resource key an entry resolves to: its explicit `key`, otherwise the property name.\n * `key` を明示していればそれ、無ければプロパティ名。\n *\n * `Extract<…, string>` を挟むのは `key?: string` (省略可) が `string | undefined` になり、素の\n * `extends string` では偽になってプロパティ名を名乗ってしまうため (実行時は `key` を使うので型が嘘になる)。\n * `key` を書いていないエントリでは `Extract` が `never` になり、プロパティ名へ落ちる。\n */\ntype ResolvedResourceKey<PropertyName extends string, Input extends ResourceDefinitionInput> = [Extract<Input[\"key\"], string>] extends [never] ? PropertyName : Extract<Input[\"key\"], string>\n\n/**\n * Define type-safe resource registry for an application.\n * アプリ全体のリソース定義(キー・表示名・説明)を一元管理するヘルパー。\n *\n * 各エントリの `key` はリテラル型のまま保持する。`Record<string, …>` へ広げてしまうと\n * {@link resourceKeysOf} が `string[]` に落ち、権限マップのキー型検査が効かなくなるため。\n *\n * @returns The registry with literal-typed resource keys. リソースキーをリテラル型で保持したレジストリ。\n */\nexport function defineResources<const T extends Record<string, ResourceDefinitionInput>>(resources: T): { [K in keyof T & string]: { key: ResolvedResourceKey<K, T[K]>; name: string; description?: string } } {\n const result = {} as { [K in keyof T & string]: { key: ResolvedResourceKey<K, T[K]>; name: string; description?: string } }\n for (const k of Object.keys(resources) as Array<keyof T & string>) {\n const item = resources[k]\n result[k] = {\n key: (item.key ?? k) as ResolvedResourceKey<typeof k, T[typeof k]>,\n name: item.name,\n description: item.description,\n }\n }\n return result\n}\n\n/**\n * Extract a registry's resource keys with their literal types preserved.\n * レジストリのリソースキーをリテラル型を保ったまま取り出す処理。\n *\n * `Object.keys(registry)` は `string[]` になり、`getMyPermissions` の戻り値も索引型へ落ちてキーの誤りを\n * 検出できなくなる。加えて `Object.keys` は **プロパティ名** を返すため、`key` を明示したエントリでは\n * 実際のリソースキーと食い違う。\n *\n * ⚠️ 1 エントリでもリテラルでない `key` (例: 変数由来の `string`) を持つと、合併の性質上レジストリ全体の\n * キー型が `string` へ広がり、他のエントリ分のコンパイル時検査も無効になる。`key` は必ずリテラルで書くこと。\n *\n * @returns The registry's resource keys as a literal union array. リテラル合併型のリソースキー配列。\n */\nexport function resourceKeysOf<T extends Record<string, { key: string }>>(registry: T): Array<T[keyof T & string][\"key\"]> {\n return Object.values(registry).map((r) => r.key) as Array<T[keyof T & string][\"key\"]>\n}\n","/**\n * React Router (v7) adapter: binds `appKey` + the DI ports and exposes the loader/action authorization API.\n * React Router (v7) 向けアダプタ。`appKey` と DI ポートを束ね、loader/action 用の認可 API を公開するモジュール。\n *\n * 設計上の中心的な不変条件は **判定は 1 回だけ** ということ。ガード (`requirePermission`) と\n * UI 表示用の権限ビューは同一の実効権限から導出されるため、「画面は開けるのに一覧では false」\n * という乖離が構造的に起こり得ない。\n */\nimport { AUTHZ_ACTIONS, type AuthzAction, assertTenantId, can, createRequirePermission, type DenyInfo, type EffectivePermission, isSameTenant, type MergedScope } from \"@aiquants/authz-core\"\nimport { redirect } from \"react-router\"\n\ntype Awaitable<T> = T | Promise<T>\nexport type AuthzUserId = string | number\n\nconst PKG = \"@aiquants/authz-react-router\"\n\n/**\n * Thrown when the API itself is called incorrectly (bad arguments, missing port) — a programmer error,\n * never an authorization decision. It is deliberately NOT an `AuthzDeniedError`: a misuse must surface as a\n * loud 500, not as a silent 403 that looks like a permission problem.\n * API の使い方が誤っている場合に送出される例外。認可拒否 (403) とは明確に区別されるプログラマ向けエラー。\n */\nexport class AuthzUsageError extends Error {\n constructor(message: string) {\n super(`[${PKG}] ${message}`)\n this.name = \"AuthzUsageError\"\n }\n}\n\n/**\n * Arguments handed to the {@link AuthzConfig.getEffectivePermissions} port.\n * 実効権限ポートへ渡す引数。\n *\n * `tenantId` is a **required, non-empty string**. It is never absent and never `null`: a port that\n * receives no tenant would have to guess, and the only guess an SQL adapter can make — omit the\n * predicate — returns every tenant's grants. That inversion is the failure this contract removes.\n * `tenantId` は必須かつ非空 (欠落を「全テナント」に読み替える経路を型ごと塞ぐ形)。\n */\nexport type EffectivePermissionsQuery = {\n userId: AuthzUserId | null\n tenantId: string\n groupIds?: (string | number)[]\n appKey: string\n /** Always a de-duplicated, non-empty list — the port may load them all in one round-trip. Read-only: the adapter reports on exactly these keys. */\n resourceKeys: readonly string[]\n}\n\nexport type AuthzConfig = {\n /** Stable app identifier (e.g. \"quants\"). Bound into every permission query. */\n appKey: string\n /** Resolve the acting user id from the request (cookie/session/openid → the consumer's user id). */\n resolveUserId: (request: Request) => Awaitable<AuthzUserId | null | undefined>\n /**\n * Resolve the acting tenant id from the request (header / cookie / subdomain → tenant id).\n * **Required**: every permission question is asked within exactly one tenant, so a host that\n * cannot name it must not be able to ask at all. Returning a blank string raises\n * `AuthzTenantError` — it is never read as \"no filter\" / \"every tenant\".\n * リクエストからテナント ID を解決するポート (必須)。空文字は「全テナント」ではなく例外。\n */\n resolveTenantId: (request: Request) => Awaitable<string>\n /**\n * Prove the acting identity may act **as** the resolved tenant. **Required**, with no permissive\n * default: `resolveTenantId` only says *which* tenant was named, and that name comes from a\n * client-influenced channel (header / sub-domain / cookie). Without this seam a user of tenant A\n * is authorized inside tenant B by naming tenant B — B's `@authenticated` grants apply and every\n * downstream tenant check still passes, because the permission genuinely belongs to B.\n * リクエストが名乗ったテナントへの帰属証明 (必須・既定値なし)。テナント名は利用者が影響できる経路から\n * 来るため、これが無いと「別テナントを名乗るだけで越境認可」になる。\n *\n * Called once per check, **before** any permission is loaded. `userId` is `null` for an anonymous\n * caller; the host decides whether that tenant's `@anonymous` grants may be used (they are public\n * by construction, so `true` is the usual answer).\n * 権限読込より前に 1 回だけ呼ばれる。匿名呼び出しの扱いはホストが決める。\n */\n assertTenantMembership: (request: Request, tenantId: string, userId: AuthzUserId | null) => Awaitable<boolean>\n /**\n * Optionally resolve acting user's group IDs, for the tenant the request was scoped to.\n * リクエストのスコープとなったテナントに対して、実行ユーザーのグループ ID を解決する任意ポート。\n *\n * `tenantId` is the value `resolveTenantId` already produced and validated, so a host never has to\n * derive the tenant a second time — a second derivation is a second thing that can disagree.\n * `tenantId` は解決・検証済みの値 (ホスト側での二重解決を不要にし、乖離の余地を作らないため)。\n */\n resolveGroupIds?: (request: Request, tenantId: string) => Awaitable<(string | number)[] | null | undefined>\n /**\n * Load effective permissions for (user, app, resources) — **one call, many resources** (typically\n * `getEffectivePermissionsMany` from `@aiquants/authz-drizzle`). A key with no grants may be omitted or\n * mapped to `null`; both mean \"no permission\" (fail-close).\n */\n getEffectivePermissions: (args: EffectivePermissionsQuery) => Awaitable<Record<string, EffectivePermission | null | undefined> | null | undefined>\n /**\n * Optional: every resource key registered for this app **within `tenantId`** (typically\n * `listResourceKeys` from `@aiquants/authz-drizzle`, whose 4th argument is that same tenant).\n * Configuring it is what makes `getMyPermissions(request)` — with no resource list — legal;\n * without it that call throws {@link AuthzUsageError} instead of guessing.\n * 指定テナントに登録済みの全リソースキーを返す任意ポート。\n *\n * The tenant is handed in already resolved and validated: making the host re-derive it from the\n * raw `Request` would create a second, divergable tenant source, and a host that derived it\n * differently (or could not) would report on another tenant's resource keys.\n * テナントは解決・検証済みの値として渡す (ホスト側での再導出は第 2 のテナント源となり乖離するため)。\n *\n * Called only after `assertTenantMembership` has proven the caller may act as `tenantId`: the set of\n * registered keys is itself that tenant's information, so a non-member must not be able to provoke\n * this query at all.\n * 帰属証明を通った後にのみ呼ばれる (登録済みキーの集合自体がテナントの情報であるため)。\n */\n listResourceKeys?: (request: Request, tenantId: string) => Awaitable<readonly string[] | null | undefined>\n /** Default deny handler. If omitted, denies throw AuthzDeniedError (403). */\n onDeny?: (info: DenyInfo) => void\n}\n\nexport type RequirePermissionOptions = {\n /** When set, denials `throw redirect(failureRedirect)` instead of throwing 403. */\n failureRedirect?: string\n}\n\n/**\n * Serializable per-resource permission summary for UI (loader → component).\n * UI 向けの直列化可能なリソース単位権限サマリ (loader からコンポーネントへ受け渡す形)。\n *\n * 真偽値は `can` 接頭辞で統一されている。`delete` のような素の動詞名は分割代入できず\n * (`const { delete } = view` は構文エラー)、フックの戻り値との二重語彙も生むため採用しない。\n */\nexport type PermissionView<K extends string = string> = {\n resourceKey: K\n canRead: boolean\n canCreate: boolean\n canUpdate: boolean\n canDelete: boolean\n /** create ∪ update */\n canWrite: boolean\n /** read granted but row and/or column restricted (→ △ partial). */\n readPartial: boolean\n actions: AuthzAction[]\n}\n\n/** Result of a successful {@link createAuthz} guard. ガード成功時の戻り値。 */\nexport type RequirePermissionResult<K extends string = string> = {\n userId: AuthzUserId | null\n /** The tenant the decision was made within (always present, never blank). 判定を行ったテナント。 */\n tenantId: string\n /** Effective row/column scope for the guarded action — apply as WHERE / column mask. */\n scope: MergedScope\n /** UI view derived from the **same** evaluation as the guard decision (no second lookup). */\n permission: PermissionView<K>\n}\n\n/**\n * Permission views keyed by the exact resource keys that were requested.\n * 要求したキーで型付けした権限ビュー群。\n *\n * リテラルのキーを渡した場合だけ「そのキーは必ず存在する」形になる。`string[]` のようにキーが実行時に\n * しか分からない場合は `| undefined` を含む索引型へ落ち、存在チェックを強制する (総和型の索引を\n * 名乗ると、要求していないキーが型の上では必ず存在することになり、元の不具合と同じ罠に戻る)。\n *\n * ⚠️ キーは配列の **要素型** から導かれる。`flag ? [\"a\"] : [\"b\"]` のように要素型が合併になる式では、\n * 実行時に片方しか無くても型の上では両方が存在する扱いになる。リテラル配列を直接渡すか\n * `resourceKeysOf(registry)` を使うこと。\n * ⚠️ 戻り値を `Record<string, …>` 型の変数へ代入すると、その文脈から `K` が `string` へ広がり\n * 索引型 (値は `| undefined`) に落ちる。リテラルキーの保証が要る箇所では代入先を広げないこと。\n * ⚠️ 戻り値は素のオブジェクト。要求していない `toString` などのキーを読むと `Object.prototype` の\n * メンバが返る (索引型の `| undefined` とは一致しない)。要求したキーだけを読むこと。\n */\nexport type PermissionViewMap<K extends string> = string extends K ? Record<string, PermissionView | undefined> : { [P in K]: PermissionView<P> }\n\n/**\n * Map an EffectivePermission to a serializable {@link PermissionView}.\n * 実効権限を直列化可能な {@link PermissionView} へ変換する処理。\n *\n * @returns The UI-facing permission summary (all-false when `perm` is absent). UI 表示用の権限サマリ。\n */\nexport function toPermissionView<K extends string>(resourceKey: K, perm: EffectivePermission | null | undefined): PermissionView<K> {\n const canRead = can(perm, \"read\")\n const canCreate = can(perm, \"create\")\n const canUpdate = can(perm, \"update\")\n const canDelete = can(perm, \"delete\")\n const readScope = perm?.scopeByAction.read\n // read が許可されていても行/列のいずれかが絞られていれば「部分」扱い (△)。\n // スコープ自体が欠けている場合、ガードは DENY_ALL_SCOPE (0 行 0 列) に倒すため、表示も「制限あり」に\n // 揃える。ここで「制限なし」に倒すと、1 行も見えないのにバッジだけ全閲覧可を示す乖離になる。\n const readPartial = canRead && (!readScope || readScope.rowScope !== null || readScope.columnScope !== null)\n return {\n resourceKey,\n canRead,\n canCreate,\n canUpdate,\n canDelete,\n canWrite: canCreate || canUpdate,\n readPartial,\n actions: perm ? Array.from(perm.actions) : [],\n }\n}\n\n/**\n * Describe a runtime value for an error message without dumping the whole object.\n * エラーメッセージ用に値の型と概要を短く表現する処理。\n *\n * @returns A short human-readable description of the value. 値を要約した短い説明文字列。\n */\nfunction describe(value: unknown): string {\n if (value === null) return \"null\"\n if (typeof value === \"string\") return `a string (${JSON.stringify(value)})`\n if (Array.isArray(value)) return `an array`\n return `${typeof value} (${String(value)})`\n}\n\n/**\n * Validate a caller-supplied resource-key list, failing loudly on every shape that would otherwise\n * degrade silently — most importantly a bare string, which `for...of` would iterate character by character.\n * 呼び出し側が渡したリソースキー配列を検証する処理。無言で劣化する形 (特に素の文字列) を全て明示的に弾く。\n *\n * @returns The validated keys, de-duplicated and order-preserving. 重複を除いた検証済みのキー配列。\n */\nfunction assertResourceKeys(value: unknown, label: string): string[] {\n if (!Array.isArray(value)) {\n const hint = typeof value === \"string\" ? ` A bare string would be iterated character by character; pass \\`{ resourceKeys: [${JSON.stringify(value)}] }\\` instead.` : \"\"\n throw new AuthzUsageError(`${label} must be an array of resource keys, but received ${describe(value)}.${hint}`)\n }\n const seen = new Set<string>()\n const keys: string[] = []\n value.forEach((key, index) => {\n if (typeof key !== \"string\" || key.trim() === \"\") {\n throw new AuthzUsageError(`${label}[${index}] must be a non-empty resource key string, but received ${describe(key)}.`)\n }\n if (seen.has(key)) return\n seen.add(key)\n keys.push(key)\n })\n return keys\n}\n\n/**\n * Reject a port result that is a single {@link EffectivePermission} instead of a per-resource record.\n * 実効権限ポートが「1 件の実効権限」を返した場合を明示的に弾く処理。\n *\n * このポートは 1 回の呼び出しで **複数リソース分** を返す契約。旧契約 (1 リソース 1 件) のまま\n * 移行し忘れると、キー参照が全て `undefined` になり **全リソース拒否が沈黙して**成立してしまう。\n * 形で見分けられるうちに落とす。\n */\nfunction assertPermissionRecord(loaded: unknown): void {\n if (loaded === null || loaded === undefined) return\n const shape = loaded as { actions?: unknown; scopeByAction?: unknown }\n if (shape.actions instanceof Set && typeof shape.scopeByAction === \"object\") {\n throw new AuthzUsageError(\n \"createAuthz({ getEffectivePermissions }) must resolve to a record keyed by resource key (e.g. getEffectivePermissionsMany from @aiquants/authz-drizzle), but it resolved to a single EffectivePermission. Keeping the single-resource port would deny every resource silently.\",\n )\n }\n}\n\n/**\n * Validate the guard query so a typo surfaces as an explicit error instead of an unexplained 403.\n * ガードの問い合わせ内容を検証する処理。誤記が「理由の分からない 403」ではなく明示的な例外になる形。\n *\n * @returns The validated resource key and action. 検証済みのリソースキーとアクション。\n */\nfunction assertPermissionQuery<K extends string>(query: unknown): { resourceKey: K; action: AuthzAction } {\n if (typeof query !== \"object\" || query === null) {\n throw new AuthzUsageError(`requirePermission(request, query) requires a query object like { resourceKey: \"documents\", action: \"read\" }, but received ${describe(query)}.`)\n }\n const { resourceKey, action } = query as { resourceKey?: unknown; action?: unknown }\n if (typeof resourceKey !== \"string\" || resourceKey.trim() === \"\") {\n throw new AuthzUsageError(`query.resourceKey must be a non-empty string, but received ${describe(resourceKey)}.`)\n }\n if (typeof action !== \"string\" || !(AUTHZ_ACTIONS as readonly string[]).includes(action)) {\n const extra = action === \"write\" ? ` \"write\" is a derived concept (create ∪ update) and is never guarded directly — guard \"create\" or \"update\".` : \"\"\n throw new AuthzUsageError(`query.action must be one of ${AUTHZ_ACTIONS.join(\" | \")}, but received ${describe(action)}.${extra}`)\n }\n return { resourceKey: resourceKey as K, action: action as AuthzAction }\n}\n\n/**\n * Keep an effective permission only when it was issued for the requested tenant (fail-close).\n * 要求テナント向けに発行された実効権限だけを残し、それ以外は「権限なし」へ倒す処理 (fail-close)。\n *\n * The guard reaches the same verdict through `@aiquants/authz-core` (deny `tenant-mismatch`).\n * Letting the UI side through instead would recreate the exact divergence this adapter exists to\n * prevent — a badge that says \"allowed\" over an action that answers 403.\n * ガード側は core が同じ判定 (deny `tenant-mismatch`) を下すため、表示側だけ通すと「バッジは可・操作は 403」の乖離になる。\n *\n * @param perm The permission the port returned. ポートが返した実効権限。\n * @param tenantId The tenant the request was scoped to. 要求のスコープとなったテナント。\n * @returns The permission when it belongs to the tenant, otherwise `null`. 帰属時のみ実効権限、他は `null`。\n */\nfunction permissionForTenant(perm: EffectivePermission | null, tenantId: string): EffectivePermission | null {\n return perm !== null && isSameTenant(tenantId, perm.tenantId) ? perm : null\n}\n\n/**\n * The resources one `getMyPermissions` call reports on, decided before any port is consulted.\n * 1 回の `getMyPermissions` 呼び出しが対象とするリソースの決定結果 (ポート呼び出し前に確定させる形)。\n *\n * 明示されたキーと「登録済みの全リソース」を型で区別するのは、帰属証明に失敗したときの扱いが\n * 異なるため。前者は呼び出し側が既に知っている名前だが、後者の鍵集合そのものがそのテナントの\n * 登録情報であり、帰属していない呼び出し側へ返してはならない。\n */\ntype ResourceKeyPlan = { kind: \"explicit\"; keys: string[] } | { kind: \"all\"; listResourceKeys: NonNullable<AuthzConfig[\"listResourceKeys\"]> }\n\n/**\n * React Router adapter. Binds `appKey` and the DI deps, exposing:\n * - `requirePermission(request, {resourceKey, action}, options?)` → `{ userId, tenantId, scope, permission }` on allow\n * (apply row WHERE / column mask with `scope`; render `permission` in the UI); denies throw 403 or redirect.\n * Every check is scoped to the tenant `resolveTenantId` returns, that tenant must be one the caller is proven\n * to belong to (`assertTenantMembership`, else deny `not-a-tenant-member`), and a permission issued for\n * another tenant is denied (`tenant-mismatch`) rather than trusted.\n * - `getMyPermissions(request, {resourceKeys})` → `Record<resourceKey, PermissionView>` for the UI,\n * or `getMyPermissions(request)` for every registered resource when `listResourceKeys` is configured.\n * Membership is proven **before** that resource list is read, so naming a foreign tenant never reveals\n * which resources it has registered.\n * `appKey` と DI ポートを束ね、ガードと自己権限取得の 2 面だけを公開するファクトリ。\n *\n * @returns The bound `requirePermission` / `getMyPermissions` pair. 結線済みの認可 API 一式。\n */\nexport function createAuthz(config: AuthzConfig) {\n // ポート未設定は型では防げない (JS ホスト / 部分的な移行)。ここで落とさないと\n // 呼び出し時に \"not a function\" という無関係な TypeError になり、原因が見えなくなる。\n if (typeof config.resolveTenantId !== \"function\") {\n throw new AuthzUsageError(`createAuthz({ resolveTenantId }) is required: every permission check is scoped to exactly one tenant, but received ${describe(config.resolveTenantId)}.`)\n }\n if (typeof config.assertTenantMembership !== \"function\") {\n throw new AuthzUsageError(\n `createAuthz({ assertTenantMembership }) is required: resolving a tenant only says which tenant the request named — it never proves the caller may act as it, so without this port naming another tenant is enough to be authorized inside it. Received ${describe(config.assertTenantMembership)}.`,\n )\n }\n\n /**\n * Resolve the acting user's group ids once per call, normalizing \"no groups port\" to `undefined`.\n * 1 回の呼び出しにつき 1 度だけグループ ID を解決する処理 (ポート未設定は `undefined` へ正規化)。\n *\n * @param request The incoming request. 受信したリクエスト。\n * @param tenantId The already-validated tenant of this check. 検証済みの対象テナント。\n * @returns The group ids, or `undefined` when no port is configured. グループ ID 配列、または未設定を表す `undefined`。\n */\n async function resolveGroupIds(request: Request, tenantId: string): Promise<(string | number)[] | undefined> {\n return config.resolveGroupIds ? ((await config.resolveGroupIds(request, tenantId)) ?? undefined) : undefined\n }\n\n /**\n * Resolve and validate the acting tenant id once per call — the single place this adapter reads it.\n * 1 回の呼び出しにつき 1 度だけテナント ID を解決・検証する処理 (このアダプタ唯一の読み取り点)。\n *\n * @param request The incoming request. 受信したリクエスト。\n * @returns The validated, non-empty tenant id. 検証済みで非空のテナント ID。\n * @throws {AuthzTenantError} When the port yields a blank id. ポートが空を返した場合。\n */\n async function resolveTenantId(request: Request): Promise<string> {\n return assertTenantId(await config.resolveTenantId(request), `${PKG} createAuthz(resolveTenantId)`)\n }\n\n /**\n * Load effective permissions for a set of resources through the single batch port.\n * 一括ポートを通じて複数リソースの実効権限をまとめて読み込む処理。\n *\n * @param tenantId The validated tenant every returned permission is scoped to. 検証済みの対象テナント。\n * @returns A permission (or `null`) for every requested key. 要求した全キー分の実効権限 (無い場合は `null`)。\n */\n async function loadPermissions(request: Request, resourceKeys: string[], userId: AuthzUserId | null, tenantId: string): Promise<Record<string, EffectivePermission | null>> {\n if (resourceKeys.length === 0) return {}\n\n const groupIds = await resolveGroupIds(request, tenantId)\n const loaded = await config.getEffectivePermissions({ userId, tenantId, groupIds, appKey: config.appKey, resourceKeys })\n assertPermissionRecord(loaded)\n // ポートがキーを落としても undefined を漏らさず null (= 権限なし) に倒す。\n // 自前プロパティのみを見るのは、`constructor` 等の継承プロパティを実効権限と取り違えないため。\n // 組み立てに fromEntries を使うのは、`__proto__` というキーが素の代入では黙って落ちるため。\n return Object.fromEntries(resourceKeys.map((key) => [key, (loaded && Object.hasOwn(loaded, key) ? loaded[key] : null) ?? null]))\n }\n\n /**\n * Guard a loader/action: deny (403 or redirect) unless the acting user holds `action` on `resourceKey`.\n * loader / action 用のガード。指定アクションを保持しない限り拒否 (403 またはリダイレクト) する処理。\n *\n * @returns The user id, the effective scope, and the matching UI view. ユーザー ID・実効スコープ・UI 表示用ビュー。\n */\n async function requirePermission<K extends string>(request: Request, query: { resourceKey: K; action: AuthzAction }, options?: RequirePermissionOptions): Promise<RequirePermissionResult<K>> {\n const { resourceKey, action } = assertPermissionQuery<K>(query)\n\n const onDeny: ((info: DenyInfo) => void) | undefined = options?.failureRedirect\n ? () => {\n throw redirect(options.failureRedirect as string)\n }\n : config.onDeny\n\n const guard = createRequirePermission<Request, AuthzUserId>({\n resolveUserId: config.resolveUserId,\n resolveTenantId,\n // 帰属証明もリクエスト単位のポートへそのまま委譲する (core が解決済みテナントを渡してくる)\n assertTenantMembership: ({ ctx, tenantId, userId }) => config.assertTenantMembership(ctx, tenantId, userId),\n // core が解決済みテナントを渡してくる。ここで別の値を引き直すと 1 リクエスト内で 2 つの\n // テナントが並立し、「判定したテナント」と「読んだテナント」がずれる。\n getEffectivePermissions: async (args) => (await loadPermissions(request, [args.resourceKey], args.userId, args.tenantId))[args.resourceKey],\n onDeny,\n })\n const result = await guard(request, { appKey: config.appKey, resourceKey, action })\n // ガードが判定に用いた実効権限をそのままビュー化する (再取得しないので乖離し得ない)\n return { userId: result.userId, tenantId: result.tenantId, scope: result.scope, permission: toPermissionView(resourceKey, result.effectivePermission) }\n }\n\n /**\n * Decide which resources one `getMyPermissions` call reports on, rejecting misuse before any port runs.\n * 1 回の `getMyPermissions` 呼び出しが対象とするリソースを決定する処理 (ポート実行前に誤用を弾く形)。\n *\n * @param options The caller-supplied options, if any. 呼び出し側が渡したオプション。\n * @returns The validated plan for this call. この呼び出しの検証済みプラン。\n * @throws {AuthzUsageError} When the keys are malformed, or omitted with no `listResourceKeys` port configured. キーが不正な場合、またはポート未設定でキーを省略した場合。\n */\n function planResourceKeys(options?: { resourceKeys?: readonly string[] }): ResourceKeyPlan {\n if (options?.resourceKeys !== undefined) {\n return { kind: \"explicit\", keys: assertResourceKeys(options.resourceKeys, \"getMyPermissions options.resourceKeys\") }\n }\n if (!config.listResourceKeys) {\n throw new AuthzUsageError(\n 'getMyPermissions(request) needs to know which resources exist. Either pass them explicitly — getMyPermissions(request, { resourceKeys: [\"documents\"] }) — or configure createAuthz({ listResourceKeys }) (e.g. listResourceKeys from @aiquants/authz-drizzle) to report on every registered resource.',\n )\n }\n return { kind: \"all\", listResourceKeys: config.listResourceKeys }\n }\n\n function getMyPermissions<K extends string>(request: Request, options: { resourceKeys: readonly K[] }): Promise<PermissionViewMap<K>>\n // キーを指定しない形は実行時に決まるキーを返すため、値は `| undefined` を含む\n // (`Record<string, PermissionView>` にすると、要求していないキーが型の上では必ず存在することになる)。\n function getMyPermissions(request: Request, options?: { resourceKeys?: readonly string[] }): Promise<Record<string, PermissionView | undefined>>\n /**\n * Report the acting user's permissions for the UI — for the named resources, or for every registered\n * resource when `resourceKeys` is omitted (requires the `listResourceKeys` port).\n * UI 表示用に操作ユーザーの権限を返す処理。キー省略時は登録済みの全リソースが対象 (要 `listResourceKeys`)。\n *\n * Views are reported for the tenant `resolveTenantId` returns, and only when\n * `assertTenantMembership` proves the caller may act as it; a permission stamped with any other\n * tenant is reported as \"no permission\", matching what the guard would decide.\n * 対象は `resolveTenantId` が返したテナント (帰属証明を通った場合のみ)。別テナントの実効権限は\n * ガードと同じく「権限なし」として報告する。\n *\n * A caller that is not a member of the tenant it named learns nothing about that tenant: the named\n * keys come back all-false, and an omitted key list yields `{}` rather than that tenant's registered\n * resource inventory — the key set is itself tenant-owned information.\n * 帰属していないテナントについては何も返さない (明示キーは全 false、キー省略時は空マップ)。\n *\n * @returns A view per requested resource key; requested keys are never missing. リソースキーごとの権限ビュー。\n */\n async function getMyPermissions(request: Request, options?: { resourceKeys?: readonly string[] }): Promise<Record<string, PermissionView>> {\n // 対象リソースの決定は I/O を伴わない検証なので先に済ませる。誤用 (キー不正 / ポート未設定) の\n // 発覚が帰属の可否に左右されると、非会員のときだけ静かに空が返る形になる。\n const plan = planResourceKeys(options)\n const userId = (await config.resolveUserId(request)) ?? null\n const tenantId = await resolveTenantId(request)\n // 帰属証明はリソースキーの解決より **前** に置く。`listResourceKeys` はそのテナントに登録済みの\n // リソース一覧を実際に問い合わせるため、後ろに置くと「名乗っただけの他テナント」へクエリが走り、\n // 返るマップの鍵集合がそのテナントの登録リソース目録そのものになる (値が全 false でも漏洩する)。\n if (!(await config.assertTenantMembership(request, tenantId, userId))) {\n // 明示されたキーは呼び出し側が既に知っている名前なので全 false のビューで返す。\n // キー省略時は鍵集合自体が他テナントの情報なので、空のマップへ倒す。\n return plan.kind === \"explicit\" ? Object.fromEntries(plan.keys.map((key) => [key, toPermissionView(key, null)])) : {}\n }\n const resourceKeys = plan.kind === \"explicit\" ? plan.keys : assertResourceKeys((await plan.listResourceKeys(request, tenantId)) ?? [], \"the listResourceKeys port result\")\n const permissions = await loadPermissions(request, resourceKeys, userId, tenantId)\n\n // `__proto__` を含むどんなキーでも必ず自前プロパティとして現れるようにする (素の代入では落ちる)。\n // 別テナントの実効権限はガードと同じく「権限なし」へ倒してから描画する。\n return Object.fromEntries(resourceKeys.map((key) => [key, toPermissionView(key, permissionForTenant(permissions[key], tenantId))]))\n }\n\n return { requirePermission, getMyPermissions }\n}\n"],"mappings":"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","names":["index_exports","__export","AuthzAdminAppView","AuthzErrorBoundary","AuthzGrantsView","AuthzGraphView","AuthzGroupRolesView","AuthzLayout","AuthzResourcesView","AuthzRolesView","AuthzUsageError","AuthzUserRolesView","ErrorAlert","Field","MyPermissionGroupStatus","MyPermissionIndicator","MyPermissionStatus","createAuthz","createAuthzAdminApp","createAuthzAdminServer","defaultAuthzAdminLabels","defineResources","flattenGroupItems","formatResourceTooltip","getPermissionSummary","makeAuthzErrorBoundary","resolveLabels","resourceKeysOf","toPermissionView","ui","usePermission","__toCommonJS","import_react_router","import_authz_core","import_react_router","defaultAuthzAdminLabels","resolveLabels","over","d","isReservedRoleKey","roleKey","isResponse","e","toActionError","createAuthzAdminServer","opts","store","RES","labels","resolveLabels","denyCrossTenant","ctx","action","pickOwned","rows","id","row","r","pickOwnedTenanted","assertPayloadTenant","payloadTenantId","ownedRole","roleId","ownedResource","resourceId","ownedGroup","groupId","assertOwnedUser","userId","assertOwnedGrant","grantId","requirePermission","request","query","tenantId","permission","layoutGuard","rolesData","roles","self","resourcesData","grantsData","grants","resources","userRolesData","assignments","users","groupRolesData","groupRoles","groups","graphData","myPermissions","form","intent","name","role","appKey","resourceKey","resource","rawAction","group","segmentOf","params","createAuthzAdminApp","opts","server","createAuthzAdminServer","basePath","SEGMENTS","segmentData","actions","loader","args","segment","userId","tenantId","myPermissions","segData","action","act","import_jsx_runtime","ui","Field","label","hint","children","ErrorAlert","message","import_authz_core","import_select_box","import_react","import_react_router","import_react","usePermission","view","readState","writeState","deleteState","import_jsx_runtime","STATE_STYLE","formatResourceTooltip","resourceKey","resourceName","key","name","PermIcon","state","MyPermissionIndicator","permission","expectedResourceKey","showResourceTooltip","labels","showScopeSummary","className","p","usePermission","l","resKey","resName","tooltipText","hasMismatch","renderBadge","label","style","badgeTitle","MyPermissionStatus","prefixLabel","showPrefixLabel","import_jsx_runtime","flattenGroupItems","items","currentLevel","result","item","itemLevel","SIZE_CONFIGS","getPermissionSummary","permission","customLabels","txtNoPerm","txtFullWrite","txtPartialRead","txtReadOnly","GroupItemPill","size","labels","summary","resKey","resName","hasMismatch","cfg","tooltipText","MyPermissionGroupStatus","prefixLabel","variant","className","style","isOpen","setIsOpen","popoverRef","handleClickOutside","event","handleKeyDown","allItems","writeCount","i","readCount","totalCount","summaryLabel","level","isChild","MyPermissionIndicator","import_jsx_runtime","actionError","d","TenantCell","tenantId","testId","ui","tenantOfRole","roles","roleId","labels","role","r","AuthzLayoutShell","children","renderHeader","myPermissions","adminResourceKey","basePath","base","variant","setVariant","tabs","permissionItems","key","perm","items","annotation","MyPermissionGroupStatus","label","isActive","AuthzLayout","makeAuthzErrorBoundary","over","L","resolveLabels","error","routeError","is403","title","message","AuthzErrorBoundary","confirmGuard","active","e","AuthzRolesView","selfAdminRoleIds","C","selfAdmin","ErrorAlert","Field","AuthzResourcesView","resources","GrantRow","g","action","setAction","a","val","AuthzGrantsView","grants","roleOptions","resourceOptions","actionOptions","selectedRole","setSelectedRole","selectedResource","setSelectedResource","selectedAction","setSelectedAction","AuthzUserRolesView","assignments","users","actorUserId","userOptions","u","selectedUser","setSelectedUser","AuthzGroupRolesView","groupRoles","groups","actionData","err","activeRoles","groupOptions","selectedGroup","setSelectedGroup","ACTION_COLORS","AuthzTopologyMap","selectedNode","onNodeClick","hoveredNode","setHoveredNode","connectedUserIds","connectedGroupIds","connectedRoleIds","connectedResourceIds","activeUsers","activeGroups","activeResources","res","getResourceActions","resId","resGrants","uniqueActions","getResourceCardHeight","resourceCardGap","cumulativeResY","resourceYMap","actions","height","top","actionYMap","act","actIdx","actY","totalResourceHeight","stdCardHeight","stdCardGap","stdItemHeight","totalRoleHeight","totalGroupHeight","totalUserHeight","svgHeight","resourceStartTop","roleStartTop","groupStartTop","userStartTop","getResourceActionCenterY","info","relativeActY","getRoleCenterY","roleIndex","getGroupCenterY","groupIndex","getUserCenterY","userIndex","resourceX","roleX","groupX","userX","activeNode","isUserActive","uId","roleIds","isGroupActive","gId","gr","isRoleActive","rId","isResourceActive","groupRoleIds","userRoleIds","isUserRoleLinkActive","isGroupRoleLinkActive","isRoleResourceLinkActive","userRoleLinks","uIdx","rIdx","roleAssignments","item","assignIdx","totalAssigns","offset","groupRoleLinks","gIdx","gRoleAssigns","roleResourceLinks","resIdx","sameResourceActionGrants","actionRoleIdx","totalSameGrants","colorConf","color","link","grantItem","y1","y2","cX1","cX2","isSelected","cardInfo","cardTop","i","y","AuthzGraphView","setSelectedNode","searchQuery","setSearchQuery","viewMode","setViewMode","roleMap","resourceMap","groupMap","userMap","isUserRelated","userId","roleIdsForResource","isGroupRelated","groupId","isRoleRelated","isResourceRelated","resourceId","query","filteredUsers","filteredGroups","filteredRoles","filteredResources","selectedUserObj","selectedGroupObj","selectedRoleObj","selectedResourceObj","handleNodeClick","type","id","resourceGrants","actStyle","roleGrants","roleGroupAssignments","assignedRoles","userAssignments","SEGMENT_VIEWS","AuthzAdminAppView","segment","View","defineResources","resources","result","k","item","resourceKeysOf","registry","import_authz_core","import_react_router","PKG","AuthzUsageError","message","toPermissionView","resourceKey","perm","canRead","canCreate","canUpdate","canDelete","readScope","readPartial","describe","value","assertResourceKeys","label","hint","seen","keys","key","index","assertPermissionRecord","loaded","shape","assertPermissionQuery","query","action","extra","permissionForTenant","tenantId","createAuthz","config","resolveGroupIds","request","resolveTenantId","loadPermissions","resourceKeys","userId","groupIds","requirePermission","options","onDeny","result","ctx","args","planResourceKeys","getMyPermissions","plan","permissions"]}