Skip to main content

rust_store_core/
rbac.rs

1//! RBAC 动态策略引擎(core 层判决插件;静态白名单引擎见 [`crate::permission`])
2//!
3//! 与静态引擎的叠加语义(deny-wins):RBAC 判决叠加在静态判决**之后**,任一拒绝即
4//! 拒绝;字段维度取交集;行条件与静态 owner 条件 `$and`。策略未注入(`registry.rbac()`
5//! 为 `None`)时全部原语直通,行为与现状逐位一致——增量收紧,永不放大既有权限面。
6//!
7//! 两个容易搞反的维度:
8//! - grant 之间(同一用户多角色 / 多 grant):**OR**(任一 grant 授予即放行其
9//!   条件命中的行 / 并集字段)——授权是并集;
10//! - 引擎之间(静态白名单 × RBAC):**AND**(deny-wins)——收紧取交集。
11//!
12//! 行为契约(与 [`crate::permission`] 一致,勿破坏):
13//! - `ctx: None` / `internal` / 豁免清单(`exempt_roles`,默认空)命中 → RBAC 不介入(直通);
14//! - 拒绝一律携带 `ERR_PERMISSION:` 稳定前缀(宿主按前缀映射 PermissionError),
15//!   二级标识 `RBAC:` 供日志与测试区分。
16
17use std::collections::HashSet;
18
19use serde_json::{json, Map, Value};
20
21use crate::permission::{get_writable_fields, Context};
22use crate::schema::{Registry, Schema};
23use crate::types::is_truthy;
24
25/// 策略模式:
26/// - [`RbacMode::Overlay`](默认):存在匹配 grant 才生效(按 model 增量收紧);
27/// - [`RbacMode::Enforce`]:受管角色(出现在 `roles` 声明中)对**所有** model 生效,
28///   无匹配 grant 即拒绝(default deny);未受管角色不介入。
29#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
30pub enum RbacMode {
31    #[default]
32    Overlay,
33    Enforce,
34}
35
36/// 写动作粒度(对齐静态引擎 `check_write_perm` 的 `ERR_NO_WRITE` / `ERR_NO_DELETE` 分派)
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub enum WriteAction {
39    Insert,
40    Update,
41    Remove,
42}
43
44impl WriteAction {
45    pub fn as_str(&self) -> &'static str {
46        match self {
47            WriteAction::Insert => "insert",
48            WriteAction::Update => "update",
49            WriteAction::Remove => "remove",
50        }
51    }
52}
53
54/// action 字符串 → [`WriteAction`];非法值显式 Err(禁静默回落)
55pub fn write_action_from_str(s: &str) -> Result<WriteAction, String> {
56    match s {
57        "insert" => Ok(WriteAction::Insert),
58        "update" => Ok(WriteAction::Update),
59        "remove" => Ok(WriteAction::Remove),
60        other => Err(format!(
61            "RBAC action \"{other}\" 非法(仅支持 read / insert / update / remove)"
62        )),
63    }
64}
65
66#[derive(Debug, Clone)]
67pub struct Grant {
68    role: String,
69    model: String,        // 精确 schema 名或 "*"(通配)
70    actions: Vec<String>, // 归一化后("write" 已展开),判定用 contains
71    read_fields: Option<HashSet<String>>,
72    write_fields: Option<HashSet<String>>,
73    owner_only: bool,
74    condition: Option<Value>, // 仅等值标量键值(解析期校验)
75}
76
77#[derive(Debug, Clone, Default)]
78pub struct RbacPolicy {
79    mode: RbacMode,
80    managed_roles: HashSet<String>,
81    grants: Vec<Grant>,
82}
83
84/// 单模型判决视图(`decide` 产出)
85#[derive(Debug, Clone)]
86pub struct ModelDecision {
87    pub allowed_actions: HashSet<String>, // read / insert / update / remove
88    /// None = 字段维度不收紧(与 [`crate::permission`] 的 Option 语义一致)
89    pub read_fields: Option<HashSet<String>>,
90    pub write_fields: Option<HashSet<String>>,
91}
92
93/// 拒绝哨兵:复用 `ERR_PERMISSION:` 前缀(宿主零改动映射 403),二级标识 `RBAC:`
94pub fn deny_msg(action: &str, model: &str) -> String {
95    format!("ERR_PERMISSION:RBAC:角色对 [{model}] 无 [{action}] 权限")
96}
97
98// ─── 解析(fail-fast,禁静默吞) ─────────────────────────────
99
100impl RbacPolicy {
101    /// 由策略 JSON 构建;任何非法形态显式 Err,绝不静默回落
102    pub fn from_json(v: &Value) -> Result<Self, String> {
103        let obj = v
104            .as_object()
105            .ok_or_else(|| "RBAC 策略必须是对象".to_string())?;
106
107        let mode = match obj.get("mode") {
108            None | Some(Value::Null) => RbacMode::Overlay,
109            Some(Value::String(s)) if s == "overlay" => RbacMode::Overlay,
110            Some(Value::String(s)) if s == "enforce" => RbacMode::Enforce,
111            Some(other) => {
112                return Err(format!(
113                    "RBAC mode {other} 非法(仅支持 overlay / enforce)"
114                ))
115            }
116        };
117
118        let mut managed_roles = HashSet::new();
119        match obj.get("roles") {
120            None | Some(Value::Null) => {}
121            Some(Value::Object(rm)) => {
122                for (name, _) in rm {
123                    if name.is_empty() {
124                        return Err("RBAC roles 含空角色名".to_string());
125                    }
126                    managed_roles.insert(name.clone());
127                }
128            }
129            Some(other) => return Err(format!("RBAC roles 必须是对象,实际 {other}")),
130        }
131        if mode == RbacMode::Enforce && managed_roles.is_empty() {
132            return Err(
133                "RBAC mode=enforce 时 roles 不能为空(无受管角色 = 策略空转,属配置错误)"
134                    .to_string(),
135            );
136        }
137
138        let mut grants = Vec::new();
139        match obj.get("grants") {
140            None | Some(Value::Null) => {}
141            Some(Value::Array(gs)) => {
142                for (i, g) in gs.iter().enumerate() {
143                    grants.push(parse_grant(i, g)?);
144                }
145            }
146            Some(other) => return Err(format!("RBAC grants 必须是数组,实际 {other}")),
147        }
148
149        Ok(Self {
150            mode,
151            managed_roles,
152            grants,
153        })
154    }
155}
156
157fn parse_grant(idx: usize, v: &Value) -> Result<Grant, String> {
158    let o = v
159        .as_object()
160        .ok_or_else(|| format!("RBAC grants[{idx}] 必须是对象"))?;
161    let get_str = |k: &str| -> Result<String, String> {
162        o.get(k)
163            .and_then(|x| x.as_str())
164            .filter(|s| !s.is_empty())
165            .map(String::from)
166            .ok_or_else(|| format!("RBAC grants[{idx}].{k} 必须是非空字符串"))
167    };
168    let role = get_str("role")?;
169    let model = get_str("model")?;
170
171    let actions_raw = o
172        .get("actions")
173        .and_then(|x| x.as_array())
174        .ok_or_else(|| format!("RBAC grants[{idx}].actions 必须是非空数组"))?;
175    if actions_raw.is_empty() {
176        return Err(format!(
177            "RBAC grants[{idx}].actions 不能为空(不授权请移除该 grant)"
178        ));
179    }
180    let mut actions: Vec<String> = Vec::new();
181    for a in actions_raw {
182        let s = a
183            .as_str()
184            .ok_or_else(|| format!("RBAC grants[{idx}].actions 含非字符串项 {a}"))?;
185        let expanded: &[&str] = match s {
186            "read" => &["read"],
187            "insert" => &["insert"],
188            "update" => &["update"],
189            "remove" => &["remove"],
190            "write" => &["insert", "update", "remove"],
191            other => {
192                return Err(format!(
193                    "RBAC grants[{idx}].actions 含非法值 \"{other}\"(仅支持 read/insert/update/remove/write)"
194                ))
195            }
196        };
197        for e in expanded {
198            if !actions.iter().any(|x| x == e) {
199                actions.push((*e).to_string());
200            }
201        }
202    }
203
204    let parse_fields = |k: &str| -> Result<Option<HashSet<String>>, String> {
205        match o.get(k) {
206            None | Some(Value::Null) => Ok(None),
207            Some(Value::Array(fs)) => {
208                if fs.is_empty() {
209                    return Err(format!(
210                        "RBAC grants[{idx}].{k} 不能为空数组(全禁请移除该 grant)"
211                    ));
212                }
213                let mut set = HashSet::new();
214                for f in fs {
215                    let s = f.as_str().filter(|s| !s.is_empty()).ok_or_else(|| {
216                        format!("RBAC grants[{idx}].{k} 含非法项 {f}(须非空字符串)")
217                    })?;
218                    set.insert(s.to_string());
219                }
220                Ok(Some(set))
221            }
222            Some(other) => Err(format!(
223                "RBAC grants[{idx}].{k} 必须是字符串数组,实际 {other}"
224            )),
225        }
226    };
227    let read_fields = parse_fields("readFields")?;
228    let write_fields = parse_fields("writeFields")?;
229
230    let owner_only = o.get("ownerOnly").map(is_truthy).unwrap_or(false);
231
232    // 第一批仅等值标量条件:键非空且不得以 "$" 开头(操作符/逻辑组禁用),
233    // 值必须是标量——超出形态显式 Err,禁运行时静默失配
234    let condition = match o.get("condition") {
235        None | Some(Value::Null) => None,
236        Some(cm @ Value::Object(_)) => {
237            let entries = cm.as_object().expect("已按 Object 匹配,此处必为对象");
238            for (k, val) in entries {
239                if k.is_empty() || k.starts_with('$') {
240                    return Err(format!(
241                        "RBAC grants[{idx}].condition 键 \"{k}\" 非法(第一批仅支持等值字段条件,键不得以 $ 开头)"
242                    ));
243                }
244                if !val.is_boolean() && !val.is_number() && !val.is_string() && !val.is_null() {
245                    return Err(format!(
246                        "RBAC grants[{idx}].condition[\"{k}\"] 必须是标量(string/number/bool/null),实际 {val}"
247                    ));
248                }
249            }
250            Some(cm.clone())
251        }
252        Some(other) => {
253            return Err(format!(
254                "RBAC grants[{idx}].condition 必须是对象(等值条件),实际 {other}"
255            ))
256        }
257    };
258
259    Ok(Grant {
260        role,
261        model,
262        actions,
263        read_fields,
264        write_fields,
265        owner_only,
266        condition,
267    })
268}
269
270// ─── 判决 ────────────────────────────────────────────────────
271
272/// 有效角色集(对齐 `permission::evaluate`:`roles` 为空回落单 `role`)
273fn effective_roles(c: &Context) -> Vec<String> {
274    match &c.roles {
275        Some(r) if !r.is_empty() => r.clone(),
276        _ => vec![c.role.clone().unwrap_or_default()],
277    }
278}
279
280/// 核心判决:`None` = RBAC 不介入(无 ctx / internal / 豁免角色 / overlay 未覆盖 /
281/// enforce 未受管)。enforce 受管但 matched 为空 → `Some`(全 deny),这是
282/// default deny 的承载点。
283pub fn decide(
284    policy: &RbacPolicy,
285    exempt_roles: &[String],
286    ctx: Option<&Context>,
287    model: &str,
288) -> Option<ModelDecision> {
289    let c = ctx?;
290    if c.internal {
291        return None;
292    }
293    let roles = effective_roles(c);
294    if roles.iter().any(|r| exempt_roles.iter().any(|x| x == r)) {
295        return None;
296    }
297
298    let matched: Vec<&Grant> = policy
299        .grants
300        .iter()
301        .filter(|g| roles.iter().any(|r| r == &g.role) && (g.model == model || g.model == "*"))
302        .collect();
303
304    match policy.mode {
305        RbacMode::Overlay if matched.is_empty() => return None,
306        RbacMode::Enforce => {
307            let managed = roles.iter().any(|r| policy.managed_roles.contains(r));
308            if !managed {
309                return None;
310            }
311        }
312        _ => {}
313    }
314
315    // 字段集:grant 间 OR 语义——任一允许该动作的 grant 未声明字段集 = 授予全字段
316    // (整体不收紧);全部声明了才取并集收紧
317    let mut allowed_actions = HashSet::new();
318    let mut read_all = false;
319    let mut read_union: HashSet<String> = HashSet::new();
320    let mut write_all = false;
321    let mut write_union: HashSet<String> = HashSet::new();
322    for g in &matched {
323        for a in &g.actions {
324            allowed_actions.insert(a.clone());
325        }
326        if g.actions.iter().any(|a| a == "read") {
327            match &g.read_fields {
328                None => read_all = true,
329                Some(s) => read_union.extend(s.iter().cloned()),
330            }
331        }
332        if g.actions
333            .iter()
334            .any(|a| a == "insert" || a == "update" || a == "remove")
335        {
336            match &g.write_fields {
337                None => write_all = true,
338                Some(s) => write_union.extend(s.iter().cloned()),
339            }
340        }
341    }
342    let read_fields = if read_all || read_union.is_empty() {
343        None
344    } else {
345        Some(read_union)
346    };
347    let write_fields = if write_all || write_union.is_empty() {
348        None
349    } else {
350        Some(write_union)
351    };
352    Some(ModelDecision {
353        allowed_actions,
354        read_fields,
355        write_fields,
356    })
357}
358
359// ─── 叠加原语(registry.rbac() == None 时全部直通) ──────────
360
361/// 表级读判定(叠加在 `can_read_schema` 之后)
362pub fn ensure_read(
363    registry: &Registry,
364    schema: &Schema,
365    ctx: Option<&Context>,
366) -> Result<(), String> {
367    let Some(p) = registry.rbac() else {
368        return Ok(());
369    };
370    match decide(p, &registry.role_rules().exempt_roles, ctx, &schema.name) {
371        None => Ok(()),
372        Some(d) if d.allowed_actions.contains("read") => Ok(()),
373        Some(_) => Err(deny_msg("read", &schema.name)),
374    }
375}
376
377/// 表级写判定(叠加在 `can_write_schema` 之后;upsert 路径裁定按 Insert 判)
378pub fn ensure_write(
379    registry: &Registry,
380    schema: &Schema,
381    ctx: Option<&Context>,
382    action: WriteAction,
383) -> Result<(), String> {
384    let Some(p) = registry.rbac() else {
385        return Ok(());
386    };
387    match decide(p, &registry.role_rules().exempt_roles, ctx, &schema.name) {
388        None => Ok(()),
389        Some(d) if d.allowed_actions.contains(action.as_str()) => Ok(()),
390        Some(_) => Err(deny_msg(action.as_str(), &schema.name)),
391    }
392}
393
394/// 探针重入判决(单条 update/remove 的 ownerOnly / condition 命中判定)。
395///
396/// grant 间 OR 语义:任一允许该动作的 grant 无行限制 → 放行;有行限制且命中
397/// doc → 放行;全部不命中 → 拒绝(杜绝静默 0 行,对齐 no-error-masking)。
398pub fn ensure_write_on_doc(
399    registry: &Registry,
400    schema: &Schema,
401    ctx: Option<&Context>,
402    action: WriteAction,
403    doc: &Value,
404) -> Result<(), String> {
405    let Some(p) = registry.rbac() else {
406        return Ok(());
407    };
408    let Some(c) = ctx else { return Ok(()) };
409    if c.internal {
410        return Ok(());
411    }
412    let Some(d) = decide(
413        p,
414        &registry.role_rules().exempt_roles,
415        Some(c),
416        &schema.name,
417    ) else {
418        return Ok(());
419    };
420    if !d.allowed_actions.contains(action.as_str()) {
421        return Err(deny_msg(action.as_str(), &schema.name));
422    }
423    let roles = effective_roles(c);
424    let hit = p.grants.iter().any(|g| {
425        g.actions.iter().any(|a| a == action.as_str())
426            && (g.model == schema.name || g.model == "*")
427            && roles.iter().any(|r| r == &g.role)
428            && match grant_row_restriction(g, c) {
429                None => true,
430                Some(cond) => doc_matches_eq(doc, &cond),
431            }
432    });
433    if hit {
434        Ok(())
435    } else {
436        Err(format!(
437            "ERR_PERMISSION:RBAC:角色对 [{}] 的 [{}] 限于本人/条件内文档",
438            schema.name,
439            action.as_str()
440        ))
441    }
442}
443
444/// 行级条件(grant 间 OR 合并):RBAC 未生效 / 存在无行限制的匹配 grant →
445/// `None`(不收紧);否则各 grant 行限制取 `$or` 串接。
446/// `action` ∈ {"read", "update", "remove"}(insert 无行级语义,调用方不传)。
447pub fn row_condition(
448    registry: &Registry,
449    schema: &Schema,
450    ctx: Option<&Context>,
451    action: &str,
452) -> Option<Value> {
453    let p = registry.rbac()?;
454    let c = ctx?;
455    if c.internal {
456        return None;
457    }
458    decide(
459        p,
460        &registry.role_rules().exempt_roles,
461        Some(c),
462        &schema.name,
463    )?;
464    let roles = effective_roles(c);
465    let mut conds: Vec<Value> = Vec::new();
466    for g in p.grants.iter().filter(|g| {
467        g.actions.iter().any(|a| a == action)
468            && (g.model == schema.name || g.model == "*")
469            && roles.iter().any(|r| r == &g.role)
470    }) {
471        match grant_row_restriction(g, c) {
472            None => return None, // 任一 grant 无行限制 → 无限制
473            Some(v) => conds.push(v),
474        }
475    }
476    match conds.len() {
477        0 => None,
478        1 => Some(conds.pop().unwrap_or(Value::Null)),
479        _ => Some(json!({ "$or": conds })),
480    }
481}
482
483/// 在静态引擎产出的 owner 条件(`merge_owner_condition` 结果)之上叠加 RBAC 行
484/// 条件:两引擎间 `$and`(deny-wins)。任一为 `None` → 取另一者;空对象视为无条件。
485pub fn merge_row_condition(
486    registry: &Registry,
487    schema: &Schema,
488    ctx: Option<&Context>,
489    action: &str,
490    base: Option<Value>,
491) -> Option<Value> {
492    let meaningful = |v: Option<Value>| -> Option<Value> {
493        v.filter(|x| x.as_object().map(|o| !o.is_empty()).unwrap_or(false))
494    };
495    let rbac_cond = row_condition(registry, schema, ctx, action);
496    match (meaningful(base), rbac_cond) {
497        (None, r) => r,
498        (b, None) => b,
499        (Some(b), Some(r)) => Some(json!({ "$and": [b, r] })),
500    }
501}
502
503/// 字段读交集:`base`(静态 `get_readable_fields` 结果)与 RBAC readFields 取交。
504/// `_id` 随 base 保留(文档标识豁免,同 `filter_writable_data` 的 D-03 理由)。
505pub fn overlay_readable_fields(
506    registry: &Registry,
507    model: &str,
508    ctx: Option<&Context>,
509    base: Option<HashSet<String>>,
510) -> Option<HashSet<String>> {
511    let Some(p) = registry.rbac() else {
512        return base;
513    };
514    let Some(d) = decide(p, &registry.role_rules().exempt_roles, ctx, model) else {
515        return base;
516    };
517    let Some(rf) = &d.read_fields else {
518        return base;
519    };
520    match base {
521        None => {
522            let mut s: HashSet<String> = rf.clone();
523            s.insert("_id".to_string());
524            Some(s)
525        }
526        Some(b) => {
527            let keep_id = b.contains("_id");
528            let mut s: HashSet<String> = b.intersection(rf).cloned().collect();
529            if keep_id {
530                s.insert("_id".to_string());
531            }
532            Some(s)
533        }
534    }
535}
536
537/// 字段写交集(语义同 [`overlay_readable_fields`],作用于 writeFields)
538pub fn overlay_writable_fields(
539    registry: &Registry,
540    model: &str,
541    ctx: Option<&Context>,
542    base: Option<HashSet<String>>,
543) -> Option<HashSet<String>> {
544    let Some(p) = registry.rbac() else {
545        return base;
546    };
547    let Some(d) = decide(p, &registry.role_rules().exempt_roles, ctx, model) else {
548        return base;
549    };
550    let Some(wf) = &d.write_fields else {
551        return base;
552    };
553    match base {
554        None => {
555            let mut s: HashSet<String> = wf.clone();
556            s.insert("_id".to_string());
557            Some(s)
558        }
559        Some(b) => {
560            let keep_id = b.contains("_id");
561            let mut s: HashSet<String> = b.intersection(wf).cloned().collect();
562            if keep_id {
563                s.insert("_id".to_string());
564            }
565            Some(s)
566        }
567    }
568}
569
570/// 写入数据过滤(RBAC 感知版,供写路径调用点整体替换
571/// `permission::filter_writable_data`):可写字段 = 静态 writable ∩ RBAC writeFields。
572/// 与 [`crate::permission::filter_writable_data`] 逐行同构(`_id` 豁免、点号按 root),
573/// 仅集合来源叠加 RBAC 交集——静态引擎文件零改动。
574pub fn filter_writable_data_overlay(
575    registry: &Registry,
576    schema: &Schema,
577    ctx: Option<&Context>,
578    data: &Value,
579) -> Value {
580    if registry.rbac().is_none() {
581        return crate::permission::filter_writable_data(registry.role_rules(), schema, ctx, data);
582    }
583    let base = get_writable_fields(registry.role_rules(), schema, ctx);
584    let writable = overlay_writable_fields(registry, &schema.name, ctx, base);
585    let Some(writable) = writable else {
586        return data.clone();
587    };
588    let Some(obj) = data.as_object() else {
589        return data.clone();
590    };
591    let mut result = Map::new();
592    for (key, val) in obj {
593        let root = key.split('.').next().unwrap_or(key);
594        if key == "_id" || writable.contains(root) {
595            result.insert(key.clone(), val.clone());
596        }
597    }
598    Value::Object(result)
599}
600
601/// 单字段可读判定(RBAC 感知版):静态 `is_field_readable` ∧ RBAC readFields。
602/// 点号路径按 root 字段判定(与静态语义一致);`ctx=None` / internal / RBAC 未生效 → 放行。
603pub fn is_field_readable_overlay(
604    registry: &Registry,
605    schema: &Schema,
606    ctx: Option<&Context>,
607    field: &str,
608) -> bool {
609    if !crate::permission::is_field_readable(registry.role_rules(), schema, ctx, field) {
610        return false;
611    }
612    let Some(p) = registry.rbac() else {
613        return true;
614    };
615    let Some(c) = ctx else { return true };
616    if c.internal {
617        return true;
618    }
619    let Some(d) = decide(
620        p,
621        &registry.role_rules().exempt_roles,
622        Some(c),
623        &schema.name,
624    ) else {
625        return true;
626    };
627    match &d.read_fields {
628        None => true,
629        Some(rf) => rf.contains(field.split('.').next().unwrap_or(field)),
630    }
631}
632
633/// 探针命令需额外投影的字段(单条 update/remove 的 RBAC 行级判定依赖):
634/// 各 grant condition 的字段键(ownerOnly 的 `createdBy` 由调用方静态并入)。
635/// 无 RBAC 行级限制 → 空表(调用方维持既有 `{_id, createdBy}` 投影)。
636pub fn probe_condition_fields(
637    registry: &Registry,
638    schema: &Schema,
639    ctx: Option<&Context>,
640    action: &str,
641) -> Vec<String> {
642    let Some(p) = registry.rbac() else {
643        return Vec::new();
644    };
645    let Some(c) = ctx else { return Vec::new() };
646    if c.internal {
647        return Vec::new();
648    }
649    if decide(
650        p,
651        &registry.role_rules().exempt_roles,
652        Some(c),
653        &schema.name,
654    )
655    .is_none()
656    {
657        return Vec::new();
658    }
659    let roles = effective_roles(c);
660    let mut fields = Vec::new();
661    for g in p.grants.iter().filter(|g| {
662        g.actions.iter().any(|a| a == action)
663            && (g.model == schema.name || g.model == "*")
664            && roles.iter().any(|r| r == &g.role)
665    }) {
666        if let Some(cond) = &g.condition {
667            if let Some(obj) = cond.as_object() {
668                for k in obj.keys() {
669                    if !fields.iter().any(|x: &String| x == k) {
670                        fields.push(k.clone());
671                    }
672                }
673            }
674        }
675    }
676    fields
677}
678
679// ─── 内部工具 ────────────────────────────────────────────────
680
681/// 单 grant 的行限制:`None` = 无限制;`ownerOnly` → `{createdBy: userId}`;
682/// `condition` → 原样;两者并存 → `$and`。`ownerOnly` 但 userId 缺失 → 恒假哨兵
683/// (禁当 `None`——那是静默放大)。
684fn grant_row_restriction(g: &Grant, c: &Context) -> Option<Value> {
685    if !g.owner_only && g.condition.is_none() {
686        return None;
687    }
688    let owner = if g.owner_only {
689        let uid = c.user_id.clone().unwrap_or_default();
690        if uid.is_empty() {
691            // 恒假条件:__rbac_impossible__ 不可能在文档出现 → doc_matches_eq 恒 false
692            Some(json!({ "__rbac_impossible__": true }))
693        } else {
694            Some(json!({ "createdBy": uid }))
695        }
696    } else {
697        None
698    };
699    match (owner, g.condition.clone()) {
700        (None, cond) => cond,
701        (owner, None) => owner,
702        (Some(o), Some(cond)) => Some(json!({ "$and": [o, cond] })),
703    }
704}
705
706/// 等值条件命中判定(`cond` 为 `{字段: 标量}`,解析期已保证);哨兵键恒不命中
707fn doc_matches_eq(doc: &Value, cond: &Value) -> bool {
708    let (Some(obj), Some(cond_map)) = (doc.as_object(), cond.as_object()) else {
709        return false;
710    };
711    cond_map
712        .iter()
713        .all(|(k, v)| obj.get(k).map(|dv| dv == v).unwrap_or(false))
714}