尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

OpenMed 同意感知缓存(ConsentCache)实现解析:面向可撤销同意上下文的本地内存缓存与审计设计

OpenMed 同意感知缓存(ConsentCache)实现解析:面向可撤销同意上下文的本地内存缓存与审计设计 OpenMed 同意感知缓存ConsentCache实现解析面向可撤销同意上下文的本地内存缓存与审计设计【免费下载链接】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/openmedopenmed.clinical.consent_cache是 OpenMed 提供的一个本地、纯内存的派生临床输出缓存模块核心能力是让「后来可以被撤销的同意上下文」所对应的缓存条目能够被确定性、可审计地失效。本文基于 docs/clinical/consent-cache.md 展开结合 openmed/clinical/consent_cache.py 源码与 tests/unit/clinical/test_consent_cache.py 测试用例系统讲解其隐私模型、指纹规范化算法、API 用法、撤销语义、审计事件与容量边界帮助读者在医疗数据本地化处理的管线中正确接入这一层基础设施保护机制。定位基础设施保护而非合规认证ConsentCache 的第一条设计原则是职责边界清晰。文档明确声明它是「infrastructure safeguard」基础设施保护措施不是合规认证compliance certification也不是临床决策保证clinical decision guarantee。从实现上看consent_cache.py 的ConsentCache只是一个受RLock保护的有界内存缓存它不会做以下任何事不做网络调用never makes a network call不写文件、不输出日志不联系任何同意注册中心consent registry不判断一次新的处理请求在法律上是否被允许。也就是说ConsentCache 只回答一个工程问题当某个同意上下文被撤销后之前基于它派生的缓存结果如何被彻底、可审计地清除。而「撤销动作是否应该发生」「处理请求是否合法」属于应用层自己的同意与披露工作流模块不越界。这一边界与 OpenMed 其他合规模块的分工是一致的例如 docs/compliance/consent-data-use-tags.md 中描述的DataUseTag/DataUseAction负责在处理入口对「数据用途标签」做本地确定性强制enforcement is local and deterministic同样不调用同意注册中心、不传输文档内容而 ConsentCache 则负责处理链路中缓存结果的存续期管理。隐私与确定性只留指纹不留原始输入为什么需要指纹化缓存条目的键由三部分组成调用方提供的cache_key、consent scope同意范围与consent revision同意修订号。如果直接把这三者的原始值存入条目元数据或审计事件就等于把患者标识符、同意范围文本等敏感内容固化在序列化输出里破坏 OpenMed「本地优先、最小化 PHI 留存」的整体目标。ConsentCache 的做法是这些值只在计算 SHA-256 指纹所需的极短时间内被规范化随后立刻丢弃原始形态。条目元数据只保存指纹撤销审计事件只保存数量。指纹算法实现指纹计算的入口在 consent_cache.py 的_fingerprintdef _fingerprint(kind: str, value: Any) - str: payload _canonical_json({kind: kind, value: _canonical_value(value)}) digest hashlib.sha256(payload.encode(utf-8)).hexdigest() return fsha256:{digest}即先对输入做无歧义的规范化_canonical_value再以{kind: ..., value: ...}结构做排序键、紧凑分隔符、纯 ASCII的 JSON 序列化_canonical_json见 consent_cache.pysort_keysTrue、separators(,, :)、ensure_asciiTrue最后用 SHA-256 摘要并加sha256:前缀确保指纹可被识别为摘要而非原始值。_canonical_valueconsent_cache.py对支持的类型做了带类型标签的规范化消除一切歧义字符串 →[string, value.strip()]去除首尾空白并标注类型避免与裸布尔/数字混淆字节串 →[bytes, value.hex()]None→[null]布尔 →[bool, value]与 int 区分开整数 →[int, value]浮点 →[float, value]且拒绝非有限值math.isfinite检查非有限数直接抛ValueError映射 →[mapping, ...]要求键为非空字符串按键排序列表/元组 →[sequence, ...]集合/frozenset →[set, ...]按规范 JSON 排序。任何不支持的输入都会抛出TypeError保证指纹输入始终是「确定的 JSON 兼容值」。三个公开指纹函数模块对外暴露三个指纹函数consent_cache.pyfingerprint_consent_scope(scope)字符串或字符串可迭代对象会被规范化为排序后的非空字符串集合再指纹化因此(analytics, summary)与{summary, analytics}会得到相同的指纹——范围语义与顺序无关fingerprint_consent_revision(revision)支持标量或 JSON 兼容的结构化值但拒绝空值None、空白字符串、空字节串、空容器都会抛ValueError见_require_non_empty_revisionfingerprint_cache_key(cache_key)同样拒绝空键。测试 test_consent_cache.py 直接验证了这一点first second、指纹以sha256:开头且原始 scope/revision 字符串不出现在指纹中。条目元数据与输出隔离ConsentCacheEntryconsent_cache.py是冻结 dataclass字段为三个指纹加上缓存值value。关键设计是value被标记为field(reprFalse, compareFalse)因此repr(entry)不会泄露缓存输出to_dict()只返回三个指纹字段缓存输出和原始输入永远不会出现在序列化元数据中。测试 test_consent_cache.py 用json.dumps(entry.to_dict(), sort_keysTrue)断言SYNTHETIC_CACHE_KEY与synthetic-output都不出现在序列化结果里repr(entry)中同样不含它们。快速上手put / get / revoke文档给出的最小用法如下完整保留from openmed.clinical.consent_cache import ConsentCache cache ConsentCache() cache.put( synthetic-result-key, {summary: synthetic derived output}, scopesummary, revisionreceipt-v1, ) result cache.get( synthetic-result-key, scopesummary, revisionreceipt-v1, ) event cache.revoke(scopesummary, revisionreceipt-v1) assert event.invalidated_count 1 assert cache.get( synthetic-result-key, scopesummary, revisionreceipt-v1, ) is None这段代码演示了完整生命周期put写入带同意上下文的派生输出 →get精确读取 →revoke撤销该同意上下文 → 再get返回None同时event.invalidated_count 1给出本次失效的条目数。别名参数与命名约定源码为put/get/revoke等所有入口同时接受两组等价的命名参数consent_cache.py 的_resolve_alias处理scope/consent_scoperevision/consent_revision_resolve_alias会拒绝「同一参数同时用两种名字传入」的情况抛TypeError: ... was supplied more than once避免歧义。put中 scope/revision 是必填的requiredTrueget中两者均可选requiredFalse。此外put有别名setrevoke有别名invalidate方便不同调用习惯的代码接入。ConsentCache还实现了__contains__判断某 cache_key 是否至少有一条目与__len__当前条目总数。返回语义put返回boolTrue表示已存储当该 scope/revision 对已被撤销时返回False写被拒绝计为一次rejected_writes而不会抛异常便于调用方以分支处理而不是 try/except 处理预期中的撤销拦截get命中返回缓存值未命中返回default默认为Noneget_entry返回ConsentCacheEntry对象本身便于需要读取指纹元数据的场景。同意上下文匹配规则为什么 get 要带 scope 和 revision文档强调get()传入的 scope 与 revision 必须与put()时一致。这是因为同一个cache_key可能在不同同意上下文下存在多条条目例如多次处理产生了不同 revision 的输出。源码 consent_cache.py 的get_entry逻辑是先按 cache_key 指纹过滤再叠加 scope/revision 指纹过滤然后要求匹配条目数恰为 1匹配数! 1一律视为未命中misses 1返回None绝不「任选一条」只有恰好一条时才计hits 1并返回。因此省略 scope/revision 的查找只有在某个 cache_key 仅存在一个同意上下文时才成功。这样设计避免了「一个键对应多个 revision 时随意挑选结果」的隐患——宁可未命中也不返回错误的缓存结果。测试 test_consent_cache.py 中的cache.get(key)之所以能返回new-output正是因为该键当时只有唯一一条 v2 条目。撤销语义精确撤销、scope 级撤销与墓碑精确撤销带 revisionrevoke(scopesummary, revisionreceipt-v1)会移除所有匹配该 scoperevision 指纹对的条目将该指纹对记入self._revoked墓碑集合tombstone此后所有对该指纹对的put都会被拒绝返回False即使该条目已经被移除。源码见 consent_cache.pyrevoke在锁内计算本次匹配的revoked_revisions精确撤销时就是{revision_fingerprint}更新墓碑、删除匹配条目、构造ConsentInvalidationEvent并追加到_audit_events。测试 test_consent_cache.py 验证了墓碑语义cache.put(key, old-output, scopesummary, revisionv1) cache.revoke(summary, v1) assert cache.put(key, stale-output, scopesummary, revisionv1) is False assert cache.is_revoked(summary, v1) assert cache.put(key, new-output, scopesummary, revisionv2) is True assert cache.get(key) new-output旧 revisionv1被永久拒绝写入而从未被观察过的新 revisionv2仍然可以写入——这正对应文档所说的「允许一个此前未见的 revision 代表一张新的同意收据」。scope 级撤销不带 revisionrevoke(scopesummary)的语义是收集该 scope 下当前仍缓存的所有 revision 指纹将它们全部加入墓碑并删除对应条目这些「已观察到的 revision」之后都被拒绝写入而一个此前未出现过的 revision仍然可以代表新的同意收据被写入。测试 test_consent_cache.py 给出了完整场景summaryscope 下 v1、v2 两条条目 billingscope 下 v1 一条执行revoke(scopesummary)后event.count 2、len(cache) 1billing/v1不受影响随后对summary/v1与summary/v2的写入均被拒绝。撤销的确定性重复执行相同的revoke是确定性的第一次返回invalidated_count1第二次没有可删除条目时返回invalidated_count0见 test_consent_cache.py并且两次事件都会被追加到审计列表。这意味着应用可以用同一撤销请求重放来保证最终一致性而不必担心副作用叠加。撤销是审计对象驱逐不是文档特别强调因容量上限导致的驱逐eviction不是同意撤销不计入撤销审计计数。换句话说revoke产生审计事件而 LRU 淘汰不产生——两者在审计语义上严格区分避免把「缓存压力」误报成「同意撤回」。审计与可观测性只计数不携带内容撤销事件格式每次revoke返回一个ConsentInvalidationEventconsent_cache.py其to_dict()只包含两个字段{event_type: consent_cache_invalidation, invalidated_count: 2}event_type固定为模块常量CONSENT_CACHE_EVENT_TYPE consent_cache_invalidationinvalidated_count不会为负__post_init__校验。事件不包含任何 cache_key、同意值或缓存输出。文档建议应用应通过自己批准approved的审计通道保留这个聚合事件。审计事件列表与统计audit_events属性返回按操作顺序排列的ConsentInvalidationEvent元组audit_log()返回其to_dict()形式仅计数clear_audit_events()清空事件列表并返回移除数量stats()返回ConsentCacheStatsconsent_cache.py包含六个仅计数字段hits、misses、writes、rejected_writes、invalidated、size同样可序列化为纯数字映射entry_metadata()返回全部条目的指纹元数据经to_dict()可用于向审计或报表面暴露条目级信息而不会带出缓存输出。测试 test_consent_cache.py 断言所有审计事件 payload 的键集合恰好是{event_type, invalidated_count}且json.dumps(audit, sort_keysTrue)中不含output-one、key-one等原始内容。常量版本模块导出CONSENT_CACHE_SCHEMA_VERSION 1consent_cache.py用于让外部审计消费者识别事件/元数据结构的版本便于未来演进。容量边界max_entries 与 LRU 驱逐ConsentCache(max_entries1024)默认容量为 1024 条consent_cache.py。构造函数对参数做严格校验max_entries必须是正整数布尔值会被显式拒绝isinstance(max_entries, bool)检查非整数抛TypeError非正数抛ValueError。底层用OrderedDict实现 LRU 近似语义每次put后move_to_end标记最近使用超限时popitem(lastFalse)从最老一端淘汰consent_cache.pyget命中同样会move_to_end保持热点条目存活。与撤销的区别在于驱逐不清墓碑、不产生审计事件、不计入invalidated统计所以驱逐不会把「已撤销的上下文」重新变成可写——墓碑集合与条目存储是两套独立的数据结构self._revoked与self._entries。clear()方法也只清空条目而保留墓碑保证「撤销是不可逆的」这一核心语义。线程安全与并发模型ConsentCache的所有读写操作都通过RLock可重入锁consent_cache.py保护。RLock而非普通Lock的选择意味着同一线程内可以安全地组合调用例如在持有锁的逻辑中再次调用内部加锁方法避免自死锁多线程服务中撤销与写入、读取之间保持原子性杜绝「撤销执行一半时新写入溜进缓存」的竞态。指纹计算在锁外完成锁内只做纯内存结构操作因此锁粒度小、对吞吐影响有限。完整接入示例处理管线中的同意生命周期把文档示例与源码 API 组合一个典型接入模式如下from openmed.clinical.consent_cache import ConsentCache cache ConsentCache(max_entries2048) # 1) 处理完成后以同意上下文为维度缓存派生输出 cache.put( patient-note-001, {summary: synthetic derived output, entities: 3}, scopesummary, revisionreceipt-2026-001, ) # 2) 后续请求精确命中scope/revision 必须与写入一致 hit cache.get( patient-note-001, scopesummary, revisionreceipt-2026-001, ) assert hit is not None # 3) 收到撤销指令后按 scope 撤销并留存聚合审计事件 event cache.revoke(scopesummary, revisionreceipt-2026-001) print(event.to_dict()) # {event_type: consent_cache_invalidation, invalidated_count: 1} # 4) 同一 revision 不再可写新 revision 仍可代表新同意 assert cache.put(patient-note-001, {...}, scopesummary, revisionreceipt-2026-001) is False assert cache.put(patient-note-001, {...}, scopesummary, revisionreceipt-2026-002) is True接入时的几条工程建议把 scope 设计为语义稳定、可排序的枚举值如summary、billing因为 scope 的规范化基于排序后的字符串集合顺序不影响匹配revision 使用不可变、可复现的收据标识如包含时间戳或一次性 ID 的字符串一旦撤销该修订永不回写每次处理都携带 scoperevision 调用get避免省略参数时因多上下文而意外未命中将ConsentInvalidationEvent落到应用自己的审计通道因为模块本身不写文件、不记日志不要把缓存驱逐当成撤销上报两者在审计语义上严格不同。源码与测试路径索引模块实现openmed/clinical/consent_cache.py测试用例tests/unit/clinical/test_consent_cache.py公共 API 导出openmed/clinical/init.pyConsentCache、ConsentCacheEntry、ConsentCacheStats、ConsentInvalidationEvent、ConsentScope、ConsentRevision及三个指纹函数均在__all__中设计文档docs/clinical/consent-cache.md相邻的合规强制层docs/compliance/consent-data-use-tags.md测试覆盖了六个核心行为指纹确定性且不泄露原始输入、条目元数据不含键与输出、精确撤销只影响匹配对、墓碑阻止旧 revision 重写、scope 级撤销与纯计数审计、重复撤销的确定性。这些用例可作为接入 ConsentCache 时行为契约的权威参照。小结ConsentCache 是一个「小而严谨」的模块它以 SHA-256 指纹替代原始同意数据、以墓碑保证撤销不可逆、以仅计数的审计事件满足可追溯性、以max_entries控制内存边界并在职责上严格保持「基础设施保护」的克制定位——不联网、不落盘、不记日志、不裁定合法性。对于在 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创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表