---
title: "專案角色改成「流程站＋專案內功能」— 需求討論稿 (FR-160)"
brand: "Guidant AI · **FR-160** 專案角色分層重整"
eyebrow: "FR-160 · Project Role by Stage & Feature — 需求討論稿 · 2026-10-22 · 待決策者裁 D1–D8"
h1: "專案角色改成「流程站＋專案內功能」"
subtitle: "系統權限縮成模組開關；專案角色照流程圖勾站、照畫面勾功能"
lede: "FR-155 做完後，專案角色頁列的 13 顆權限是從程式檢查點倒推出來的，管理員看不出「專案稽核人員要勾哪幾顆」；AI 自動分類、問卷、系統安全計畫這些塞進專案頁的功能又只住在系統權限，專案角色管不到；同一個動作要在系統頁和專案頁各勾一次。本案定一條分層判準——**動作有沒有「專案」這個對象**——系統角色頁縮成「每模組一顆可使用＋沒有專案對象的動作」；專案角色頁改成三區：流程站職責（照流程圖一站一勾）、專案內功能（含 AI 分類、問卷、SSP）、專案管理；流程設計器的「主要角色」改成從角色表反查，不再手選。"
chips: [
  {text: "已拍板：分層判準＝動作有沒有專案對象；系統層縮成模組開關（決策者 2026-10-22 選 B）", kind: ok},
  {text: "D1–D8 待裁", kind: accent},
  {text: "雛形：prototype/index.html（三頁可點）", kind: accent},
  {text: "改到客戶看得到的系統角色設定，升級要換算", kind: crit}
]
footer: "FR-160 · 專案角色分層重整 — 需求討論稿 · 2026-10-22 · 盤點來源：DEV capabilities 表（238 顆）、BE／套件 API 守門 grep、FE 專案頁 v-can 盤點 · 前作：FR-155 專案角色併入 RBAC、FR-132 存取管理、FR-140 內外稽核流程"
---

## 需求背景與目標 {#why nav="背景"}

### 決策者看到的問題（2026-10-22 原話）

> 我在專案權限那邊，要讓 user 可以知道說噢我今天專案稽核人員要給他哪些？現在看起來都是一堆不相干的權限很詭異。
> 每一站的權限點不同專案角色要怎麼設定？然後怎麼跟流程那邊的設定結合？發現當初最後一個流程設定設計得很潦草。
> 各個功能裡面的功能點也要被納進來，像是能不能用 AI 自動分類等等。
> [— 決策者]{.attr}

### 實查出來的三個根因

| # | 根因 | 證據 |
|---|---|---|
| ① | **專案角色的 13 顆權限是照程式檢查點切的，不是照流程站切的**。跟流程圖對得上的只有 4 顆（稽核判定、審閱、管理改善計畫、推進任一階段），其餘 9 顆是專案管理類 | `common/authz/project.py`、`infra/readmodel/` 8 支 SQL、5 套件呼叫端的 `proj.*` 分布 |
| ② | **塞進專案頁的其他模組功能，權限全住在系統層，專案角色管不到**：AI 自動分類（`evidence.classification.*` 5 顆）、問卷（`survey.*` 5 顆）、SSP 編輯匯入（`oscal.ssp.*`）、資源庫更新（`oscal.resource-library.*`，專案頁 20 處按鈕）、檢測結果、風險 | FE 專案頁 `v-can="CAP.*"` 盤點：`GRC_JOB_UPDATE` 30 處、`OSCAL_RESOURCE_LIBRARY_UPDATE` 20、`SURVEY_SURVEY_UPDATE` 17、`EVIDENCE_CLASSIFICATION_*` 13 |
| ③ | **同一個動作掛兩道門且兩道幾乎重複**：後端每個專案內寫入都是「系統 `grc.*` 過了 → 再問專案 `proj.*`」；出貨系統角色「稽核主管」是 `grc.*.*` 全開，「稽核員」「一般成員」也開了任務、稽核、報告的讀寫，**系統層那道門幾乎人人都有，真正擋人的是專案層**，但畫面上兩邊字一樣 | `api/` 與套件 `require_capability("grc.*")` 共 200 餘處；`resources/roles/shipped-roles.json` |

另有兩個實查發現，與設計直接相關：

- **每一站裡的動作已經綁在「站」上**：填稽核結果只能在稽核站（`assert_round_phase(r, {"auditing"})` 回 412），改善計畫里程碑只能在缺失改善站（`{"remediation"}`）。「站」已經是真正的工作單位，權限只是多問「你是不是負責這站的人」。
- **三個站的「做事的人」不是靠角色**：規劃站的任務看 `task_assignees`、審閱站看 `job_review_step_assignees`、缺失改善站的里程碑表有 `assignee_user_id`（DEV 18 筆有指派）但結案時沒去看它。角色真正在管的只有「能不能推進這一站」與「稽核站誰能填結果」。

### 分層判準（決策者 2026-10-22 拍板）

> **看這個動作有沒有「專案」這個對象。** 問得出「哪個專案」→ 專案層；問不出 → 系統層。

程式面可機械判定：後端檢查點有先 resolve「這筆資料屬於哪個專案」的就是專案層，沒有的就是系統層。可寫守衛測試驗證，不靠人判斷。

## 分工概述（30 秒版） {#overview nav="分工概述"}

**一句話**：先用判準把 238 顆系統權限逐顆分邊定稿（C.1），再改資料與升級換算（C.2），把專案內功能點補上專案層守門（C.3），最後三個畫面（C.4）。

| 階段 | 做什麼（白話） | 產出 | 完成怎麼判定（決策者可親手檢查） | 依賴 |
|---|---|---|---|---|
| **C.1 分邊定稿** | 238 顆系統權限逐顆套判準：留系統層／併成模組開關／搬到專案層；專案內每個功能點對到一顆專案權限；六個流程站各對到哪幾顆 | `inventory/c1-capability-split.md`（三張表） | 打開表：每顆都有「系統／專案」結論與一句理由；邊界案例（資源庫、分類設定檔、風險）各有明確歸屬 | D1–D4 |
| **C.2 資料與升級** | 系統層新增「可使用 X」每模組一顆、刪併細顆；專案層新增約 15～20 顆；每租戶既有系統角色自動換算（原有任一細顆 → 給模組開關；原只有 read → 給「專案內只能看」）；四個範例專案角色改成站＋功能的組合 | migration、seed、真實舊庫排練紀錄 | 真實舊庫升級後，用舊帳號登入，選單與專案內按鈕與升級前一模一樣 | C.1 |
| **C.3 守門補齊** | 專案內功能點（AI 分類、問卷、SSP、報告、風險…）後端補「專案層也要過」；被指派人那條路補齊（任務、關卡、里程碑）；系統層細顆的檢查改成模組開關 | 約 25 個檢查點改動，橫跨 5 套件 | 建一個只勾「稽核」站的專案角色指派給某人：他能填稽核結果，但按 AI 自動分類回 403；把「AI 自動分類」勾上立刻能按 | C.2、D5 |
| **C.4 畫面** | 專案角色頁三區（流程站、專案內功能、專案管理）＋右側「能做什麼」預覽；系統角色頁改成模組開關＋公司層動作；流程設計器「主要角色」改唯讀反查 | FE 三頁 | 對照雛形三頁；建「專案稽核人員」只勾兩站，進專案看到的按鈕正好是那兩站的 | C.3、D6–D8 |

C.1→C.2→C.3→C.4 照順序。

## 已定案事項 {#decided nav="已定案"}

::: {.callout .decided}
**✅ 決策者 2026-10-22 已拍板**

- **分層判準**：動作有沒有「專案」這個對象。
- **系統層縮成一層**（選項 B）：每模組一顆「可使用」＋沒有專案對象的動作；專案內細節全交專案角色。接受代價：系統角色頁細顆消失、升級要自動換算、版本說明要寫。
- **專案角色頁要能回答「專案稽核人員要給他哪些」**：照流程圖勾站，專案內功能點（含 AI 自動分類）納進來。
- **流程那邊的設定要跟角色結合**，不再各設各的。
:::

## 待決策 D1–D8 {#pending nav="待決策"}

::: {.callout .pending}
**D1 系統層最後長什麼樣**

套判準後，系統層剩兩類：

| 類 | 內容 | 顆數 |
|---|---|---|
| 模組開關 | `grc.use`（專案稽核）、`evidence.use`（證據分類）、`survey.use`（問卷）、`oscal.ssp.use`（系統安全計畫）、`detection.use`、`asset.use`、`ai.use`、`workspace.use` | 8 |
| 沒有專案對象的動作 | 建專案 `grc.project.create`、刪專案 `grc.project.delete`、流程範本 `flow.template.*`、審閱關卡範本 `grc.review-step-template.manage`、RACI 預設 `grc.raci.manage`、公司層風險總表 `grc.risk.*`、治理總覽 `grc.governance.*`、合規框架 `oscal.framework.*`、資源庫 `oscal.resource-library.*`、分類設定檔 `evidence.classification-profile.*`、雲端整合 `evidence.cloud-integration.*`、檢測設定（plugin／profile／agent／scan-zone）、資產、AI 設定、系統設定、組織與存取、原廠平台 | 約 110 |

從 238 縮到約 120，拿掉的全是「專案內的讀寫增刪細顆」（`grc.job.*`、`grc.audit.*`、`grc.job-evidence.*`、`grc.job-comment.*`、`grc.summary-report.*`、`grc.project.read/update/assign`、`evidence.classification.*`、`survey.*`、`oscal.ssp.*`、`detection.source-scan.execute/export`）。

[**建議照上表**。另加一顆 `grc.readonly`「專案內只能看」：讓管理員仍可把某人設成全公司唯讀，不必逐專案設瀏覽者。升級時原本只有 `grc.*.read` 的角色自動拿到它。]{.rec}
:::

::: {.callout .pending}
**D2 專案層「流程站」怎麼表示**

選項：
- **(a) 每站一顆 `proj.stage.<code>`**：`proj.stage.planning`、`proj.stage.ap_authoring`、`proj.stage.audit`、`proj.stage.review`、`proj.stage.poam`、`proj.stage.closing`，勾了＝做這站的事＋推進這站。站的動作（填結果、建改善計畫）後端改查對應站的顆。
- (b) 維持現在 4 顆動作型權限，畫面上換個名字。

[**建議 (a)**：站就是工作單位，權限名對到站，流程設計器才能反查。`stage_objects` 表已是資料驅動，新加站就新加一顆。]{.rec}
:::

::: {.callout .pending}
**D3 「推進這一站」與「做這站的事」要不要分開**

例：有客戶要「稽核員填結果，但只有稽核主管能按推進」。
- **(a) 不分開**：勾了站就兩者都有，清單短。
- (b) 分開：每站兩顆，清單加倍。

[**建議 (a)**，有客戶要再加。「推進任何階段」這顆仍保留給負責人。]{.rec}
:::

::: {.callout .pending}
**D4 專案內功能點定稿**

照「畫面上的功能」分組，不照程式模組。草案：

| 分組 | 顆 | 白話 |
|---|---|---|
| 任務 | `proj.job.manage` | 建立、指派、匯入、代成員完成、催辦 |
| 佐證 | `proj.evidence.upload`、`proj.evidence.classify`、`proj.evidence.manage` | 上傳佐證／跑 AI 自動分類與確認結果／刪除與批次操作 |
| 問卷 | `proj.survey.design`、`proj.survey.publish` | 設計問卷／發布問卷（填答走被指派人） |
| 系統安全計畫 | `proj.ssp.update`、`proj.ssp.import` | 編輯／匯入匯出 |
| 評估計畫 | `proj.ap.import` | 匯入匯出評估計畫 |
| 報告 | `proj.report.manage`、`proj.report.read` | 建立與發布／檢視匯出 |
| 風險 | `proj.risk.manage` | 專案內風險評估 |
| 檢測 | `proj.detection.read` | 看檢測結果 |
| 專案管理 | `proj.project.update`、`proj.participant.manage`、`proj.round.manage`、`proj.stage.advance-any` | 設定／成員／輪次／推進任何階段 |

[**建議照上表**，C.1 盤點時逐顆對到後端檢查點，缺的補、沒人用的刪。]{.rec}
:::

::: {.callout .pending}
**D5 「被指派的人」這條路**

任務（`task_assignees`）、審閱關卡（`job_review_step_assignees`）已經是「被指派人 ∪ 有管理權限的人」；改善計畫里程碑有 `assignee_user_id` 但結案沒看它。
- **(a) 三處統一**：里程碑結案補「被指派人也可以」；決策者說的「POA&M 負責人或專案負責人」就是這條。
- (b) 只補里程碑。

[**建議 (a)**，寫成一條通則進 `common/authz/__init__.py` 決策表。]{.rec}
:::

::: {.callout .pending}
**D6 流程設計器的「主要角色」欄**

- **(a) 改成唯讀反查**：顯示「負責此站的角色：稽核員、資深稽核員」，從角色表反查哪些專案角色勾了這站；站的主責存 `proj.stage.<code>`，`main_role` 欄不再讓人選。
- (b) 維持可選但只列六站。

[**建議 (a)**：決策者原話「流程那邊的設定要跟角色結合」。範本上不存角色、只存站，角色改了流程不用改。]{.rec}
:::

::: {.callout .pending}
**D7 專案角色頁右側「能做什麼」預覽**

勾完即時顯示：能推哪幾站（照流程圖順序）、進專案會看到哪些按鈕、會收哪些通知。
- **(a) 做**，與系統角色頁「可見選單預覽」同機制。
- (b) 不做。

[**建議 (a)**，這是「看不出勾了能幹嘛」的直接解法。]{.rec}
:::

::: {.callout .pending}
**D8 範例專案角色重新定義**

| 範例角色 | 流程站 | 專案內功能 | 管理 |
|---|---|---|---|
| 負責人（預設） | 全部 | 全部 | 全部 |
| 稽核員 | 稽核計畫填寫、稽核 | 佐證上傳、AI 分類、評估計畫匯入、報告檢視 | — |
| 審核者 | 審閱 | — | — |
| 瀏覽者 | — | 報告檢視 | — |

[**建議照上表**；「專案稽核人員」就是稽核員，管理員一眼看得出勾了兩站。]{.rec}
:::

## 做出來長什麼樣 {#ui nav="畫面"}

三頁雛形在 [prototype/index.html](prototype/index.html)。

| 頁 | 新增／改變 |
|---|---|
| 1 專案角色詳情 | 三區：**流程職責**（照流程圖列六站，一站一勾，站名旁寫「勾了能做什麼」）、**專案內功能**（任務／佐證／問卷／SSP／評估計畫／報告／風險／檢測）、**專案管理**；右側「這個角色能做什麼」預覽 |
| 2 系統角色詳情（縮併後） | 「權限」區改成兩段：**模組開關**（8 顆）、**公司層動作**（照模組分組）；專案內細顆消失 |
| 3 流程設計器 | 任務節點「主要角色」改唯讀：「負責此站的角色：稽核員、資深稽核員」，旁邊連去角色頁 |

## 分層表（草案，C.1 定稿） {#split nav="分層表"}

### 邊界案例

| 功能 | 歸屬 | 理由 |
|---|---|---|
| 資源庫更新（專案頁有 20 處按鈕） | 系統層 | 資源庫不屬於任何專案；專案頁上的按鈕是在改共用範本 |
| AI 分類設定檔 | 系統層 | 設定檔全公司共用 |
| 在專案裡跑 AI 分類 | 專案層 `proj.evidence.classify` | 問得出哪個專案 |
| 公司層風險總表 | 系統層 `grc.risk.*` | 跨專案 |
| 專案內風險評估 | 專案層 `proj.risk.manage` | 綁專案 |
| 雲端硬碟同步設定 | 系統層 | 整合設定；專案的「初始化資料夾」走 `proj.project.update` |
| 標準答案匯入（不綁專案） | 系統層 `evidence.use`＋模組內細顆 | FR-155 R8 已裁 |
| 問卷範本庫 | 系統層 | 共用；專案內的問卷設計走 `proj.survey.design` |

### 判定流程

```{.mermaid cap="圖 1 — 一次「按下 AI 自動分類」的判定（縮併後）"}
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#E2F0F1','primaryTextColor':'#14201F','primaryBorderColor':'#0E7C86','secondaryColor':'#EEF2F3','secondaryTextColor':'#14201F','tertiaryColor':'#FBFCFC','tertiaryTextColor':'#14201F','lineColor':'#4A5A5C','textColor':'#14201F','mainBkg':'#E2F0F1','nodeBorder':'#0E7C86','nodeTextColor':'#14201F','edgeLabelBackground':'#FBFCFC','titleColor':'#14201F','clusterBkg':'#FBFCFC','clusterBorder':'#E4EAEB','actorBkg':'#E2F0F1','actorTextColor':'#14201F','actorBorder':'#0E7C86','actorLineColor':'#C7D1D2','signalColor':'#4A5A5C','signalTextColor':'#14201F','labelBoxBkgColor':'#EEF2F3','labelBoxBorderColor':'#C7D1D2','labelTextColor':'#14201F','loopTextColor':'#14201F','noteBkgColor':'#F6EBD5','noteTextColor':'#14201F','noteBorderColor':'#9C6B12','activationBkgColor':'#EEF2F3','activationBorderColor':'#C7D1D2','sequenceNumberColor':'#FFFFFF'}}}%%
flowchart LR
  A[按「AI 自動分類」] --> B{系統層<br/>evidence.use？}
  B -- 沒有 --> X1[選單就看不到這個模組]
  B -- 有 --> C{專案層<br/>這個專案的角色有<br/>proj.evidence.classify？}
  C -- 沒有 --> X2[按鈕不顯示；API 403]
  C -- 有 --> D[執行]
```

```{.mermaid cap="圖 2 — 流程站與角色的結合（縮併後）"}
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#E2F0F1','primaryTextColor':'#14201F','primaryBorderColor':'#0E7C86','secondaryColor':'#EEF2F3','secondaryTextColor':'#14201F','tertiaryColor':'#FBFCFC','tertiaryTextColor':'#14201F','lineColor':'#4A5A5C','textColor':'#14201F','mainBkg':'#E2F0F1','nodeBorder':'#0E7C86','nodeTextColor':'#14201F','edgeLabelBackground':'#FBFCFC','titleColor':'#14201F','clusterBkg':'#FBFCFC','clusterBorder':'#E4EAEB','actorBkg':'#E2F0F1','actorTextColor':'#14201F','actorBorder':'#0E7C86','actorLineColor':'#C7D1D2','signalColor':'#4A5A5C','signalTextColor':'#14201F','labelBoxBkgColor':'#EEF2F3','labelBoxBorderColor':'#C7D1D2','labelTextColor':'#14201F','loopTextColor':'#14201F','noteBkgColor':'#F6EBD5','noteTextColor':'#14201F','noteBorderColor':'#9C6B12','activationBkgColor':'#EEF2F3','activationBorderColor':'#C7D1D2','sequenceNumberColor':'#FFFFFF'}}}%%
flowchart TB
  subgraph tpl[流程範本（設計器）]
    S1[規劃 stage=planning] --> S2[稽核計畫填寫 stage=ap_authoring] --> S3[稽核 stage=audit] --> S4[缺失改善 stage=poam]
  end
  subgraph role[專案角色頁]
    R1[稽核員：勾 ap_authoring、audit]
    R2[負責人：勾全部＋推進任何階段]
  end
  subgraph run[專案執行]
    P[走到稽核站] --> Q{我的角色勾了 audit？<br/>或有推進任何階段？}
    Q -- 是 --> OK[能填結果、能推進]
    Q -- 否 --> NO[只能看]
  end
  tpl -. 範本只存站代碼，不存角色 .-> run
  role -. 角色勾了哪些站 .-> run
  S3 -. 設計器反查：負責此站＝稽核員、負責人 .-> R1
```

## 風險與不做 {#risk nav="風險"}

::: {.callout .warn}
**⚠️ 三個最可能出事的點**

- **升級換算**：客戶自建的系統角色勾了細顆（例：只勾 `grc.job.read`），換算規則要定清楚：任一細顆 → 模組開關；只有 read → 模組開關＋`grc.readonly`。排練用真實舊庫，逐角色比對升級前後「選單與按鈕」而不是權限名。
- **授權照（license）**：授權照綁在系統層能力點的 `license_module` 上。縮併後 `evidence.use` 這類開關要接住原本的 `license_module`，否則客戶授權會被判「沒買」。C.1 要先實查 `common/authz/license.py` 的對照。
- **五套件守門補齊**：AI 分類、問卷、SSP、檢測、報告各在不同套件，補專案層守門要走 path dependency 一次發版；漏一處就是「系統層放寬了、專案層又沒擋」的洞。守衛測試要掃「專案內端點必有專案層守門」。
:::

**明確不做**：拒絕規則；「推進」與「做事」分開（D3 裁後才做）；專案角色匯出 JSON；部門範圍。
