invariant 規則(SCP)
invariant 是 principal-blind 的資料完整性 floor:不論誰寫、從哪個入口寫,列的 post-image 都必須符合 policy。它特別覆蓋一般使用者寫入、trigger actions、callback,以及核准後 staged change 的實際套用,避免某個 carrier 成為繞過路徑。
只有 policy form
{
"type": "invariant",
"name": "完成案件必須有結案日",
"policy": {
"if": { "column": "狀態", "op": "eq", "value": "完成" },
"then": { "column": "結案日", "op": "is_not_null" }
},
"enforcement": "observe"
}invariant 不解析任何房間 scope,因此不接受 channel 的 flat column / require_present form;需要固定 link 集合時,請在 policy leaf 明確提供 IDs。它可用於 chatroom、department、company 三種 scope,也可以與 triggers 共存。每張表最多四條 invariant,全部以 AND 組合,且仍受全表 10 條規則上限約束。
when 不適用;條件必須像上例放在 if node 中。規則判斷 create、update、restore 與 revert 的 post-image;delete 沒有需要驗證的 post-image,因此不由 invariant 否決。既有不合規列仍能被修正,只要修正後的 post-image 合規。
Policy node grammar
Invariant 與 channel 共用同一個 bounded grammar:最深 5 層、最多 24 個 leaf、其中最多 6 個 link leaf。
| 節點 | Shape | 語意 |
|---|---|---|
| AND | { "and": [node, ...] } | 全部成立 |
| OR | { "or": [node, ...] } | 至少一個成立 |
| NOT | { "not": node } | 反轉結果 |
| IF | { "if": node, "then": node, "else": node? } | 無 else 時是 guard:condition false 即通過 |
| Scalar | { "column": name, "op": op, "value": scalar? } | 本表 stored scalar predicate |
| Link membership | { "link": name, "op": op, "value": [id]?, "require_present": bool? } | 固定 ID 集合條件 |
| Link target | { "link": name, "target": node, "quantifier": "any" or "all", "require_present": bool? } | 一跳檢查 linked record |
Scalar operators 是 eq、neq、gt、gte、lt、lte、in、contains、is_null、is_not_null。Link membership operators 是 subset_of、none_in、is_empty、is_not_empty、eq、in。
subset_of、eq、in 必須明確指定 require_present;in 是 multi-id 集合的 subset sugar,eq 的 value 恰好一個 ID。is_empty / is_not_empty 不帶 value。Link target 僅一跳;quantifier: "all" 必須明確指定 require_present,避免空集合的 vacuous truth。若條件 branch 會約束 link,if condition 應由 link_target 的權威資料判斷,不可由 writer-controlled scalar 自我宣告。
以下規則要求每筆庫存都連到至少一個啟用中的產品:
{
"type": "invariant",
"name": "產品必須啟用",
"policy": {
"link": "產品",
"target": { "column": "啟用中", "op": "eq", "value": true },
"quantifier": "all",
"require_present": true
},
"enforcement": "enforce"
}observe 再 enforce
enforcement 是 observe 或 enforce,預設 observe。Observe 會執行相同評估並回報 preview 診斷,但不拒絕寫入或遮蔽讀取;切到 enforce 前,請先處理 preview 中的 invariant_violations。Enforce 違規會以 403 並帶 machine-readable detail.error: "scp_invariant_violation" 拒絕。
使用 rules.preview 驗證候選 policy,再用 rules.set 發布完整清單。
同表若還有 channel 規則
Invariant 本身不解析任何房間 scope。不過同一張 department table 若也有 enforcing channel 規則,呼叫仍必須通過那道 floor——而 channel floor 是從呼叫者所屬的房間解析出來的,不是從 query parameter。這裡沒有東西可以重試:?acting_chatroom_id 已於 2026-07-29 從所有路由移除,現在只會被靜默忽略。
兩道 floor 的失敗方式不同,請以 detail.error 分支,不要用 HTTP status:
detail.error | 由誰擋下 | 意義 |
|---|---|---|
scp_invariant_violation | Invariant | Post-image 對任何寫入者都不合法。修正該列 |
scp_scope_undeclared | Channel | 你所屬的存活房間,沒有一個在這張表上宣告 scope_values。同一張表的讀取不會報錯,而是回 200 零列或 uniform 404 |
scp_out_of_scope | Channel | 你那些房間的合併 scope 沒有涵蓋這一列的 link targets |
合併 scope 如何解析、以及為什麼 agent session 的答案可能比 REST 更窄,見 union 契約。
整體安全層次與 ACL 的關係見 ACL 的 SCP 總覽。