"""内存生命周期 fixture，不实现 OCR、向量模型或分布式一致性。"""
from copy import deepcopy

class Store:
    def __init__(self):
        self.sources, self.index, self.cache = {}, {}, {}
        self.epoch = 0

    def publish(self, doc_id, version, text, allowed):
        old = self.sources.get(doc_id)
        if old and old["deleted"]:
            raise ValueError("墓碑禁止普通发布；本模拟不支持恢复或复用文档标识")
        if old and version <= old["version"]:
            raise ValueError("版本必须递增")
        if not text.strip() or not allowed:
            raise ValueError("正文或 ACL 缺失，拒绝发布")
        # ①解析闸门：fixture 已是规范文本；生产须先校验结构与来源。
        doc = dict(version=version, text=text, allowed=set(allowed), deleted=False)
        # ②完整版本发布；模拟单进程原子切换，不冒充跨库事务。
        self.sources[doc_id] = doc
        self.index[doc_id] = deepcopy(doc)
        self.epoch += 1
        self.cache.clear()

    def revoke(self, doc_id, user):
        self.sources[doc_id]["allowed"].discard(user)
        self.epoch += 1
        self.cache.clear()  # ③撤权先改变权威状态，故意留下陈旧索引。

    def delete(self, doc_id):
        self.sources[doc_id]["deleted"] = True
        self.epoch += 1
        self.cache.clear()  # ③先封读，物理索引删除另行进行。

    def cleanup(self, doc_id):
        if not self.sources[doc_id]["deleted"]:
            raise ValueError("必须先封读再清理")
        self.index.pop(doc_id, None)
        # 清正文和权限，但保留标识、版本与墓碑，永久拒绝此标识的迟到发布。
        self.sources[doc_id]["text"] = ""
        self.sources[doc_id]["allowed"].clear()

    def valid(self, user, doc_id, version):
        doc = self.sources[doc_id]
        return not doc["deleted"] and user in doc["allowed"] and version == doc["version"]

    def retrieve(self, user, query):
        key = (user, query, self.epoch)
        if key not in self.cache:
            # ④先过滤后匹配，候选返回前再检查当前权威状态。
            self.cache[key] = [(i, d["version"], d["text"]) for i, d in self.index.items()
                               if user in d["allowed"] and query in d["text"]]
        return [row for row in self.cache[key] if self.valid(user, row[0], row[1])]


def main():
    store = Store()
    store.publish("policy", 1, "报销期限：30天", {"alice", "bob"})
    assert store.retrieve("bob", "报销")[0][2] == "报销期限：30天"
    old = deepcopy(store.index)
    store.publish("policy", 2, "报销期限：15天", {"alice", "bob"})
    assert store.retrieve("alice", "报销")[0][2] == "报销期限：15天"
    fresh = deepcopy(store.index)
    store.index = old
    assert store.retrieve("bob", "报销") == []  # 旧索引回流不能复活旧版本。
    store.index = fresh
    store.cache.clear()
    assert store.retrieve("bob", "报销")
    store.revoke("policy", "bob")
    store.cache[("bob", "报销", store.epoch)] = [("policy", 2, "报销期限：15天")]
    assert store.retrieve("bob", "报销") == []  # 陈旧缓存命中也必须复核权限。
    assert store.retrieve("alice", "报销")
    store.delete("policy")
    assert store.retrieve("alice", "报销") == []
    assert "policy" in store.index  # 未物理删除也不许泄露。
    for cleaned in (False, True):
        if cleaned:
            store.cleanup("policy")
            store.cleanup("policy")
            assert not store.index
        before = deepcopy((store.sources, store.index, store.cache, store.epoch))
        for version in (1, 2, 3, 100):
            try:
                store.publish("policy", version, "报销迟到更新", {"alice"})
            except ValueError as error:
                assert "墓碑" in str(error)
            else:
                raise AssertionError("迟到发布复活墓碑")
            assert (store.sources, store.index, store.cache, store.epoch) == before
            assert store.retrieve("alice", "报销") == []
        if cleaned:
            store.index = fresh  # 清理后离线重建回流仍不能复活。
            store.cache.clear()  # 强制走回流索引扫描，不让空缓存短路测试。
            assert store.retrieve("alice", "报销") == []
            store.cleanup("policy")
    print("PASS update, stale-version, revoke, delete, physical-cleanup; tombstone-late-publish; SIMULATED")

if __name__ == "__main__":
    main()
