本章目標與前置條件
讀完後,你應能分辨 document version、space binding、visibility policy、restrictive override 與 audience candidate,並知道為何「模型猜到的 audience」不能直接成為存取授權。前置條件是理解版本狀態、selector、tenant isolation 與 outbox。
功能現況
| 區塊 | 後端已核實功能 | 前端現況 |
|---|---|---|
| Document lifecycle | document list/create/detail、version list/detail、publish、revoke;POST /v1/documents/:documentId/versions(createDocumentVersion)只在 OpenAPI 契約中、尚未註冊路由(e2e 斷言 404),目前無法經 API 建立可發布的版本 | 當前 web checkout 無專屬 page.tsx/live hook;/knowledge/documents 是 /v1/me/menus 的可見節點時,catch-all 顯示待開發頁 |
| Knowledge spaces | space list/create/get/update、document bind/list/unbind、audience scopes;新 binding 一律為 pending,目前沒有程式把它轉為 active | /knowledge/spaces 由後端 route registry 宣告;沒有專屬頁面元件,走 catch-all 待開發狀態 |
| Visibility policy | create/get/list、revision、retire(權限點為 knowledge.visibility_policy.delete)、selector add/remove | backend contract 與 MSW permission seed 存在;沒有 /v1/knowledge live adapter,不能把 fixture 當 live |
| Document override | get/set/clear restrictive selector set;set 與 clear 都 bump ACL 並寫 audit | 前端無對應 adapter 或專屬頁面 |
| Audience review | candidate list、approve/reject(兩者都需要 knowledge.audience.approve);approved 才 promote scope;candidate 的 evidence、model_version、prompt_version 隨 row 保存;目前沒有產生 candidate 的程式 | 前端無 live review page |
| 檢索與 ingest | 尚未實作:沒有 ingest、projection 或檢索端點 | 無 |
核心概念
Document version 與 projection 分離
Document 是邏輯內容(kind 為 file 或 faq),version 保存 immutable revision,狀態包含 draft、published、superseded、revoked、deleted。publish 會把前一個 published version 轉為 superseded 並改指 current_version_id;revoke 撤回 published version,若它是目前版本就清空指標。published version 不代表已能被 retrieval 使用;knowledge binding 還要等 space policy 與投影狀態,而 binding 啟用與 projection consumer 目前都尚未實作,worker 收到 binding/visibility/override 與 document.version.* 事件只記錄變更。
Visibility 是 selector 的聯集,override 只能收窄
policy selector 支援 tenant、role、group、org_unit,org_unit 才能使用 subtree;tenant selector 不帶 subject_id,其他 kind 必須帶。document override 與 space policy 取交集,不能用 override 擴大 space 已授權的集合。沒有 active policy 的 space fall closed;retire 是撤回,不是刪除稽核事實。以上是資料模型與 service 驗證的規則,讀取端的評估要等檢索鏈落地後才存在。
ACL version 是 cache invalidation 邊界
只有會收緊或改變授權事實的寫入,才在同一 tenant transaction 內 bump knowledge_acl_versions 的租戶計數器:space 更新(PUT,含改指向 default policy)、解綁、policy revise 與 retire、selector 新增與移除、override 設定與清除,以及 document publish(含 supersede)與 revoke。建立 space、建立 policy 與綁定文件(pending)刻意不 bump、也不發事件,governance integration test 會斷言計數器不動。
knowledge 治理事件(knowledge.visibility.changed、knowledge.binding.changed、knowledge.override.changed)的 payload 帶 acl_version;缺少 counter 的事件應視為 malformed,而不是當成 0。document.version.published、document.version.revoked 的 payload 只有 document、version、revision 與 reason,不帶 acl_version。未來的讀模型或 retrieval cache 需以 counter 判斷是否需要丟棄。
Audience 不等於 visibility
audience scope 是內容適用對象的 reviewed statement,永遠不自行 widening/narrowing visibility。candidate 保存 evidence、model/prompt version 和 reviewer;只有人審 approved 才建立 audience scope,reject 也留下可追查狀態。審核不碰 ACL counter、不發 outbox,但會寫 knowledge.audience.reviewed audit;對已審核的 candidate 再做決定回 audience_candidate_reviewed(409)。
一條治理鏈
正在繪製架構圖…
查看圖表原始碼
flowchart LR Doc["POST /v1/documents"] --> Version["draft version:createDocumentVersion 尚未開放"] Version --> Publish["publish/revoke"] Publish -- "bump + document.version.*" --> ACL["knowledge_acl_versions + outbox"] Space["space + visibility policy"] --> Bind["space binding:pending"] Tighten["解綁、policy/selector/override 變更、space 更新"] -- "bump + knowledge.*.changed" --> ACL ACL --> Worker["worker:目前只記 log"] Worker -. "規劃中" .-> Projection["retrieval projection:尚未實作"] Bind -. "啟用尚未實作" .-> Projection Candidate["AI audience candidate:尚無產生器"] --> Review["human review"] Review -- "不直接授權、不 bump" --> Scope["audience scope"]
實作步驟與示例
- 先確認是 document lifecycle、space governance、visibility policy 或 audience review,對照
internal/route/testdata/registry.golden(或routes.go的register*Routes)的 permission/risk/tenant-wide;api/openapi.yaml只定義 wire 形狀,不含這些欄位。 - 新增 selector kind 或 status 時,先改 domain vocabulary,再同步 DB CHECK、OpenAPI enum、service validation、worker payload 與 contract tests。
- 會收緊或改變授權事實的寫入(publish/revoke、解綁、policy revise/retire、selector、override、space 更新)要在同一 transaction 同時保存業務變更、ACL bump 與 outbox event;建立 space/policy 與建立
pendingbinding 不 bump。worker 在 transaction 外處理事件,目前只記 log。 - UI 只顯示 server 返回的 status、ACL revision 與 reviewer evidence;不自行拼出「已可檢索」或「已授權」。
GET /v1/knowledge/visibility-policies?status=active&page=1&page_size=20
POST /v1/knowledge/audience-candidates/<candidate-id>/review
Content-Type: application/json
{"decision":"approve"}ID 僅為格式示例。decision 只接受 approve 或 reject,其他值回 422;兩者都需要 knowledge.audience.approve。驗證 approved 時需看到 candidate 與 promoted scope 的一致性,以及 knowledge.audience.reviewed audit;audience review 不改變 authorization truth,因此不會有 ACL bump 或 outbox event。retrieval projection 尚未實作,不能以此宣稱檢索已完整交付。
驗證矩陣
| 驗證問題 | 程式碼/測試證據 | 仍需另外做的驗收 |
|---|---|---|
| document version transition | internal/api/v1/documents.go、internal/service/document/document.go、tests/integration/postgres/document_lifecycle_test.go、tests/e2e/document_routes_test.go | object storage、版本建立路由(尚未開放)與 publish/revoke 的 runtime |
| space binding | internal/service/knowledge/spaces.go、internal/api/v1/knowledge_test.go、tests/integration/postgres/knowledge_governance_test.go | binding 啟用、projection consumer 與 retrieval(尚未實作) |
| policy/selector/override | internal/service/knowledge/policies.go、internal/service/knowledge/overrides.go、internal/domain/knowledge.go、tests/integration/postgres/knowledge_governance_test.go、tests/integration/postgres/knowledge_truthtable_test.go | cache invalidation(目前沒有 cache)與跨租戶負向案例 |
| audience review | internal/service/knowledge/audience.go、internal/api/v1/knowledge_audience.go、internal/api/v1/knowledge_test.go | candidate 產生器(尚無)、evidence 檢視與 promote readback |
| 前端頁面 | nexus-pro-web-plus/app/(platform)/[...segments]/page.tsx、libs/permission/menuAccess.ts、test/fixtures/routeRegistry.json、test/msw/handlers/me.ts | 當前 checkout 無專屬 knowledge page.tsx/live hook;需另立前端實作與 API adapter |
常見錯誤
- 把
publishedversion 直接視為 retrieval 可見;binding、policy 與 projection 仍有獨立狀態。 - 用一個 global visibility flag 代替 selector 聯集與 override 交集,造成 policy 變更不可追溯。
- 把 audience candidate approve 當成 ACL grant;audience 與 visibility 是刻意分離的模型。
- 發出沒有
acl_version的 knowledge event,讓 consumer 誤判 cache 已最新。 - 為建立 space、policy 或
pendingbinding 補上 ACL bump;它們不改變授權事實,governance integration test 會斷言計數器不動。 - 以為
POST /v1/documents/:documentId/versions已可呼叫;它只在契約中,路由尚未註冊。 - 以 permission fixture 或 route registry 宣稱正式 UI 已完成;當前 checkout 沒有專屬 knowledge
page.tsx/live hook,路由是 catch-all 的待開發頁,test/fixtures/routeRegistry.json只是由後端同步的測試 fixture,線上導覽以/v1/me/menus為準。