Skip to main content

vigil_redaction/
lang_hint.rs

1//! v0.10 Sprint 2 — typed `LanguageHint` wrapper(Decision A-prime,Codex `019dfdab`)。
2//!
3//! **触发**:v0.9 Decision D=C 锁定 production firewall **不接** lang;但 SDK consumer
4//! 仍可能想 reproducible 走 lang-aware path。直接传 `Option<&str>` 不够 — 没有
5//! provenance / confidence 信息,caller 无法判 trust;启发式 detect 误判(短文本
6//! 17/45 EU sample,见 `feedback_lang_review_authoritative`)若混入 production 决策
7//! 路径会静默 threshold 误路由。
8//!
9//! **A-prime 设计**(本模块):typed `LanguageHint { lang, source, confidence }`,
10//! 强制 caller 表达**信息来源** + **可信度**;low-confidence 一律 fail-closed 退化
11//! baseline。
12//!
13//! **当前 v0.10 范围**:
14//! - typed wrapper + 工厂方法 + `into_lang_str()` 转 `Option<String>`(low-conf → None)
15//! - `scan_text_with_engine_with_hint(text, engine, hint)` 浅级 wrapper(SDK 友好)
16//! - SDK re-export(`vigil_sdk::{LanguageHint, LangHintSource}`)
17//! - **不**接 Firewall::evaluate(D=C 锁定;v0.11+ 视用户反馈)
18
19use crate::engine::RedactionEngine;
20use crate::scan::{scan_text_with_engine_with_lang, RedactionResult, ScanError};
21
22/// **v0.10 Sprint 2** — `LanguageHint` 信息来源 enum。
23///
24/// caller 必须表明 lang 字符串来自哪类信息 — 影响 audit trail + 可信度判定。
25/// `into_lang_str()` 决策时按 source × confidence 综合判 fail-closed 退化:
26/// - `CallerProvided`:caller 明确决策(如用户 locale 设置 / 业务上下文)— 高信任
27/// - `FixtureExperimental`:fixture / 测试 / release-gate 模式 — 中信任(仅非 production)
28/// - `Heuristic`:启发式 detect(unicode + 关键词)— 低信任,advisory only
29///
30/// **SemVer**:`#[non_exhaustive]` — 未来加 source(如 `UserAgent` / `MlClassifier`)
31/// 不破。
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
33#[non_exhaustive]
34pub enum LangHintSource {
35    /// caller 显式传入(可信度最高;典型 user locale / 业务上下文)
36    CallerProvided,
37    /// fixture / 测试 / release-gate 场景(权威源,但仅非 production)
38    FixtureExperimental,
39    /// 启发式 lang detect(unicode 字符集 + 关键词;仅 advisory,不可作权威决策)
40    Heuristic,
41}
42
43impl LangHintSource {
44    /// 该 source 是否本质可信(进 production 决策)。
45    /// `Heuristic` 始终 false(`feedback_lang_review_authoritative` 约束)。
46    pub fn is_trusted(&self) -> bool {
47        // crate 内部穷举所有 variant(LangHintSource 定义在本 module);加新 variant
48        // 时 compiler 会 force update 本 match — 比 runtime `_ => false` fail-closed
49        // 兜底更早 catch。外部 SDK consumer 因 #[non_exhaustive] 必须写 `_` 兜底。
50        match self {
51            LangHintSource::CallerProvided => true,
52            LangHintSource::FixtureExperimental => true,
53            LangHintSource::Heuristic => false,
54        }
55    }
56}
57
58/// **v0.10 Sprint 2** — typed lang hint wrapper(Decision A-prime)。
59///
60/// **设计意图**:替代裸 `Option<&str>` 给 SDK consumer / 未来 firewall lang-aware
61/// 路径用;**强制 provenance + confidence**,low-conf 一律 fail-closed 退化。
62///
63/// **典型用法**(SDK consumer):
64/// ```rust
65/// use vigil_redaction::lang_hint::{LanguageHint, LangHintSource};
66///
67/// // caller 知道用户 locale → 高信任
68/// let hint = LanguageHint::caller_provided("de");
69///
70/// // 启发式 detect → advisory,low-conf 自动 fail-closed
71/// let hint = LanguageHint::heuristic("de", 0.4);  // confidence 太低,into_lang_str → None
72/// ```
73///
74/// **SemVer**:`#[non_exhaustive]` — 未来加字段(如 `audit_id` / `provider_chain`)
75/// 不破;pub fields 可读,struct literal 构造仅 crate 内可用。
76#[derive(Debug, Clone)]
77#[non_exhaustive]
78pub struct LanguageHint {
79    /// ISO 639-1 lowercase(`"en"` / `"de"` / `"it"` / `"fr"` / ...);与 fixture
80    /// lang 字段对齐
81    pub lang: String,
82    /// 信息来源(用于 audit trail + 可信度决策)
83    pub source: LangHintSource,
84    /// 置信度 [0.0, 1.0];low-conf(< 0.5)`into_lang_str` 一律返 None(fail-closed
85    /// 退化 baseline path,避免误降 policy)
86    pub confidence: f32,
87}
88
89/// `into_lang_str` 用的 fail-closed 阈值。confidence < 此值 → None(走 baseline)。
90///
91/// **0.5 选择理由**:
92/// - 启发式 detect 在短文本无关键词时常返 0.3-0.4(`feedback_lang_review_authoritative`)
93/// - caller_provided / fixture 一般 1.0(用户/fixture 权威源)
94/// - 0.5 阈值切开"模糊 advisory" vs "可决策" 两类
95pub const LANG_HINT_TRUSTED_CONFIDENCE: f32 = 0.5;
96
97impl LanguageHint {
98    /// caller 显式传入(`CallerProvided` source,confidence = 1.0)。
99    /// 适用:用户 locale 设置 / 业务上下文 / 已校验输入。
100    pub fn caller_provided(lang: impl Into<String>) -> Self {
101        Self {
102            lang: lang.into(),
103            source: LangHintSource::CallerProvided,
104            confidence: 1.0,
105        }
106    }
107
108    /// fixture / 测试场景(`FixtureExperimental` source,confidence = 1.0)。
109    /// 仅非 production;典型 release-gate / spike-gate / fixture lang 字段透传。
110    pub fn fixture(lang: impl Into<String>) -> Self {
111        Self {
112            lang: lang.into(),
113            source: LangHintSource::FixtureExperimental,
114            confidence: 1.0,
115        }
116    }
117
118    /// 启发式 detect(`Heuristic` source,caller 给 confidence)。
119    /// **决策不可信**:即使 confidence = 1.0,`is_trusted` 返 false(由
120    /// `LangHintSource` 静态判定);仅 advisory(diagnose / suggestion UI)。
121    /// confidence < 0.5 时 `into_lang_str` 也返 None。
122    pub fn heuristic(lang: impl Into<String>, confidence: f32) -> Self {
123        Self {
124            lang: lang.into(),
125            source: LangHintSource::Heuristic,
126            confidence: confidence.clamp(0.0, 1.0),
127        }
128    }
129
130    /// **v0.10 Sprint 6** — 启发式 lang detect(advisory only)。
131    ///
132    /// 调用 [`detect_lang_heuristic`] 内部启发式(unicode 字符集 + 关键词),返
133    /// `LanguageHint` with `LangHintSource::Heuristic`。
134    /// **始终返 Heuristic** — `lang_str()` **永返 None**,无法触发 firewall 决策路径。
135    ///
136    /// 适用场景:
137    /// - fixture lang 字段标注辅助(P1.0 启发式同口径,但用 Rust 端版本)
138    /// - SDK consumer 诊断 UI / 建议性显示
139    /// - **永不**作 production 决策权威(`feedback_lang_review_authoritative` 约束)
140    pub fn detect(text: &str) -> Self {
141        detect_lang_heuristic(text)
142    }
143
144    /// **fail-closed 转换**:返 `Option<&str>`(走 `scan_text_with_engine_with_lang`)。
145    ///
146    /// 决策规则:
147    /// - confidence < `LANG_HINT_TRUSTED_CONFIDENCE`(0.5)→ `None`(退化 baseline)
148    /// - `LangHintSource::Heuristic` → `None`(无论 confidence,启发式不可信)
149    /// - 其他(`CallerProvided` / `FixtureExperimental` + confidence ≥ 0.5)→ `Some(&lang)`
150    ///
151    /// 这是 D=C 决议下的 SDK 边界 — caller 即使传 `Heuristic`,也**不会**触发
152    /// lang-conditional threshold(防 production 误降 policy)。
153    pub fn lang_str(&self) -> Option<&str> {
154        if !self.source.is_trusted() {
155            return None;
156        }
157        if self.confidence < LANG_HINT_TRUSTED_CONFIDENCE {
158            return None;
159        }
160        Some(self.lang.as_str())
161    }
162}
163
164/// **v0.10 Sprint 6** — 独立函数版启发式 lang detect(advisory only)。
165///
166/// **算法**(与 `scripts/spike-p3/analyze_fixture_distribution.py::detect_lang` 同口径):
167/// 1. unicode 字符集(明确特征,confidence 高)
168///    - 韩文 Hangul ≥ 2 字符 → `("ko", 0.9)`
169///    - 日文 Hiragana/Katakana ≥ 2 → `("ja", 0.9)`
170///    - 中文 CJK Han ≥ 2 → `("zh", 0.85)`(可能与 ja 共有汉字,故 confidence 略低)
171/// 2. 拉丁语系关键词(明确特征,confidence 中-高)
172///    - 德语关键词命中 → `("de", 0.7)`
173///    - 法语关键词命中 → `("fr", 0.7)`
174///    - 意大利语关键词命中 → `("it", 0.7)`
175///    - 西班牙语关键词命中 → `("es", 0.7)`
176/// 3. 重音字符 fallback(模糊,confidence 低)
177///    - `äöüß` 字符 → `("de", 0.5)`
178///    - 其他西欧重音字符 → `("fr", 0.4)`(默认归 fr,西欧最广)
179/// 4. 无特征 → `("en", 0.3)`(短文本无关键词时低 confidence,fail-closed 退化)
180///
181/// **fail-closed**:无论返哪个 lang,`source = Heuristic` 永远不可作 production 决策。
182/// caller 用 `into_lang_str()` / `lang_str()` 始终返 None(except low-conf <0.5 也返 None)。
183pub fn detect_lang_heuristic(text: &str) -> LanguageHint {
184    use LangHintSource::Heuristic;
185
186    // CJK 字符集(明确特征)
187    let mut cjk = 0;
188    let mut hira = 0;
189    let mut kata = 0;
190    let mut hangul = 0;
191    for ch in text.chars() {
192        let cp = ch as u32;
193        if (0x4E00..=0x9FFF).contains(&cp) {
194            cjk += 1;
195        } else if (0x3040..=0x309F).contains(&cp) {
196            hira += 1;
197        } else if (0x30A0..=0x30FF).contains(&cp) {
198            kata += 1;
199        } else if (0xAC00..=0xD7AF).contains(&cp) {
200            hangul += 1;
201        }
202    }
203    if hangul >= 2 {
204        return LanguageHint {
205            lang: "ko".to_string(),
206            source: Heuristic,
207            confidence: 0.9,
208        };
209    }
210    if hira + kata >= 2 {
211        return LanguageHint {
212            lang: "ja".to_string(),
213            source: Heuristic,
214            confidence: 0.9,
215        };
216    }
217    if cjk >= 2 {
218        return LanguageHint {
219            lang: "zh".to_string(),
220            source: Heuristic,
221            confidence: 0.85,
222        };
223    }
224
225    // 关键词命中(中-高 confidence)
226    let low = text.to_lowercase();
227    let de_hints = [
228        "herr ",
229        "frau ",
230        "straße",
231        "strasse",
232        "gmbh",
233        "münchen",
234        "berlin",
235        "hamburg",
236        "köln",
237        "müller",
238        "schmidt",
239        " und ",
240        "ich bin",
241        "guten tag",
242        "bitte",
243        "webseite",
244        "konto",
245        "verwendet",
246        "verfügbar",
247        "geboren am",
248    ];
249    let fr_hints = [
250        "monsieur",
251        "madame",
252        "bonjour",
253        "paris",
254        "lyon",
255        "marseille",
256        "merci",
257        "veuillez",
258        "envoyer",
259        "visitez",
260        "né le ",
261        "née le ",
262        "téléphone",
263        "adresse:",
264        " et ",
265    ];
266    let it_hints = [
267        "signor",
268        "signora",
269        "roma",
270        "milano",
271        "napoli",
272        "bologna",
273        "buongiorno",
274        "grazie",
275        "contatta",
276        "telefono",
277        "nato il",
278        "nata il",
279        "codice fiscale",
280        "visita ",
281        "indirizzo:",
282        " e ",
283    ];
284    let es_hints = [
285        "señor",
286        "señora",
287        "calle ",
288        "madrid",
289        "barcelona",
290        "valencia",
291        "sevilla",
292        "gracias",
293        "por favor",
294        "avenida",
295    ];
296    if de_hints.iter().any(|h| low.contains(h)) {
297        return LanguageHint {
298            lang: "de".to_string(),
299            source: Heuristic,
300            confidence: 0.7,
301        };
302    }
303    if fr_hints.iter().any(|h| low.contains(h)) {
304        return LanguageHint {
305            lang: "fr".to_string(),
306            source: Heuristic,
307            confidence: 0.7,
308        };
309    }
310    if it_hints.iter().any(|h| low.contains(h)) {
311        return LanguageHint {
312            lang: "it".to_string(),
313            source: Heuristic,
314            confidence: 0.7,
315        };
316    }
317    if es_hints.iter().any(|h| low.contains(h)) {
318        return LanguageHint {
319            lang: "es".to_string(),
320            source: Heuristic,
321            confidence: 0.7,
322        };
323    }
324
325    // 字符集 fallback(模糊;feedback_lang_review_authoritative 警告:短文本误判 17/45)
326    let has_de_chars = text
327        .chars()
328        .any(|c| matches!(c, 'ä' | 'ö' | 'ü' | 'ß' | 'Ä' | 'Ö' | 'Ü'));
329    if has_de_chars {
330        return LanguageHint {
331            lang: "de".to_string(),
332            source: Heuristic,
333            confidence: 0.45, // < TRUSTED 0.5(fail-closed 退化 baseline)
334        };
335    }
336    let has_western_accent = text.chars().any(|c| {
337        matches!(
338            c,
339            'à' | 'â'
340                | 'ç'
341                | 'é'
342                | 'è'
343                | 'ê'
344                | 'ë'
345                | 'î'
346                | 'ï'
347                | 'ô'
348                | 'û'
349                | 'ù'
350                | 'À'
351                | 'É'
352                | 'È'
353                | 'Ê'
354                | 'Ô'
355        )
356    });
357    if has_western_accent {
358        return LanguageHint {
359            lang: "fr".to_string(),
360            source: Heuristic,
361            confidence: 0.4,
362        };
363    }
364
365    // 无特征 → en,低 confidence(fail-closed:lang_str 返 None)
366    LanguageHint {
367        lang: "en".to_string(),
368        source: Heuristic,
369        confidence: 0.3,
370    }
371}
372
373/// **v0.10 Sprint 2** — `scan_text_with_engine_with_hint` 浅级 wrapper。
374///
375/// SDK consumer 友好版 `scan_text_with_engine_with_lang`:接 typed
376/// [`LanguageHint`](Option),内部按 fail-closed 规则转 `Option<&str>`。
377///
378/// 等价于:
379/// ```ignore
380/// let lang = hint.and_then(|h| h.lang_str());
381/// scan_text_with_engine_with_lang(input, engine, lang)
382/// ```
383///
384/// 但 typed wrapper 让 caller 必须表达 source / confidence,**强制可解释 + 可
385/// 进 audit**;裸 `Option<&str>` 无 provenance,SDK consumer 易混淆来源。
386///
387/// **SemVer**:新公共 API,SemVer 安全(legacy `scan_text_with_engine_with_lang`
388/// 保留,未改签名)。
389pub fn scan_text_with_engine_with_hint(
390    input: &str,
391    engine: &dyn RedactionEngine,
392    hint: Option<&LanguageHint>,
393) -> Result<RedactionResult, ScanError> {
394    let lang = hint.and_then(|h| h.lang_str());
395    scan_text_with_engine_with_lang(input, engine, lang)
396}
397
398#[cfg(test)]
399mod tests {
400    use super::*;
401
402    #[test]
403    fn caller_provided_high_trust_returns_lang() {
404        let h = LanguageHint::caller_provided("de");
405        assert_eq!(h.source, LangHintSource::CallerProvided);
406        assert!((h.confidence - 1.0).abs() < f32::EPSILON);
407        assert_eq!(h.lang_str(), Some("de"));
408    }
409
410    #[test]
411    fn fixture_experimental_returns_lang() {
412        let h = LanguageHint::fixture("it");
413        assert_eq!(h.source, LangHintSource::FixtureExperimental);
414        assert_eq!(h.lang_str(), Some("it"));
415    }
416
417    /// **关键**:Heuristic source 即使 confidence=1.0 也返 None(不可信任)。
418    /// 这是 D=C 决议下 SDK 边界;`feedback_lang_review_authoritative` 约束。
419    #[test]
420    fn heuristic_always_returns_none_even_max_confidence() {
421        let h = LanguageHint::heuristic("de", 1.0);
422        assert_eq!(h.source, LangHintSource::Heuristic);
423        assert_eq!(
424            h.lang_str(),
425            None,
426            "Heuristic source 即使 confidence=1.0 也必须返 None(决策不可信任)"
427        );
428    }
429
430    /// low confidence(< 0.5)即使 trusted source 也返 None(fail-closed)
431    #[test]
432    fn low_confidence_returns_none_even_caller_provided() {
433        let mut h = LanguageHint::caller_provided("de");
434        h.confidence = 0.4; // 模拟 caller 自降信任
435        assert_eq!(
436            h.lang_str(),
437            None,
438            "confidence < 0.5 必须 fail-closed 返 None"
439        );
440    }
441
442    /// confidence clamp [0.0, 1.0]
443    #[test]
444    fn heuristic_confidence_clamp() {
445        let h_neg = LanguageHint::heuristic("de", -0.5);
446        assert!(h_neg.confidence >= 0.0);
447        let h_over = LanguageHint::heuristic("de", 2.0);
448        assert!(h_over.confidence <= 1.0);
449    }
450
451    /// LangHintSource non_exhaustive — caller 必 _ 通配
452    #[test]
453    #[allow(unreachable_patterns)]
454    fn lang_hint_source_non_exhaustive_match_compiles() {
455        let s = LangHintSource::CallerProvided;
456        let trusted = match s {
457            LangHintSource::CallerProvided => true,
458            LangHintSource::FixtureExperimental => true,
459            LangHintSource::Heuristic => false,
460            _ => false, // non_exhaustive 强制 _
461        };
462        assert!(trusted);
463    }
464
465    /// `is_trusted` 与 `lang_str` 决策一致(Heuristic / 未知 source 不可信)
466    #[test]
467    fn is_trusted_consistent_with_lang_str_decision() {
468        assert!(LangHintSource::CallerProvided.is_trusted());
469        assert!(LangHintSource::FixtureExperimental.is_trusted());
470        assert!(!LangHintSource::Heuristic.is_trusted());
471    }
472
473    /// scan_text_with_engine_with_hint 等价于 hint.lang_str() + scan_with_lang
474    #[test]
475    fn scan_with_hint_empty_input_fail_closed() {
476        let h = LanguageHint::caller_provided("de");
477        let r = scan_text_with_engine_with_hint("", &crate::engine::NoopEngine, Some(&h));
478        assert!(matches!(r, Err(ScanError::EmptyInput)));
479    }
480
481    /// hint = None 等价于 scan_text_with_engine(legacy)
482    #[test]
483    fn scan_with_hint_none_equivalent_to_legacy() {
484        let r = scan_text_with_engine_with_hint("hello", &crate::engine::NoopEngine, None)
485            .expect("non-empty");
486        // NoopEngine 返空 model findings;Hard rules 在 "hello" 上不命中
487        assert!(r.findings.is_empty(), "NoopEngine + 'hello' 无 finding");
488    }
489
490    // ─── v0.10 Sprint 6 — advisory lang detect 守门 ───
491
492    /// CJK 字符明确特征(高 confidence)
493    #[test]
494    fn detect_lang_zh_chinese() {
495        let h = detect_lang_heuristic("请联系王小明处理订单");
496        assert_eq!(h.lang, "zh");
497        assert_eq!(h.source, LangHintSource::Heuristic);
498        assert!(h.confidence >= 0.8);
499    }
500
501    #[test]
502    fn detect_lang_ja_japanese() {
503        let h = detect_lang_heuristic("田中太郎さんが昨日来ました");
504        assert_eq!(h.lang, "ja");
505        assert!(h.confidence >= 0.85);
506    }
507
508    #[test]
509    fn detect_lang_ko_korean() {
510        let h = detect_lang_heuristic("김민수 씨에게 연락하세요");
511        assert_eq!(h.lang, "ko");
512        assert!(h.confidence >= 0.85);
513    }
514
515    /// 拉丁语系关键词命中(中 confidence 0.7)
516    #[test]
517    fn detect_lang_de_keyword() {
518        let h = detect_lang_heuristic("Herr Schmidt arbeitet hier.");
519        assert_eq!(h.lang, "de");
520        assert!((h.confidence - 0.7).abs() < 0.01);
521    }
522
523    #[test]
524    fn detect_lang_fr_keyword() {
525        let h = detect_lang_heuristic("Monsieur Dupont travaille ici.");
526        assert_eq!(h.lang, "fr");
527    }
528
529    #[test]
530    fn detect_lang_it_keyword() {
531        let h = detect_lang_heuristic("Il signor Rossi lavora qui.");
532        assert_eq!(h.lang, "it");
533    }
534
535    /// 短文本无关键词 → en 低 confidence(fail-closed)
536    #[test]
537    fn detect_lang_short_text_low_confidence() {
538        let h = detect_lang_heuristic("John Smith works here.");
539        assert_eq!(h.lang, "en");
540        assert!(
541            h.confidence < LANG_HINT_TRUSTED_CONFIDENCE,
542            "短英文文本 confidence 必须 < 0.5(fail-closed 退化 baseline)"
543        );
544        assert_eq!(
545            h.lang_str(),
546            None,
547            "无论 lang 是什么,Heuristic source 永返 None"
548        );
549    }
550
551    /// **关键不变量**:即使 detect 命中明确语言(高 confidence),lang_str 仍返 None
552    /// (Heuristic source 永不可信任 — D=C 锁定下的 SDK 边界)
553    #[test]
554    fn detect_lang_high_confidence_still_not_trusted() {
555        let h = detect_lang_heuristic("田中太郎さんが昨日来ました");
556        assert!(h.confidence >= 0.85, "ja 应高 confidence");
557        assert_eq!(
558            h.lang_str(),
559            None,
560            "Heuristic source 即使 high confidence 也必须返 None(feedback_lang_review_authoritative)"
561        );
562    }
563
564    /// `LanguageHint::detect(text)` 等价独立函数
565    #[test]
566    fn language_hint_detect_method_equivalent_to_function() {
567        let text = "Herr Schmidt arbeitet hier.";
568        let from_method = LanguageHint::detect(text);
569        let from_fn = detect_lang_heuristic(text);
570        assert_eq!(from_method.lang, from_fn.lang);
571        assert_eq!(from_method.source, from_fn.source);
572        assert_eq!(from_method.confidence, from_fn.confidence);
573    }
574
575    /// 重音字符 fallback(de ä/ö/ü/ß)— 模糊但比 en fallback 好
576    #[test]
577    fn detect_lang_de_accent_fallback() {
578        // 短文本无关键词,但有 ß 字符
579        let h = detect_lang_heuristic("Herr ß test");
580        // 实际"Herr "命中关键词,先返 de;此 case 走关键词路径
581        assert_eq!(h.lang, "de");
582
583        // 真 fallback case:无关键词但有 ä/ö/ü/ß
584        let h2 = detect_lang_heuristic("würde");
585        assert_eq!(h2.lang, "de");
586        assert!(
587            h2.confidence < LANG_HINT_TRUSTED_CONFIDENCE,
588            "fallback 模糊路径 confidence 必须 < 0.5"
589        );
590    }
591
592    /// 与 fixture lang 字段(P1.0+ 人工核对权威)对比 — 启发式应**部分**命中,
593    /// 但**绝不能**作权威源(本测试文档化:detect 仅 advisory)。
594    #[test]
595    fn detect_lang_documented_as_advisory_not_authoritative() {
596        // 短文本,fixture 真值是 en,但启发式无关键词
597        let h = detect_lang_heuristic("John Smith works here.");
598        // 即使启发式判 en,confidence 仍低 → lang_str None,不影响 production 决策
599        assert_eq!(h.source, LangHintSource::Heuristic);
600        assert!(!h.source.is_trusted(), "Heuristic source 永远不可信任");
601    }
602}