跳轉到

通知紀錄(Project Alert Notifications)

功能群:專案管理|上層見 專案總覽頁(本頁由總覽頁動作列「通知紀錄」進入)

事實基準:2026-10-14 從 FE src/views/project/ProjectAlertNotificationsView.vue、BE api/task_alert/、app/task_alert/service/、infra/task_alert/、infra/readmodel/detection/scan_alert_context_query.py/support_end_query.py、migration 2026-10-12-fr137-*/2026-10-13-fr137-* 實查掃出

變更紀錄

日期 FR 變更
2026-10-14 FR-137 新頁:專案內的條件通知寄送紀錄——SBOM 掃描新出現 CRITICAL/HIGH 的額外通知、資訊系統版本支援期剩 90/30/7 天的提醒,每封一列

1. 功能描述

通知紀錄是專案層的唯讀紀錄頁:系統依條件自動寄出的通知信,每寄一位收件人就在這裡留一列,讓專案成員查得到「寄了沒、寄給誰、為什麼寄、寄失敗的原因」。目前有兩種來源:

  1. SBOM 門檻通知:週期任務(或手動執行)發起的第三方元件弱點掃描完成後,與同一筆任務上一次成功的掃描比對,有新出現的 CRITICAL/HIGH 弱點就寄給任務設定的「高風險額外通知對象」(設定見任務配置 §5「任務週期」)。
  2. 支援期提醒:專案 SSP 資產清冊納入的資訊系統,若某版本的支援期結束日剩 90/30/7 天,寄給該 CRA 專案的管理者(版本與支援期在資訊系統盤點維護)。

本頁只看、不寄、不補寄——寄送失敗的不會自動重寄。

主要使用者:專案任一參與者(manager/auditor/reviewer/viewer 都看得到)。

角色速覽

角色 一句話
專案參與者 任一角色都可看本專案的通知紀錄
非參與者 看不到(回 404 GRC_404001)

1.1 功能總覽(本頁全部功能)

# 功能 說明 位置 詳述
1 返回專案總覽 回本專案總覽頁 Header 左側箭頭 —
2 三張計數卡 本專案通知(全部)/已寄出/寄送失敗;失敗數 > 0 時字轉紅 Header 下 §6.2
3 寄送失敗提示 有失敗時顯示「有 N 封寄送失敗……請管理員到郵件設定啟用;失敗的不會自動補寄」 計數卡下(條件顯示) §12
4 篩選:結果 全部/已寄出/寄送失敗 篩選列 §6.2
5 篩選:期間 近 90 天(預設)/全部 篩選列 §6.2
6 通知清單 分頁(25/50/100),新到舊;五欄:時間/掃描/新出現 high 以上/收件人/結果 主表 §5
7 點列跳轉 SBOM 列 → 該次掃描結果頁;支援期列 → 資訊系統盤點 主表整列 UC-PAN-01

UC(§2)展開一條:查看通知並追到來源。兩種通知怎麼產生屬背景行為,見 §11。

2. Use Case

角色速覽(同 §1)。

UC-PAN-01 查看通知並追到來源

項目 內容
角色 專案參與者
前置條件 專案存在、使用者是本專案參與者;系統至少寄過一封本專案的條件通知
產出 / 後置條件 唯讀,不改任何資料
  1. 專案總覽頁動作列按「通知紀錄」→ 進本頁,預設顯示近 90 天、全部結果。
  2. 看計數卡;有寄送失敗時,提示條說明原因(多半是郵件伺服器未啟用)。
  3. 切換「結果」「期間」篩選,清單回第 1 頁重查。
  4. 點 SBOM 列 → 開 掃描結果頁(source-scan-result,帶該次 scan_uid);點支援期列 → 跳 資訊系統盤點。

3. 權限矩陣

操作 FE 判定 BE 強制 BE 檢查位置
進頁 路由登記為內頁(母頁 project-auditor-overview),需 grc.project.read @require_license("project")+@require_capability("grc.project.read") api/task_alert/routes/task_alert_notification_route.py
讀清單 總覽頁的入口鈕不分角色都顯示 assert_project_participant:任一參與者放行,非參與者 404 GRC_404001 app/task_alert/service/task_alert_notification_app_service.py::list_notifications

守門分兩層:route 層驗「你有沒有讀專案的能力點」(主體域),app service 層驗「你是不是這個專案的人」(資源域)。

4. 狀態機與前置條件

本頁無狀態機。每列的「結果」三態:

結果 意思 何時寫入
pending(寄送中) 已搶下紀錄列、信還沒寄完 寄信前先插列(唯一鍵擋重複)
sent(已寄出) 郵件服務回成功,sent_at 記時間 寄成功後
failed(寄送失敗) 例外或郵件服務回「未寄出」;error 記原因 寄失敗後;不自動補寄
條件 效果 出處
專案 uid 查不到 404 GRC_404001 list_notifications
非本專案參與者 404 GRC_404001(不洩漏專案存在) common/authz/project.py::assert_project_participant

5. UI 設計

區 內容
Header 返回鈕+「通知紀錄 — 專案名」
說明句 「SBOM 掃描比上一次多出 CRITICAL/HIGH 弱點時寄給任務設定的額外通知對象;資訊系統版本支援期剩 90/30/7 天時寄給 CRA 專案管理者。每封都記在這裡。」
計數卡 ×3 全部/已寄出/寄送失敗(計數隨「期間」篩選,不隨「結果」篩選)
篩選列 結果(SelectButton)、期間(SelectButton)
主表 見下表;空狀態「目前沒有通知紀錄」

主表欄位:

欄 SBOM 門檻通知列 支援期提醒列
時間 sent_at(未寄成功則 created_at),YYYY-MM-DD HH:mm 同左
掃描 第一行任務名;第二行「源碼包檔名 · 首次掃描」或「源碼包檔名 · vs 上一次」 第一行「支援期提醒 系統名 版本」;第二行「剩 N 天 · 支援期結束日」
新出現 high 以上 CRITICAL n(紅)、HIGH n(橘)tag,數字為 0 的不顯示 「支援期」tag
收件人 顯示名(無則信箱)+第二行信箱 同左
結果 三態 tag;失敗的 hover 顯示錯誤原因 同左

📸 截圖待補(亮色模式):含 SBOM 與支援期兩種列、有一列失敗的畫面 alert-notifications.png。

6. API 規格

Envelope:成功 {"status": true, "data": …}、失敗 {"error_code": "…", "msg": "…"}。

6.1 總清單

分類 Method + Path 說明 完整規格
清單 POST /project/{project_uid}/alert-notifications 分頁取本專案通知紀錄+計數 §6.2
專案名 GET /grc/project/{project_uid} Header 顯示專案名(失敗不擋頁) GAI-SD-02

6.2 [POST] /project/{project_uid}/alert-notifications

Request(TaskAlertNotificationListRequestSchema):

{
  "pager": { "page": 1, "page_size": 25 },
  "filters": { "result": "failed", "period": "90d" }
}
欄位 型別 必填 說明
pager.page / pager.page_size int 否 預設 1/25
filters.result string|null 否 pending/sent/failed;null=全部
filters.period string|null 否 90d=近 90 天;all 或 null=全部

Response data(TaskAlertNotificationListResponseSchema):{items[], paging, summary}。

items[]:

欄位 型別 說明
uid string 紀錄 uid
object_type string scan_alert(SBOM 門檻)/support_end(支援期)
object_uid / stage string|null 支援期列:資訊系統版本 uid、階段 d90/d30/d7
system_name / version / support_end_date string|date|null 支援期列:系統名、版本、支援期結束日(後端依 object_uid 補)
scan_uid / vs_scan_uid string|null SBOM 列:這次掃描、比對的上一次(首掃為 null)
job_name / source_filename string|null SBOM 列:任務名、源碼包檔名(後端依 scan_uid 補)
new_critical_count / new_high_count int 新出現的 CRITICAL/HIGH 數
recipient_email / recipient_label string 收件信箱、顯示名
result / error string 三態與失敗原因
created_at / sent_at datetime 建立時間、寄出時間

summary:{total, sent, failed}——只套「期間」條件,不套「結果」條件。

排序:created_at DESC, id DESC。出處:api/task_alert/serializers/task_alert_notification.py、app/task_alert/service/task_alert_notification_app_service.py、infra/task_alert/repository/task_alert_notification_repo_impl.py::page。

7. 前端檔案地圖(compliance-manager-fe/)

檔案 角色
src/views/project/ProjectAlertNotificationsView.vue 本頁(計數卡、篩選、清單、點列跳轉)
src/views/project/ProjectAuditorOverview.vue 動作列「通知紀錄」入口鈕
src/config/router/index.js route project-alert-notifications,path /project/projects/:id/alert-notifications
src/config/api/api.js PROJECT_ALERT_NOTIFICATIONS
src/config/locales/i18n/*/alert-notifications.json 文案

8. 後端檔案地圖

鏈路 Route App Service 底層
清單 api/task_alert/routes/task_alert_notification_route.py TaskAlertNotificationAppService.list_notifications TaskAlertNotificationDomainService → task_alert_notification_repo_impl;標籤補值 infra/readmodel/detection/scan_alert_context_query.py::scan_labels、support_end_query.py::labels
SBOM 門檻通知(寫入) — app/task_alert/service/scan_alert_app_service.py::handle,收件人解析 scan_alert_recipients.py jedi-source-scan 的 on_scan_completed 回呼(core/plugins/source_scan.py);新出現判定用套件 jedi_source_scan.plugin.scan_alert_diff.new_findings
支援期提醒(寫入) — app/task_alert/service/support_end_reminder_service.py::run_once core/scheduler.py 的 task_recurrence_tick(每 5 分鐘);查詢 infra/readmodel/detection/support_end_query.py

DI:di_containers/task_alert/task_alert_containers.py。

9. DB

9.1 資料表總清單

表 讀寫 說明
compliance.task_alert_notifications 讀(本頁)/寫(背景) 一列一位收件人
compliance.source_scans 讀 SBOM 列補任務名、源碼包名
compliance.information_system_versions/information_systems 讀 支援期列補系統名、版本、結束日

9.3 compliance.task_alert_notifications

欄位 型別 說明
uid varchar(36) 對外識別
tenant_id / org_unit_id int 租戶隔離(RLS FORCE)
project_id bigint → compliance.projects(CASCADE)
object_type varchar(32) scan_alert/support_end
job_execution_id bigint|null soft-ref → 發起掃描的任務
scan_uid / vs_scan_uid varchar(36)|null soft-ref → 這次/比對的上一次掃描
object_uid / stage varchar|null 支援期:版本 uid、d90/d30/d7
new_critical_count / new_high_count int 新出現數
recipient_email / recipient_label varchar 收件人
result / error / sent_at varchar/text/timestamptz 三態、原因、寄出時間

唯一索引 uq_task_alert_notifications_object_recipient (object_type, COALESCE(scan_uid, object_uid), COALESCE(stage,''), recipient_email)——同一次掃描、同一支援期階段對同一信箱只會有一列,重跑不重寄。

10. 頁面邏輯與資料對應

進頁同時打專案明細與清單;篩選改變 → 回第 1 頁重查;換頁 → 帶新 page/page_size 重查。點列依 object_type 分流跳轉。專案名取不到只記 console,不擋清單。

11. 背景行為與外部依賴

類型 內容
SBOM 門檻通知 任務發起的 SBOM 掃描完成(status='done')且任務有設額外通知對象時觸發:找同一筆任務上一次成功的掃描當基準(沒有就是首掃,全部弱點都算新出現),只取 CRITICAL/HIGH 的新出現弱點,有才寄。收件人=任務設定的專案參與人員(已退出專案的略過)+系統責任角色在 living SSP 上對應的人員信箱;同信箱只寄一次。信件語系繁中,列前 5 筆弱點
支援期提醒 掛在每 5 分鐘的 task_recurrence_tick 之後跑。以台北時區的「今天」算剩餘天數,剛好等於 90/30/7 天的版本才算;找出 SSP 資產清冊納入該系統、框架為 CRA、未結束的專案,寄給專案 manager。一天跑很多次,靠唯一索引每階段只寄一次
寄送 先插 pending 列搶位,搶不到=已寄過、跳過;郵件服務回 False(SMTP 未啟用)記 failed 不記成已寄;一位寄失敗不擋其他收件人
Socket 無;本頁進頁與篩選時打 API
系統參數 郵件設定(系統設定 / 郵件設定);信中連結用 SYSTEM_URL

12. 邊界情況與已知坑

  1. 寄失敗不補寄:failed 列不會被任何排程重試;修好郵件設定後,同一次掃描或同一支援期階段也不會再寄(唯一索引已佔位)。
  2. 計數卡不跟「結果」篩選:計數只套期間;選「寄送失敗」時清單只剩失敗列,但計數卡仍顯示三種數字,這是刻意的(卡片是總覽)。
  3. 同一批老弱點不重寄:比對基準是「同一筆任務上一次成功的掃描」,上次就有的弱點不算新出現;中間有失敗的掃描不算基準。
  4. 支援期提醒剛好落在 90/30/7 天才寄:當天系統沒在跑(停機超過一天)就錯過那一階段,不補寄。
  5. 非參與者回 404 不是 403:沿用專案讀取守門慣例,不洩漏專案是否存在。

13. 開發與驗證

  • 跑起來:起 BE(api+worker,SBOM 掃描在 worker 跑)+FE;帳號見 .env/部署文件
  • 導航:專案總覽頁 → 動作列「通知紀錄」
  • 前置資料:一筆綁「第三方元件弱點掃描」的檢測工具任務,週期設定加一位額外通知對象 → 手動執行兩次(第一次全部算新出現);支援期:資訊系統版本的結束日設成今天+30 天,且該系統在 CRA 專案的 SSP 資產清冊內,等下一個 5 分鐘
  • 相關:任務配置「任務週期」、我的任務 §11.4、FR-137