OpenMed 隐私证据新鲜度门控:基于注入时钟与聚合报告的本地确定性发布检查
2026/9/19 22:45:57 网站建设 项目流程

OpenMed 隐私证据新鲜度门控:基于注入时钟与聚合报告的本地确定性发布检查

【免费下载链接】openmedLocal-first healthcare AI: clinical NER & HIPAA PII de-identification that runs 100% on-device. 2,200+ medical models, 21 languages, Apple MLX + Python, no cloud, no patient data leaving your network. Apache-2.0项目地址: https://gitcode.com/GitHub_Trending/ope/openmed

OpenMed 的隐私发布证据只有在"仍然处于产生它的策略有效期内"时才具备资格。本文将讲解openmed.compliance模块中的证据新鲜度门控(evidence freshness gate)机制:如何用强类型策略定义各类证据的最大年龄、如何注入确定性时钟完成可重放的评估、supersession 语义如何淘汰旧证据,以及为何报告只输出聚合计数而绝不触碰敏感字段。读完本文,你将能在自己的发布流程中直接使用EvidenceFreshnessPolicyEvidenceRecordevaluate_evidence_freshnessassert_evidence_freshness构建一个不依赖网络、可重放、隐私安全的发布检查。

新鲜度门控的定位:本地确定性检查,而非合规认证

在 OpenMed 的合规体系中,隐私发布证据(如去标识化发布报告、校准结果)只有在产生它的策略版本仍然生效、且证据年龄未超过策略设定的上限时,才能作为发布依据。核心实现位于 openmed/compliance/evidence_freshness.py,并通过 openmed/compliance/init.py 导出全部公共 API(含EvidenceFreshnessPolicyEvidenceRecordEvidenceFreshnessReportEvidenceFreshnessErrorevaluate_evidence_freshnessassert_evidence_freshness等)。

该门控具有三个关键边界:

  • 本地且确定性:评估器不会调用datetime.now(),也不会发起任何网络请求,评估时间完全由调用方注入,因此同一份证据、同一份策略、同一个时钟值必然得到同一个结论;
  • 只做技术门控:它不宣称底层证据"准确、完整、法律充分或临床安全",仅是发布流水线中的一个技术控制点;
  • 聚合输出:报告只包含策略版本、总数、通过/拒绝计数与确定性原因计数,不携带任何记录级引用或时间戳。

定义强类型的年龄策略

每种证据类型(evidence kind)都对应一个datetime.timedelta类型的最大年龄上限。类型被刻意保留为timedelta而非裸整数,这样调用方无法悄悄把单位从"天"改成"小时"或"秒"。策略版本号则进行精确字符串比较,不做任何网络版本发现。

from datetime import timedelta from openmed.compliance import EvidenceFreshnessPolicy policy = EvidenceFreshnessPolicy( policy_version="privacy-v2", age_limits={ "release": timedelta(days=30), "calibration": timedelta(days=7), }, )

通配符与优先级

当多种证据类型共享同一个年龄上限时,可使用通配符键"*"作为兜底;某个具体类型一旦在age_limits中出现,其精确条目永远优先于通配符。这一逻辑由max_age_for()实现(见 evidence_freshness.py):先查精确类型,未命中再回落到"*"

def max_age_for(self, evidence_type: str) -> timedelta | None: return self.age_limits.get( evidence_type, self.age_limits.get(_WILDCARD_LIMIT), )

构造期的强校验

从源码可以确认,策略构造做了三重约束:

  1. age_limits与旧命名max_age_by_type只能二选一传入,否则抛TypeError(evidence_freshness.py);
  2. 所有上限必须是datetime.timedelta且非负(_require_duration,见 evidence_freshness.py),传入30这样的裸整数会直接抛TypeError,测试 test_evidence_freshness.py 对此有专门覆盖;
  3. policy_version必须是非空的不透明令牌(opaque token),由_SAFE_TOKEN_RE^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$)校验(evidence_freshness.py)。

age_limits内部会被排序并包装为只读的MappingProxyType,保证策略在评估期间不可变,从而支持可重放性。此外还提供了from_mapping()类方法,允许从{"version": ..., "limits": ...}形式的映射构造策略(evidence_freshness.py),EvidenceAgePolicy则是面向发布规范术语的等价别名。

注入时钟进行可重放的评估

评估必须且只能注入一个时钟来源:as_of(显式评估时刻)、nowas_of的别名)或clock(可调用对象,或暴露now()方法的对象),三者只能提供一个,否则抛ValueError。这是刻意设计——省略时钟会被视为错误,而不是隐式读取宿主机的墙钟时间,从而让发布决策完全可重放。该约束由_resolve_evaluation_time()保证(evidence_freshness.py),测试也验证了"未提供或提供多个时钟来源都会报错"(test_evidence_freshness.py)。

from datetime import datetime, timezone from openmed.compliance import EvidenceRecord, evaluate_evidence_freshness as_of = datetime(2026, 8, 12, 12, 0, tzinfo=timezone.utc) evidence = [ EvidenceRecord( evidence_id="release-2026-08-11", evidence_type="release", generated_at=datetime(2026, 8, 11, 12, 0, tzinfo=timezone.utc), policy_version="privacy-v2", ) ] report = evaluate_evidence_freshness(evidence, policy, as_of=as_of) if not report.passed: raise RuntimeError(report.failure_message())

时间戳解析规则

_parse_datetime()(evidence_freshness.py)只接受**带时区(aware)**的时间:

  • 接受datetime对象与 ISO 8601 字符串,字符串末尾的Z会被规范化为+00:00
  • 无时区(naive)的时间戳一律视为无效;
  • 所有有效时间都会被转换到 UTC 统一比较;
  • 超出 UTC 可表示范围(如0001-01-01T00:00:00+01:00)的时间会被判定为invalid_timestamp,测试 test_evidence_freshness.py 对该边界有覆盖。

测试 test_evidence_freshness.py 还演示了可重放性:用as_of评估与用一个返回相同时刻的固定时钟对象评估,得到的报告to_dict()完全一致,且时钟对象恰好只被调用一次。

失败关闭(fail closed)清单

门控对以下所有情况一律拒绝通过(每条记录最多贡献一个主原因,保证计数稳定):

场景原因码
空输入(没有任何证据记录)missing_evidence
缺少evidence_idmissing_evidence_id
evidence_id非法(非不透明令牌)invalid_evidence_id
重复的evidence_idduplicate_evidence_id
缺少evidence_typemissing_evidence_type
evidence_type非法invalid_evidence_type
evidence_type未在策略中登记(含无通配符兜底)unknown_evidence_type
缺少时间戳missing_timestamp
时间戳非法(naive、畸形、越界)invalid_timestamp
时间戳晚于评估时刻(未来时间)future_timestamp
时间戳超出类型上限(已过期)expired_evidence
缺少policy_versionmissing_policy_version
policy_version非法invalid_policy_version
policy_version与策略版本不完全一致policy_mismatch
超会话引用非法invalid_supersession_link
记录已被更早/更新的记录取代superseded_evidence

全部原因码以常量形式定义在 evidence_freshness.py,并集中收在_REASON_CODES集合中用于报告校验。

边界规则:年龄恰好等于配置上限的证据仍然视为当前有效(as_of - generated_at > max_age才判定过期),测试 test_evidence_freshness.py 验证了 30 天整的证据通过、而 31 天的证据被判定为expired_evidence

记录构造与映射别名

EvidenceRecord除了直接构造,还接受一系列常见序列化别名:时间戳可以是timestamp/observed_at,类型可以是type/kind,ID 可以是id/record_id,策略可以是policy(evidence_freshness.py)。同时刻/同类型/同引用的别名参数会触发TypeError,避免歧义。from_mapping()只读取这些安全的描述字段,忽略所有其他映射字段——测试 test_evidence_freshness.py 表明即使记录中混入raw_sensitive_fixture这样的合成敏感值,也不会出现在任何序列化输出中。PrivacyEvidence是该类的语义化别名。

Supersession:新旧证据的取代语义

superseded_by表示某条证据记录不再具备资格;而一条替换记录可以用supersedes指向前一个不透明引用:

  • 若被引用的更早记录在本次评估的同一批输入中,它会被计为superseded_evidence
  • 若更早记录只存在于外部归档中(不在本次输入里),替换记录依然保持合格。
replacement = EvidenceRecord( evidence_id="release-2026-08-12", evidence_type="release", generated_at=as_of, policy_version="privacy-v2", supersedes="release-2026-08-11", )

实现上,evaluate_evidence_freshness()会先扫描全部记录建立evidence_id -> 索引列表的映射,再收集"被supersedes指向且出现在输入中"以及"被superseded_by标记"的记录 ID 集合,最后在逐条判定主原因时命中superseded_ids即返回SUPERSEDED_EVIDENCE(evidence_freshness.py)。

测试 test_evidence_freshness.py 完整验证了三态行为:旧记录与替换记录同批输入时旧记录被拒、仅提交替换记录时整体通过、显式superseded_by也能单独触发拒绝。另外:

  • 引用之间仅作为不透明令牌比较,不会做语义解析或网络查询;
  • 超会话引用本身也要通过_SAFE_TOKEN_RE校验,非法令牌(如含空格)会触发invalid_supersession_link(测试见 test_evidence_freshness.py);
  • 所有引用值永远不会出现在新鲜度报告中

隐私安全的诊断输出

EvidenceFreshnessReportto_dict()to_json()只包含:

  • policy_version(策略版本)
  • total_count(输入总数)
  • current_count/accepted_count(通过/当前有效数)
  • rejected_count(拒绝数)
  • reason_counts(确定性原因计数,按原因码排序)
  • passed(便捷布尔属性,等价于fresh

它们不包含证据引用(evidence_id)、时间戳、载荷字段、源文本、标识符或任何超会话值。测试 test_evidence_freshness.py 明确断言"e-current""synthetic-payload"都不会出现在to_json()输出中。failure_message()也只会生成形如privacy evidence freshness gate failed: rejected=5, reasons=expired_evidence=1, ...的计数级消息(evidence_freshness.py)。

from openmed.compliance import assert_evidence_freshness, EvidenceFreshnessError try: assert_evidence_freshness(evidence, policy, as_of=as_of) except EvidenceFreshnessError as exc: print(exc.report.to_json()) # 只有聚合计数,绝无记录级字段

assert_evidence_freshness()check_evidence_freshness()(后者的单一实现别名)在门控未通过时抛出携带同一份报告对象的EvidenceFreshnessError,异常消息同样只含计数(evidence_freshness.py)。

结论:把新鲜度门控接入发布流水线

将上述机制组合起来,一个完整的发布检查片段如下:

from datetime import datetime, timezone from openmed.compliance import EvidenceFreshnessPolicy, EvidenceRecord, evaluate_evidence_freshness policy = EvidenceFreshnessPolicy( policy_version="privacy-v2", age_limits={"release": timedelta(days=30), "*": timedelta(days=7)}, ) as_of = datetime(2026, 8, 12, 12, 0, tzinfo=timezone.utc) evidence = [EvidenceRecord( evidence_id="release-2026-08-12", evidence_type="release", generated_at=as_of, policy_version="privacy-v2", )] report = evaluate_evidence_freshness(evidence, policy, as_of=as_of) # report.passed 为 True 且 to_json() 只含聚合计数,可安全写入 CI 日志 print(report.to_json())

关键要点回顾:

  1. 类型安全:年龄上限必须是timedelta,版本号精确比较,杜绝单位漂移与版本漂移;
  2. 可重放:评估时间必须由as_of/now/clock注入,评估器既不读墙钟也不联网,同一输入必然得到同一结论;
  3. 失败关闭:空输入、缺失/非法字段、未知类型、naive/未来/过期时间戳、版本不匹配、非法超会话引用全部拒绝,年龄恰好等于上限则通过;
  4. 取代语义supersedes/superseded_by以不透明令牌完成新旧证据淘汰,外部归档中的旧记录不影响替换记录资格;
  5. 隐私输出:报告与异常只含策略版本和聚合计数,永不外泄证据引用、时间戳、载荷或超会话值。

该门控已在 tests/unit/compliance/test_evidence_freshness.py 中具备完整的单元测试覆盖(可重放性、失败关闭矩阵、类型约束、边界年龄、超会话三态、映射别名与隐私输出)。需要进一步了解它与 OpenMed 其他合规控制(如访问审查过期门控、报告基数预算、防篡改审计链)如何配合使用时,可继续阅读 docs/compliance/access-review-expiry.md、[docs/compliance/report-cardinality.md] 与 [docs/compliance/audit-envelopes.md]。

【免费下载链接】openmedLocal-first healthcare AI: clinical NER & HIPAA PII de-identification that runs 100% on-device. 2,200+ medical models, 21 languages, Apple MLX + Python, no cloud, no patient data leaving your network. Apache-2.0项目地址: https://gitcode.com/GitHub_Trending/ope/openmed

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询