Chronaxis用户手册English
本页目录

chronaxis.core.decoded_binding

源码基线:8904c2f744951857659abd40aaa3535d57faf53c

1"""把实际封存解码历史的来源证据关联为可执行的轻量信号绑定。23输入为DeclarationBindings及逐来源DecodedSourceEvidence,输出不可变的4DecodedBindings与独立运行身份。先检查SHA格式、投影、会话和whole-source EOF,5再把完整实际证据与原声明身份纳入摘要;运行依赖还须存在于实际读取投影中。6声明身份只证明配置,实际证据应由执行层工厂完整重放封存历史后生成。7单独构造合法SHA文本或EOF字段本身不证明文件已读取、校验或具有这些内容。8本模块仅依赖标准库和核心声明模型,不持有样本历史、不读写文件或加载NumPy。9模型无关闭资源;执行层持有元数据的预算与生命周期由对应lease负责。10"""1112# 冻结证据和运行身份;不引入数组、来源适配器或磁盘操作。13from dataclasses import dataclass, field14from hashlib import sha25615from .binding import SignalDefinition, _identity16from .declarations import DeclarationBindings17from .unit_conversion import require_config_version18from .validation import require_identifier19from ..timebase.conversion import require_int64_ns202122def _require_sha256(value: str, name: str) -> None:23    """只校验固定SHA-256文本格式,不声称摘要来自已核实文件。2425    参数: value为64位小写十六进制文本;name为错误定位字段。26    返回: None;无数据读取或字符串归一化。27    异常: TypeError为非原生str;ValueError为长度或字符不符。28    """29    # 明确拒绝隐式str转换,保留工厂生成的规范摘要。30    if type(value) is not str:31        raise TypeError(f'{name}需要原生SHA-256字符串')32    # 固定字符域同时拒绝大写、空白、换行和非十六进制字符。33    if len(value) != 64 or any(character not in '0123456789abcdef' for character in value):34        raise ValueError(f'{name}需要64位小写十六进制SHA-256')353637@dataclass(frozen=True)38class DecodedSourceEvidence:39    """保存工厂已核对的一份来源历史事实,字段冻结且不保存逐条样本。4041    三份摘要分别关联实际描述、请求和规范历史,不以文件名代替数据身份。42    counts记录全源重放总量,projected_aliases保存实际请求投影顺序。43    观测端点允许未知且保持None;EOF只接受整个来源结束,不能冒充批次结束。44    模型仅验证证据结构,实际文件、字段、计数和摘要由执行层工厂负责核实。45    """4647    source: str  # 当前声明中的唯一来源名。48    session_id: str  # 当前实际重放所属会话。49    description_sha256: str  # 实际来源描述及完整转换契约摘要。50    request_sha256: str  # 本次读取范围、投影和批次声明摘要。51    history_sha256: str  # 已核实规范历史字节摘要,不冒充原始日志摘要。52    sample_count: int  # 全源实际样本总数,非负原生整数。53    progress_count: int  # 全源实际批次进度记录数,至少包含最终EOF。54    projected_aliases: tuple[str, ...]  # 实际读取的唯一简单别名,空投影合法。55    observation_start_ns: int | None  # 原描述中的已知会话坐标首端;未知不补0。56    observation_end_ns: int | None  # 最终EOF中的已知会话坐标终点;未知不补周期。57    source_eof: bool  # 必须为True,表示整个配置来源已完成。5859    def __post_init__(self) -> None:60        """检查固定身份、摘要格式、全源计数、投影和精确观测端点。6162        参数: 无,读取冻结字段;时间单位均为整数纳秒。63        返回: None;合法整数端点规范为原生int,None保持未知。64        异常: TypeError/ValueError为结构或事实矛盾;时间溢出异常原样传播。65        副作用: 仅在构造期规范本对象端点,不访问来源或验证文件内容。66        """67        # 来源/会话名称不能借点号路径或任意对象绕过身份检查。68        require_identifier(self.source, 'decoded.source')69        require_identifier(self.session_id, 'decoded.session_id')70        # 三种证据用途不同,逐项校验而不把任一摘要当另一个摘要使用。71        for name, value in (('description_sha256', self.description_sha256),72                            ('request_sha256', self.request_sha256),73                            ('history_sha256', self.history_sha256)):74            _require_sha256(value, f'decoded.{name}')75        # bool不是计数;最终EOF至少对应一条实际进度记录。76        if type(self.sample_count) is not int or self.sample_count < 0:77            raise ValueError('decoded.sample_count需要非负原生int')78        if type(self.progress_count) is not int or self.progress_count < 1:79            raise ValueError('decoded.progress_count需要至少为1的原生int')80        # 冻结投影顺序,不隐式消除重复或接受可变列表。81        if type(self.projected_aliases) is not tuple:82            raise TypeError('decoded.projected_aliases需要tuple')83        for alias in self.projected_aliases:84            require_identifier(alias, 'decoded.projected_alias')85        if len(set(self.projected_aliases)) != len(self.projected_aliases):86            raise ValueError('decoded.projected_aliases不能重复')87        # 单个已知端点独立检查;不凭请求域或投影首样本推断未知端点。88        start = None if self.observation_start_ns is None else require_int64_ns(self.observation_start_ns, 'decoded.observation_start_ns')89        end = None if self.observation_end_ns is None else require_int64_ns(self.observation_end_ns, 'decoded.observation_end_ns')90        if start is not None and end is not None and end < start:91            raise ValueError('decoded来源观测终点早于起点')92        # 普通批次结束不能升级为可执行证据,只接真正全源耗尽的明确bool。93        if type(self.source_eof) is not bool or not self.source_eof:94            raise ValueError('decoded证据需要真正whole-source EOF=True')95        # 身份编码只接原生标量,整数规范保留精确坐标,不改变端点含义。96        object.__setattr__(self, 'observation_start_ns', start)97        object.__setattr__(self, 'observation_end_ns', end)9899100@dataclass(frozen=True)101class DecodedBindings:102    """冻结当前声明与实际解码历史证据,供编译和两个数值context入口使用。103104    sources必须与声明来源一一对应,投影只能包含该来源声明的原信号别名。105    identity使用decoded-binding-v1前缀与完整证据,不能与声明或memory身份混用。106    resolve仅委托冻结声明;执行前仍检查实际投影和已有同步政策。107    本对象不保存SourceSpec.rows,也不能交给材料化完整内存历史的快捷入口。108    """109110    config_version: int | None = field(default=None, kw_only=True)  # 缺省继承声明,显式不得冲突。111    declarations: DeclarationBindings  # 原配置关系与同步依据;其身份仍只是声明证明。112    sources: tuple[DecodedSourceEvidence, ...]  # 已核对来源事实,保留工厂的固定顺序。113    identity: str = field(init=False)  # 声明加实际证据的独立SHA-256运行身份。114115    def __post_init__(self) -> None:116        """检查来源/会话/投影归属,再生成与完整实际证据关联的身份。117118        参数: 无,读取declarations与sources;所有容器必须冻结。119        返回: None;构造identity,不重新读取历史或改变声明。120        异常: TypeError/ValueError为模型、重复/缺少来源、会话或投影冲突。121        """122        # 只接明确类型,声明或自由dict不能自行冒充实际来源证据。123        if (not isinstance(self.declarations, DeclarationBindings) or type(self.sources) is not tuple124                or any(not isinstance(item, DecodedSourceEvidence) for item in self.sources)):125            raise TypeError('解码绑定需要DeclarationBindings和DecodedSourceEvidence tuple')126        names = {item.source for item in self.sources}127        if len(names) != len(self.sources) or names != {item.name for item in self.declarations.sources}:128            raise ValueError('解码证据来源必须唯一且与声明完全对应')129        for evidence in self.sources:130            # 来源事实不能跨会话借用,即使字段/摘要文本相同也不接受。131            if evidence.session_id != self.declarations.session_id:132                raise ValueError('解码证据会话与当前声明不一致')133            aliases = {item.name for item in self.declarations.signals if item.source == evidence.source}134            if not set(evidence.projected_aliases).issubset(aliases):135                raise ValueError('解码投影包含其他来源或未声明别名')136        # 完整证据保留来源/请求/历史摘要、原范围、全源计数和实际投影;不只哈希配置。137        # 运行绑定继承配置规则,不因实际来源替换而重设版本。138        version = self.declarations.config_version139        if self.config_version is not None and require_config_version(self.config_version) != version:140            raise ValueError('运行绑定config_version与声明不一致')141        object.__setattr__(self, 'config_version', version)142        payload = ('decoded-binding-v1', self.declarations.identity,143                   tuple((item.source, item.session_id, item.description_sha256, item.request_sha256,144                          item.history_sha256, item.sample_count, item.progress_count, item.projected_aliases,145                          item.observation_start_ns, item.observation_end_ns, item.source_eof) for item in self.sources))146        object.__setattr__(self, 'identity', sha256(_identity(payload).encode('utf-8')).hexdigest())147148    @property149    def extensions(self):150        """读取宿主注册。参数: 无。返回: 冻结注册或None。异常: 无。"""151        return self.declarations.extensions152153    @property154    def lookups(self):155        """读取原声明标定表。参数: 无。返回: 冻结tuple。异常: 无。"""156        return self.declarations.lookups157158    @property159    def signals(self) -> tuple[SignalDefinition, ...]:160        """读取当前冻结信号声明,不创建原始行或数值历史。161162        参数: 无。返回: SignalDefinition tuple。异常: 无;属性不能重新赋值。163        """164        return self.declarations.signals165166    @property167    def projected_signals(self) -> tuple[SignalDefinition, ...]:168        """按原声明顺序返回实际历史投影中的定义,不把未读取声明当作运行状态。169170        参数: 无,使用当前已校验sources中的projected_aliases。171        返回: 原SignalDefinition对象组成的tuple;signals仍保留完整声明。172        异常: 分配或取消错误原样传播。调用执行层须先为有限索引和tuple预付费用。173        不读取历史、不改变绑定身份,也不把不同来源的投影误判为同步依据。174        """175        selected = None176        try:177            selected = {name for source in self.sources for name in source.projected_aliases}178            return tuple(item for item in self.declarations.signals if item.name in selected)179        finally:180            selected = None181182    @property183    def derived_names(self) -> tuple[str, ...]:184        """读取当前统一名称空间中的派生别名。185186        参数: 无。返回: str tuple。异常: 无;不把派生名称当实际读入列。187        """188        return self.declarations.derived_names189190    def resolve(self, name: str) -> SignalDefinition:191        """按当前声明查找原信号定义,不推断字段或执行数值转换。192193        参数: name为安全简单别名。返回: 当前SignalDefinition。194        异常: ValueError为非法或未声明名称;查找本身不证明该别名已投影。195        """196        return self.declarations.resolve(name)197198    def require_synchronized(self, names: tuple[str, ...]) -> None:199        """先证明全部实际依赖已读取,再应用既有多源同步政策。200201        参数: names为展开后的原信号依赖tuple;空依赖合法。202        返回: None;不修改投影、偏移或同步声明。203        异常: TypeError为错误容器;ValueError为未知/未投影依赖或同步缺项。204        """205        # 可变/无限依赖容器不能绕过冻结编译协议。206        if type(names) is not tuple:207            raise TypeError('解码联合依赖names必须为tuple')208        evidence_by_source = {item.source: item for item in self.sources}209        for name in names:210            definition = self.resolve(name)211            if name not in evidence_by_source[definition.source].projected_aliases:212                raise ValueError(f'运行依赖未在实际解码投影中:{name}')213        # offset数值或同名时基不替代原有核实同步依据;不另立宽松政策。214        self.declarations.require_synchronized(names)215
Chronaxis 用户手册 · 离线阅读