aps-agent/server/aps_domain/explore_boundary.py

158 lines
5.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ============================================================
# Explore 与 Runtime 数据边界 v1(moduleId: domain-explore-boundary, 可重生 ✅)
# plan.md §5.1 + §9.7 / 矩阵 55 行:统一通道上下文与存储隔离
# - readonly_view(world):主干世界只读视图,任何写操作抛 PermissionError(fail closed)
# - run_explore(world, fn):统一 Explore 通道——内部深拷贝沙盒,fn 只拿到沙盒,
# 主干永不外泄写引用;任何 Explore 工具无法取得主干写能力(强制而非约定)
# ============================================================
from __future__ import annotations
import copy
from collections.abc import Callable
from typing import Any, TypeVar
T = TypeVar("T")
# 嵌套写保护深度(足够覆盖 world 顶层 + 常用列表/字典表;防御性限制避免全递归开销)
_MAX_DEPTH = 32
class ReadOnlyWorld:
"""主干世界只读视图:读操作透传,写操作(set/del/嵌套变异)抛 PermissionError。
用于把主干世界交给只读 Explore 工具;任何尝试写主干的行为立即失败关闭。
"""
def __init__(self, data: dict[str, Any], depth: int = 0) -> None:
object.__setattr__(self, "_data", data)
object.__setattr__(self, "_depth", depth)
# ---- 读协议 ----
def __getitem__(self, key: str) -> Any:
return _wrap(self._data[key], self._depth)
def get(self, key: str, default: Any = None) -> Any:
try:
return self[key]
except (KeyError, TypeError):
return default
def __contains__(self, key: object) -> bool:
return key in self._data
def keys(self):
return self._data.keys()
def values(self):
return (_wrap(v, self._depth) for v in self._data.values())
def items(self):
return ((k, _wrap(v, self._depth)) for k, v in self._data.items())
def __iter__(self):
return iter(self._data)
def __len__(self) -> int:
return len(self._data)
def __repr__(self) -> str:
return f"ReadOnlyWorld({len(self._data)} keys)"
# ---- 写协议:fail closed ----
def __setitem__(self, key: str, value: Any) -> None:
raise PermissionError(f"Explore 工具尝试写主干世界: key={key!r}")
def __delitem__(self, key: str) -> None:
raise PermissionError(f"Explore 工具尝试删除主干世界: key={key!r}")
def setdefault(self, key: str, default: Any = None) -> Any:
raise PermissionError(f"Explore 工具尝试 setdefault 主干世界: key={key!r}")
def update(self, *args, **kwargs) -> None:
raise PermissionError("Explore 工具尝试 update 主干世界")
def clear(self) -> None:
raise PermissionError("Explore 工具尝试 clear 主干世界")
def pop(self, *args) -> Any:
raise PermissionError("Explore 工具尝试 pop 主干世界")
def popitem(self):
raise PermissionError("Explore 工具尝试 popitem 主干世界")
class _ReadOnlyList:
"""只读列表视图:读透传,写抛 PermissionError。"""
def __init__(self, data: list[Any], depth: int) -> None:
self._data = data
self._depth = depth
def __getitem__(self, index):
return _wrap(self._data[index], self._depth)
def __iter__(self):
return (_wrap(v, self._depth) for v in self._data)
def __len__(self) -> int:
return len(self._data)
def __contains__(self, item: object) -> bool:
return item in self._data
def __repr__(self) -> str:
return f"ReadOnlyList({len(self._data)} items)"
def __setitem__(self, index, value) -> None:
raise PermissionError("Explore 工具尝试写主干列表")
def __delitem__(self, index) -> None:
raise PermissionError("Explore 工具尝试删除主干列表")
def append(self, *args) -> None:
raise PermissionError("Explore 工具尝试 append 主干列表")
def extend(self, *args) -> None:
raise PermissionError("Explore 工具尝试 extend 主干列表")
def insert(self, *args) -> None:
raise PermissionError("Explore 工具尝试 insert 主干列表")
def remove(self, *args) -> None:
raise PermissionError("Explore 工具尝试 remove 主干列表")
def pop(self, *args) -> Any:
raise PermissionError("Explore 工具尝试 pop 主干列表")
def clear(self) -> None:
raise PermissionError("Explore 工具尝试 clear 主干列表")
def sort(self, *args, **kwargs) -> None:
raise PermissionError("Explore 工具尝试 sort 主干列表")
def _wrap(value: Any, depth: int) -> Any:
"""按深度把 dict/list 包成只读视图(标量原样透传)。"""
if depth >= _MAX_DEPTH:
return value
if isinstance(value, dict):
return ReadOnlyWorld(value, depth + 1)
if isinstance(value, list):
return _ReadOnlyList(value, depth + 1)
return value
def readonly_view(world: dict[str, Any]) -> ReadOnlyWorld:
"""主干世界只读视图入口(Explore 工具只应拿到此视图)。"""
return ReadOnlyWorld(world)
def run_explore(world: dict[str, Any], fn: Callable[[dict[str, Any]], T]) -> T:
"""统一 Explore 通道:深拷贝沙盒 → 执行 → 返回结果。
主干永远以原始 dict 形式留在外部;fn 只接收沙盒 dict(写沙盒安全),
主干写能力在通道边界处被切断(矩阵 55 行:任何 Explore 工具无法取得主干写能力)。
"""
sandbox = copy.deepcopy(world) # 沙盒:深拷贝隔离(§5.1 铁律)
return fn(sandbox)