DEV fail-open 路由基線白名單(CM-1760,2026-09-13 盤點)

查詢(DEV,localhost:5432/guidant_ai_dev,唯讀):

SELECT u.id, u.name, u.url, u.enable
FROM ui_routes u
WHERE u.pid IS NOT NULL AND u.pid <> 0
  AND NOT EXISTS (SELECT 1 FROM route_capabilities rc WHERE rc.route_id = u.id)
ORDER BY u.name;

(pid IS NULL 或 pid = 0 的節點是選單分組節點/頂層節點,本來就無 url、不是可綁 權限的葉節點,已排除。)

§1

結果:恰好 3 筆

id name url enable 為什麼可以 fail-open
49 project-dashboard /project/dashboard 1 scripts/sql/2026-05-26-menu-group-restructure.sql Step 2 明寫「不掛 capability(所有 user 可見)」——搬遷自 AppMenu.vue 原本寫死的入口,本來就無角色限制。
50 my-tasks /project/task-manage 1 同上(同一支 migration 同一段),待辦任務對任何登入者都可見。
54 license-status /license/status 1 scripts/sql/2026-08-10-fr062-8-license-menu-restructure.sql 明寫「/license/status(route 54)刻意不綁任何 capability:全租戶可見的自己狀態頁,維持現況 fail-open 可見」——與同群組的 license-manage(root 專屬管理頁,已綁 license.read)刻意區分。

沒有發現「真漏綁」的路由——三筆都能在各自來源 migration 找到明確的白話說明,判定為 既定設計而非疏漏。本棒不需回報決策者裁「要不要開卡補」(.3a 卡面待裁事項已解)。

§2

一筆歷史個案(不在 DEV 現況清單內,但守衛測試需要處理)

license-manage 在 scripts/sql/2026-08-08-fr062-2-license-menu-route.sql 首次 新增時沒有綁 route_capabilities(該檔註解:「不綁 capability:本階段守門走既有 platform-admin 軸(route decorator),未新增 capability」)——這是「用另一套授權軸 守門、DB 層刻意留空」的設計,不是遺漏。兩天後 scripts/sql/2026-08-10-fr062-8- license-menu-restructure.sql 補上了 license.read 能力點與綁定,DEV 現況因此已不 在 fail-open 清單內(見上表,license-manage 未出現)。

守衛測試的白名單(test/test_package_ui_routes_have_route_capabilities.py 的 _INTENTIONAL_FAIL_OPEN_ROUTES)以 (檔案, name) 為單位登記,而非只用 name—— 只豁免 2026-08-08-fr062-2-... 這一支舊檔案,2026-08-10-fr062-8-... (已補綁定的那支)與 04-seed-core.sql(出貨 seed 攤平現況,license-manage 一列 已含綁定)都不在白名單內、必須實際過守衛。這樣設計是為了避免「用 name 當白名單」 把未來任何新增同名路由的檔案也一併豁免掉,形成守衛盲區。

§3

對 .3c 的輸入

.3c(CM-1762,套件 migration+jedi pyproject 對齊)盤點「哪些套件有頁面」時,本 白名單是既有出貨路由的基線;套件新增選單路由時仍受本守衛約束,不會被本白名單 豁免(豁免以「檔案」為單位,套件 SQL 檔名與這裡登記的主專案檔名不會撞名)。