跳到正文

接口契约 1.0.0(SX-003 冻结) ​

本文件描述 sottr-explorer 的版本化接口契约。契约由以下内容共同冻结,任何字段含义、 枚举或单位的变更都必须升级 schema_version(见文末“版本策略”):

载体位置
JSON Schemaschemas/report.schema.json、schemas/batch.schema.json、schemas/desktop-result.schema.json、schemas/export.schema.json、schemas/metadata.schema.json
契约夹具fixtures/contract/{valid,invalid,representation,adapted}/
Rust 类型crates/sottr-core/src/report/(Report、信封)、crates/sottr-core/src/metadata.rs
前端类型apps/desktop/src/contract/*.d.ts(由 schema 生成,勿手改)
验证命令cargo test;cd apps/desktop && pnpm contract:check;CLI 输出复核见“分析管线与命令行”

schema_version 固定字符串 "1.0.0"。它与 Python v0.2 研究报告的 schema_version: 2 无关;tool_version、parser_profile_id、metadata_version、reference_set_version 独立记录,未知版本为 null 并在 diagnostics 中给出原因,不虚构构建号。

文件与版本规则 ​

  • 所有对象都要求显式列出全部字段;additionalProperties: false,契约外字段被拒绝。
  • 可空字段用 null 表达;未知不是零、不是空数组。
  • 内部 32 位 ID 固定 0x 加 8 位大写十六进制(^0x[0-9A-F]{8}$)。
  • 64 位资源/本地化键一律用字符串往返(十进制或 0x 十六进制),禁止经 JavaScript Number。
  • 有界字节偏移用整数并附十六进制显示(offset + offset_hex);无偏移为 null。
  • JSON 拒绝 NaN、Infinity 与重复键:schema 无法表达这两条,由解析落点任务 (SX-004/SX-008/SX-011,验收矩阵 T10)在读取处强制;契约测试以表示规则夹具固定边界。

Report(schemas/report.schema.json) ​

必填字段:schema_version、tool_version、parser_profile_id、metadata_version、 reference_set_version、input_digest、context、capabilities、coverage、 observations、assessments、locations、evidence、diagnostics。

字段类型语义与未知表达
input_digestobjectcompressed_sha256、expanded_sha256(可空)、压缩/解压字节数;个人绝对路径不是契约字段
contextobjectplatform(可空)、mode、build(可空,不虚构)、dlc(可空数组)、source、limitations
capabilitiesobject分列 container、sections、inventory、counters、reference_difference、location,每项 status + reason_codes
coverageobjectsupported_category_ids、reference_category_ids、supported_region_counter_keys、unavailable_reason_codes、evidence_refs;43 项稀疏计数不用数组填充变成完整矩阵
observationsobjectcontainer、sections、overall_completion、inventory、region_counters
assessmentsarray逐项 internal_id + observation + assessment + rule_id + evidence_refs + limitations
locationsarrayLocationHint(见下)
evidencearrayEvidence(见下)
diagnosticsarraycode、severity、scope、message、evidence_refs;UI 只看 code/severity/scope,不解析自然语言

值与未知规则 ​

  • ProgressValue(observations.overall_completion):value、unit、support_status、 reason、evidence_level、evidence_refs、limitations。value 为 null 时 reason 必填且非空;合法零保留 0。
  • RegionCounter.value 同规则:原始读数超过来源目标时保留 raw_value/raw_value_hex, value 与剩余置 null,不裁剪到目标值。
  • observations.inventory:status、inventory_complete、completeness_scope、 records、record_count、unique_id_count、duplicate_record_count。完整性只指当前 parser profile 下表恢复完整,不代表全游戏全集完备;异常 type28 时必须 inventory_complete = false 且阻断缺席推断(新产品强化规则,见 SX-005/SX-007)。
  • 物品记录保留 raw_companion_u32、raw_companion_hex、raw_low_two_bits、槽号与偏移; 重复 ID 保留全部原始记录,未知非零 ID 不丢弃,伴随字不赋状态语义。

三组概念相互独立(不得合并为单一“徽标”) ​

概念字段取值
业务观测observationpresent、reference_present_current_absent、unknown
业务评估assessmentlikely_collected、likely_missing、unknown、verified_collected、verified_uncollected
证据等级evidence_levelobserved、community_correlated、game_validated
上下文context.mode / context.sourcecampaign/profile/unknown/unsupported;user_declared/fixture_provenance/unknown
定位精度locations[].precisionregion、guide_group、guide_item、verified_coordinates

约束:

  • assessment 为 likely_* 或 verified_* 时 rule_id 必填;原始 present 默认仅表示已观察。
  • 首版无新游戏内证据时不产生 verified_*;禁止无依据概率分数(没有 confidence 小数)。
  • 差集为空不等于全游戏收集完成;结构完整不是全集完整证明。

LocationHint ​

  • precision 与 mapping_kind 分列;member_ids(组成员关系)与 guide_item(精确条目映射)分开。
  • group_actionability:whole_group_check / general_reference / unavailable, 携带 reason_codes、evidence_refs 与 evaluated_input_digest(与当前快照绑定)。 whole_group_check 只在完整性、兼容计数、零当前计数、参考范围与组关联全部匹配时给出; 非零、未知、冲突或变化时必须撤销并重新评估,前端不得从文案/计数自行推导。
  • coordinates:无已验证映射时为 null;precision 为 verified_coordinates 时必须有 坐标对象(x、y、z、coordinate_space、unit、level_id)。map_transform 携带变换时其 transform_parameters 是 Transform 对象的 JSON 字符串, 定义见下文「BasemapDataset」;无已验证变换时 map_transform 整体为 null。

Evidence ​

必含 evidence_id(稳定)、method、source_refs、sample_scope、limitations; 按类型附 offset/offset_hex、record_index、reference_set_id、cross_checks、 source_versions(来源标识 → 固定提交/文档版本)。

信封(batch / desktop-result / export) ​

  • 批量外层 BatchReport:results[],每个结果 {input_ref, report|null, error|null}; report 与 error 恰有一个非空;input_ref 为文件名或脱敏引用,不含个人绝对路径。
  • 桌面结果信封 DesktopResult:request_id、input_ref、snapshot_id、snapshot_time、 freshness、last_refresh_error、report、error。信封与核心 Report 分离:
    • freshness ∈ current / refreshing / stale / none,只说明本应用最近分析/刷新状态, 不声称与游戏实时同步;非 none 时必须携带快照身份。
    • 取消与晚到结果由 request_id 与当前选定请求比对识别,不混入报告业务值; 旧快照必须标 stale 并保留 last_refresh_error。
    • 取消的请求返回“无结果信封”(freshness = none,report 与 error 均为 null, 见 valid/desktop-result.cancelled.json):不产生成功报告,也不冒充输入错误。
    • 失败刷新(INPUT_CHANGED/INPUT_UNREADABLE 等):若该输入已有旧快照,信封同时 携带旧报告(stale + last_refresh_error);没有旧快照时为 none + error。
  • 导出信封 ExportDocument:document_kind = "report_export"、快照身份、 freshness(不含 none)、last_refresh_error_code、input_digest 与 report。 过期导出的 JSON 文件本身带 stale 标识;CSV 导出以明确列保留同样的快照身份与刷新错误码 (列定义见“导出与分享(SX-011)”,已实现并验收)。普通核心 JSON 与导出信封各自有 schema。

MetadataDataset(schemas/metadata.schema.json) ​

  • items:键为内部 ID;条目含 internal_id、aliases(全部保留)、display_name(可空)、 display_language、name_source、name_kind(source_localized/curated_short/ machine_translated/unknown)、category_id、region_id、applicability、 location_mappings、source_refs、review。
  • display_name 为 null 或 name_kind = unknown 时,前端使用带标识的“类别加 ID”回退文案, 不自造正式名称;人工简名必须标注不是游戏正式名称。categories、regions、 guide_groups 同样携带 name_kind 以区分来源本地化名称与人工简名。
  • guide_groups(稳定组 ID、member_ids、target_count、reference_set_id、 counter_keys、来源):成员关系与精确条目映射分开;counter_keys 是整组判断所需的 计数证据,group_actionability 由领域层按当前报告评估(见下),元数据不写死静态 true。
  • reference_sets:category_ids 与 reference_ids 逐类别参考 ID(键集合必须一致), applicability(游戏/平台/模式/构建范围/DLC 与备注)、来源存档哈希(压缩文件小写 十六进制 SHA-256)、去重规则。
  • region_counters:43 项稀疏计数映射,键为 counter:<region>:<category>;条目含 serialized_offset、行号、相对偏移、source_total 与 counts_toward(该计数对应的 参考类别合并口径,如贮藏点计数同时对应普通与稀有贮藏点)。缺项不是 0;偏移必须满足 0x10BC + 行号 × 104 + 相对偏移 恒等式且在 4 MiB 战役家族内(加载器强制)。
  • sources(URL 仅允许 http/https、固定提交、访问日期、许可、再分发状态、方法、审核)。
  • 许可状态非 yes 的来源数据不得进入发布包(SX-018 复核);元数据不执行脚本、不自动联网更新。

元数据加载与校验(SX-008) ​

加载器位于 crates/sottr-core/src/metadata.rs,只处理不可变字节、不访问文件系统; 仓库内数据集在 data/metadata/(三个文件作为一组加载,说明见该目录 README)。

  • 严格 JSON:拒绝重复键、NaN/Infinity、非 http/https 链接、映射键与条目 ID 不一致、计数偏移恒等式不符与跨文件同名条目冲突;空输入返回空元数据包。
  • 限制(MetadataLimits 默认,可按调用方评审调整):单文件 4 MiB、单字符串 4096 B、 单数组 8192 项、单对象 8192 条、嵌套 64 层。
  • 稳定错误码:METADATA_TOO_LARGE、METADATA_INVALID_JSON、 METADATA_NON_STANDARD_NUMBER、METADATA_DUPLICATE_KEY、METADATA_LIMIT_EXCEEDED、 METADATA_INVALID_VALUE(含越界 ID)、METADATA_INVALID_URL、METADATA_KEY_MISMATCH、 METADATA_COUNTER_MAPPING_INVALID、METADATA_UNRESOLVED_REFERENCE、 METADATA_VERSION_CONFLICT、METADATA_ENTRY_CONFLICT。
  • 合并规则:多个文件必须有相同的 metadata_version 与命名空间;相同条目去重, 冲突条目报错。引用(来源、类别、区域、组、参考集合、计数键)必须可解析。
  • 内容哈希:加载结果记录每个文件的小写十六进制 SHA-256;data/metadata/manifest.json 记录同一组哈希,Rust 测试对照防漂移。
  • 无兜底:缺少目录/空输入时计数规格为空(能力矩阵按 COUNTER_MAPPING_UNAVAILABLE 降级)、参考视图不存在、条目与攻略组为空;没有任何内置字典或偏移回退。
  • 领域视图转换:region_counter_specs()(按偏移排序)、reference_view()(按当前 上下文计算适用性:平台/构建/DLC 未知时保留限制文本,明确不匹配时 applicable = false)、 classify_ids()(仅按内部 ID 分类)、counter_scope_totals()(按 counts_toward 作用域汇总,供交叉检查组装层使用)。

组级定位门禁(domain/location.rs) ​

whole_group_check 只在快照为 current、普通战役上下文、表恢复完整、参考集合适用、 组成员落于参考范围、计数键解析且来源目标之和等于组目标数、组计数为零时给出。 稳定原因码:COMPLETE_TABLE、ZERO_GROUP_COUNT、REFERENCE_MATCH(整组可查); GROUP_COUNT_NONZERO、GROUP_COUNT_UNKNOWN、GROUP_COUNT_CONFLICT、 REFERENCE_NOT_APPLICABLE、GROUP_ASSOCIATION_UNCONFIRMED(降为 general_reference); INVENTORY_INCOMPLETE、INVENTORY_UNAVAILABLE、CONTEXT_NOT_CAMPAIGN、 SNAPSHOT_NOT_CURRENT(unavailable,关闭行动摘要)。输出绑定 evaluated_input_digest,每次刷新/切槽必须重新评估。

BasemapDataset(schemas/basemap.schema.json) ​

真实地图基底层:data/map/basemaps.json,记录每个子关卡的底图与「图鉴归一化坐标 → 图片像素」的变换。 加载器为 crates/sottr-core/src/basemap.rs,前端类型 apps/desktop/src/contract/basemap.d.ts 由 pnpm contract:types 生成。

坐标约定(唯一一处定义) ​

  • 图鉴 position.x / position.y 是每单位独立的归一化空间,取值 [0,1]。
  • 原点在底图取景框的西北角;y 向下(向南)增大,与图片像素 v 同向。
  • 因此首版变换是纯比例映射,不含平移、旋转与镜像:
text
u = x · W          v = y · H
  • x = 0, y = 0 → 图片左上角;x = 1, y = 1 → 图片右下角。
  • 不同子关卡的 [0,1] 空间互不相通,不存在全局地图坐标。

Transform 对象(唯一形态) ​

json
{
  "kind": "affine",
  "matrix": [2048.0, 0.0, 0.0, 0.0, 2048.0, 0.0],
  "coordinate_y_axis": "down",
  "image_origin": "top_left"
}
字段取值说明
kind"affine"首版只允许仿射;不提供单应
matrix6 个有限数 [a,b,c,d,e,f]u = a·x + b·y + c,v = d·x + e·y + f;首版必须等于 [W,0,0,0,H,0]
coordinate_y_axis"up" / "down"图鉴坐标的 y 方向;本数据集恒为 down
image_origin"top_left"图片原点

matrix 与 image_width / image_height 的等值关系由加载器校验(JSON Schema 只能约束元素个数)。

transform_parameters 字符串 ​

报告契约 MapTransform.transform_parameters 是字符串,内容就是上面这个 Transform 对象的 JSON 序列化,不另立格式:

  • 只允许同一份字段集,禁止附加字段;
  • 数字用 JSON 数字(不得为字符串);
  • 序列化时不得出现 NaN / Infinity;
  • 无已验证变换时 map_transform 整体为 null,不用空对象或空字符串占位。

即:基底层里的 transform 与报告里的 transform_parameters 是同一对象的两种载体 (前者已解析,后者为字符串)。

状态与精度门槛(D5) ​

status条件
unverified三个误差字段必须为 null
approximate中位误差 ≤ 2% 图片对角线
calibrated中位 ≤ 0.5% 且 P95 ≤ 1% 图片对角线

加载器按状态强制校验门槛,不达标直接拒绝,不做静默降级。 transform_basis = "analytic" 表示变换由游戏数据解析得出,此时误差字段是验证结果 而非拟合残差;error_source 区分 measured(逐单位抽样)与 global_estimate(汇总估计)。

双层校验分工 ​

  • JSON Schema:字段集、类型、枚举、矩阵元素个数、状态与误差字段的搭配 (if/then 条件在类型层被丢弃,故必须由加载器执行)。
  • Rust 加载器:键与 basemap_id 一致、unit 全局唯一、矩阵等于 [W,0,0,0,H,0]、 source_refs 可解析、D5 门槛、bundled 必须带 image_sha256。
  • 图片存在性、字节数、SHA-256 与 WebP 实际像素尺寸由 pnpm contract:images (apps/desktop/scripts/contract/check-basemap-images.mjs)校验; image_root.kind = bundled 时直接校验仓库内 image_root.path, external 时用 --root 或 BASEMAP_IMAGE_ROOT 指向图片目录。 加载器只接收字节,不读文件系统。
  • pnpm contract:check 不含图片校验:图片体积大且可外置,故单独成命令。

分析管线与命令行(SX-009) ​

crates/sottr-core/src/analyze.rs 提供同核心分析管线:

  • analyze_bytes(bytes, declared, metadata, limits) -> Result<Report, InputError>: 只接收不可变字节;容器无效或超出资源限制返回输入级错误 (DECOMPRESSION_FAILED / RESOURCE_LIMIT,retryable = false),不产生报告、 不做语义解释;局部失败留在报告能力矩阵与 diagnostics。
  • 报告装配顺序:容器 → 区段 → 记录 → 上下文 → 整体候选/43 项计数 → 参考视图与 分类 → 交叉检查 → 参考评估 → 定位(组门禁按当前快照评估)→ 证据(计数与参考集合 来源接线)→ 覆盖范围 → 诊断。上下文不适用时整体候选与区域计数按 unsupported + 原因关闭,原始身份观测保留。
  • 参考集合选择:当前规则为“恰好一个”(MetadataBundle::primary_reference_set_id); 零个或多个时不猜选择规则,只保留原始身份观测并给出 METADATA_UNAVAILABLE / METADATA_CONFLICT 诊断。
  • 计数交叉检查:counts_toward 为单类别的作用域按 category_counter_totals 逐类别比较;多类别合并作用域(如贮藏点计数同时覆盖普通与稀有类别)按 counter_scope_checks 比较观察并集;任一处不一致即 COUNTER_MISMATCH 降级, 不给 likely_*。贮藏点合并口径的对照关系为 77 = 65 + 12。
  • 分析器诊断码:上下文发现(MODE_*/BUILD_UNKNOWN_*/DLC_UNKNOWN_*)、 CONTAINER_TRAILING_BYTES、COUNTER_MAPPING_INVALID、METADATA_UNAVAILABLE, 以及既有 UNSUPPORTED_LAYOUT/UNSUPPORTED_CONTEXT/COUNTER_MISMATCH/ METADATA_CONFLICT/LOCATION_UNVERIFIED;同一(code, scope, message)去重。

CLI(crates/sottr-cli,最终语法):

text
sottr-cli analyze --input <文件或目录> --context <campaign|unknown> \
    --output <新目录> --metadata <元数据目录>
  • 目录输入收集 *.dat(≤128 个,按名称排序);单个文件输入等价于单文件批量。
  • profile.dat 始终按资料文件处理(仅容器观察),批量 --context 不覆盖该排除; --context campaign 是用户声明,不是格式识别证明。
  • 输出写入必须尚不存在的目录中的 analysis.json(schemas/batch.schema.json)。 单文件输入级错误(INPUT_UNREADABLE、DECOMPRESSION_FAILED、RESOURCE_LIMIT; 适配层对明显超限的文件先做大小预检,避免整份读入)写入对应结果条目, 不影响其他文件的输出。
  • 退出码:全部成功 0;至少一个输入级错误 1(批量 JSON 仍写出全部结果); 用法或配置错误 2。纯分析不做全文件两两比较。
  • 输出 JSON 的独立复核:node apps/desktop/scripts/contract/validate-document.mjs --schema batch --file <路径>(或 pnpm contract:document -- --schema batch --file <路径>)。

Python v0.2 适配(显式、不升级语义) ​

derived_candidates 通过显式适配器进入契约的观测层(实现于 SX-010),示例见 fixtures/contract/adapted/。规则:

Python v0.2契约 1.0.0说明
derived_candidates.overall_completion.candidate_percentobservations.overall_completion.valueevidence_level = community_correlated;warning 文本进入 limitations
region_candidates.entries[]observations.region_counters[]candidate_count 为 null(超源目标)→ value = null 且 reason 必填;raw_u32_le 原样保留
present_item_id_inventory.records[]observations.inventory.records[]表格名/槽号/偏移/伴随字保留;candidate_names 属于元数据侧,不进入报告
reference_presence_differencesassessments[](reference_present_current_absent)assessment 是否为 likely_missing 由规则决定;差集本身不构成评估
validated 旧模型层不映射任何 candidate_* 都不得因改名升级为 validated / verified_*

差分回归(SX-010) ​

cargo test -p sottr-core --test parity:Python v0.2(research/save-inspector/ 参照副本) 作为正常已知输入的参照,其原始输出保留在输出目录(report.json、CSV、日志); 规范化适配器(crates/sottr-core/tests/parity/adapter.rs)逐层对照,差异与判定写入 parity_report.json(附 parity_summary.txt)。

  • 环境变量:SOTTR_SAMPLE_DIR(私有五文件)、SOTTR_PUBLIC_DIR(按来源恢复的公开样本)、 SOTTR_PARITY_OUT_DIR(报告与原始输出基目录;默认 target/tmp/parity;私有回归应指向 仓库外目录)、SOTTR_PYTHON(解释器;默认探测 py -3/python3/python)。 夹具变量未设置时安静跳过(不记为通过)并说明覆盖缺口。
  • 对照范围:战役夹具比容器、哈希与原始观察、区段、记录序列与伴随字、唯一集合、43 项计数、 整体候选(float32 精确值 + 位模式,不是两位小数)、参考差集与跨文件重复组;profile 只比 容器、哈希与原始观察,并断言双方都不产生战役语义。
  • 规范化只处理路径(报告不含绝对路径)、键顺序与展示精度;任何状态/完整性差异都记录为差异, 范围外字段必须在 scope.excluded 中有原因(如 ascii_strings、两两比较、候选数值解释、 元数据侧候选名称)。
  • 设计内强化行为(异常 type28 的表不完整与缺席推断阻断,SX-005)以带 adjudication 的差异 记录;未解释差异使回归失败,不要求复制旧不安全输出。
  • 夹具清单:tests/parity/fixtures/public_samples.json(20 个公开战役样本 + 额外条目;由 build_public_inventory.py 从研究包公开证据生成,--check 复核)与 tests/parity/fixtures/private_samples.json(五文件期望值;不含哈希,哈希等式由回归断言)。
  • 对照在核心观测层进行(区段流、记录恢复、计数读数、整体读数、参考集合);SX-009 报告装配 的对应字段由 analyze_pipeline 测试另行固定。

导出与分享(SX-011) ​

核心导出实现于 crates/sottr-core/src/report/export.rs(不访问文件系统);CLI 负责 目标校验、快照时钟与两阶段写盘。

text
sottr-cli export --input <文件> --context <campaign|unknown> \
    --output <新目录> --metadata <元数据目录> \
    [--format json|csv|both] [--share] [--snapshot-id <ID>] \
    [--snapshot-time <RFC3339>] [--stale --refresh-error <CODE>]
  • JSON:report.json 是导出信封(ExportDocument,schema 见 export.schema.json), input_digest 从报告复制,导出与屏幕报告不可能分叉。
  • CSV:report.csv 的列固定为 snapshot_id、snapshot_time、freshness、 last_refresh_error_code、input_ref、input_sha256、id、candidate_names、 observation、assessment、evidence_level、rule_id、rule_description、 location_id、location_precision;每行对应一项评估。candidate_names 为 JSON 数组 (逐字保留全部候选名称);ID 输出 0x+8 位大写十六进制;数值不经浮点改写。
  • CSV 安全:以 =、+、-、@、制表或回车等危险前缀开头的外部文本(含前导空白 绕过,如 " =1+1")在 RFC 4180 引号之前加 ' 转义标记;安全文本不改写。JSON 数组 编码的候选名称单元格以 [ 开头,两层防护叠加。
  • 分享模式(--share):input_ref 去目录;所有字符串中的路径样片段(盘符、UNC、 file://、含分隔符的 .dat 引用)替换为文件名并保留其后紧邻文本;业务状态、证据、 定位精度与 schema 不变;导出文本必须通过 scan_private_material 自检(零命中)。 产品报告不携带存档原始字符串;分享脱敏是防御层,不把脱敏等同于全部内容可公开。
  • 严格序列化:拒绝 NaN/Infinity(信封内浮点必须有限);u64 资源键只经字符串 往返(ResourceKey,禁止 JSON 数字);导出内所有整数字面量在 JS 安全范围内。
  • 旧结果导出:--stale 必须携带 --refresh-error <CODE>(形如 ^[A-Z][A-Z0-9_]*$);信封与 CSV 的明确列同时携带 stale 与错误码,不依赖文件名 或截图传达过期。
  • 写盘安全:输出目录必须不存在且与输入不重叠(输出不得位于输入文件或输入所在 目录内;按规范路径比较);产物先写 <名称>.tmp,全部成功后再逐一改名提交;失败或 取消(未提交即丢弃)清理临时文件与本次创建的目录,不留下“完整报告”假象; --format 控制是否写出 JSON/CSV。输入级错误(不可读/无法分析)退出码 1, 用法与配置错误退出码 2。

桌面后端命令(SX-014) ​

apps/desktop/src-tauri 实现 Tauri 最小权限命令层:分析复用 analyze_bytes(同核心)、读取与写盘复用 sottr-platform(稳定快照、路径策略、 受控输出提交与快照时钟),不因界面需要放宽核心或路径规则。

命令参数按 Tauri 约定使用 camelCase(如 requestId);返回值使用 snake_case 字段(与契约 JSON 一致);错误对象为 {"code","message"}(稳定码,界面不解析 自然语言)。

命令作用要点
discover_saves在受限位置发现游戏目录(Known Folder/%USERPROFILE%)只读、有界、不做全盘扫描;候选状态为稳定字符串
list_candidates有界枚举候选存档深度/数量/大小上限;跳过符号链接与重解析点;不读取内容
select_input由用户主动打开系统文件/目录选择器取消返回 null;前端无 dialog:* 权限
analyze_file稳定快照读取 + 同核心分析返回 DesktopResult;见下“请求与快照语义”
cancel_analysis取消在飞请求已完成/未知返回 cancelled=false;取消不产生成功报告
export_report导出当前快照(JSON/CSV)目标必须是新目录且与输入不重叠;默认分享脱敏;旧结果携带 stale 与刷新错误码
resolve_guide_link仅解析/校验攻略来源链接(不打开)与 open_guide_link 同一校验路径;供“复制已核验链接”等无副作用操作
open_guide_link打开攻略来源链接只接受 snapshot_id/location_id/source_ref 稳定 ID;URL 从元数据解析并校验 http/https 后交给系统外部浏览器
open_external_url打开界面合成的外链(搜索地址等)不绑定快照;与攻略直链同一 http/https + 主机 + 无空白白名单
open_detail_window打开/复用独立详情窗口只接受 ≤2 MiB 的 JSON 对象 payload;先写 Rust 内存再建窗口;已有 detail 窗口时聚焦并广播 detail-payload-updated
read_detail_window_payload详情弹窗读取最近一次 payload只读 Rust 进程内存(窗口关闭不清除);不读存档、不落盘

请求与快照语义(开发方案 SX-014 的“界面接线补充”):

  • 每个分析请求以 request_id 隔离;同一输入后发起的请求序号更大,晚到的旧结果 不写入快照存储;前端只提交当前选定请求的结果(按 request_id 比对)。
  • 取消标记与结果提交在同一把会话锁内判定:取消先到绝不产生成功报告;提交先到 则 cancel_analysis 返回 cancelled=false。
  • 内存中每个输入保留最近一次成功快照(上限 16 个输入);失败刷新把它标记为 stale 并记录刷新错误码;被取消的请求只返回无结果信封。
  • 输入路径只用于路径策略、去重与输出重叠校验,不出现在信封、分享导出与外部 链接中;打开攻略链接不附加存档内容或账户信息。
  • 未列入 permissions/sottr-commands.toml 与对应 capability 的命令会被 ACL 拒绝。 两个 capability:default(主窗口 main,core:app/event/path/window 默认权限 + 11 条应用命令中除读取 payload 外的全部)与 detail(详情弹窗 detail,仅 core:event:default、core:window:allow-close、allow-read-detail-window-payload 与 allow-open-external-url);两者均未开放 opener/dialog/shell/文件系统/ 图像/菜单/托盘/资源/WebView 查询权限;WebView 导航只允许应用自身来源 (CSP + 导航门禁),外部页面不进入 WebView。
  • 窗口事件按标签收窄:只有主窗口(main)销毁才取消在飞分析、关闭详情弹窗并退出 应用;关闭详情弹窗(detail)不影响主窗口。详情 payload 只存进程内存(≤2 MiB、 必须是 JSON 对象),是打开时的只读快照,不含完整路径、证据或诊断数据。
  • 命令错误码:INVALID_PARAMS、INTERNAL、METADATA_UNAVAILABLE、 SNAPSHOT_UNKNOWN、SNAPSHOT_MISMATCH、LINK_LOCATION_UNKNOWN、 LINK_SOURCE_UNKNOWN、LINK_URL_UNSUPPORTED、LINK_OPEN_FAILED、 UNSUPPORTED_LOCATION、DISCOVERY_*、PATH_*、OUTPUT_*。

夹具清单 ​

夹具覆盖点
valid/report.valid.json完整合法:本地化名、圣胡安 0/8 whole_group_check、组多 ID、likely_missing
valid/report.degraded.json局部降级:表不完整、超目标计数、general_reference→unavailable、缺席推断关闭
valid/report.nonzero-group.json圣胡安非零 3/8 → general_reference,撤销整组检查
valid/metadata.valid.json本地化名 / 仅别名(unknown)/ region_id 为空 / 同组多 ID / 许可 pending / 稀疏计数与参考 ID
valid/batch.mixed.json合法 + 降级 + 输入错误三结果并存的批量外层
valid/desktop-result.{current,stale,other-slot}.json当前快照 / 旧快照(stale + 刷新错误)/ 跨槽晚返回
valid/desktop-result.cancelled.json取消的无结果信封(none,report/error 均 null;SX-014 补齐)
valid/export.stale.json过期导出信封(stale + 最近刷新错误码)
invalid/*未确立枚举值、null 缺原因、verified_coordinates 缺坐标、非法链接协议
representation/u64-boundary.json、u64-as-number.jsonu64 仅字符串往返;数字形式必须被拒绝
adapted/*Python v0.2 适配输入与输出形状示例
crates/sottr-core/tests/parity/fixtures/*.json差分回归夹具清单(SX-010):私有五文件期望值(不含哈希)与公开 20 样本来源/哈希清单
data/metadata/*.json(真实数据)由 pnpm contract:validate 与 schemas/metadata.schema.json 校验;Rust 加载测试另行核对数量、哈希与交叉一致性
valid/basemap.valid.json、valid/basemap.minimal.json基底层合法夹具:三种状态、bundled/external 根目录;同时由 Rust 加载器复核
invalid/basemap.*矩阵元素数不足、unverified 带误差、calibrated 误差为 null、未知字段、图片路径含父目录
data/map/basemaps.json(真实数据)由 pnpm contract:validate 与 crates/sottr-core/tests/basemap_dataset.rs 双层校验

版本策略 ​

  • 变更字段含义、状态枚举或单位 → 升级 schema_version 并保留适配说明与迁移段落。
  • 新增字段:本契约为“全字段显式 + 禁止额外字段”,因此新增字段同样属于版本变更; 先评审,再升级。SX-008 在契约发布前补全了 metadata.schema.json 的 region_counters、名称种类与参考/组字段(未发布版本内的补齐,schema_version 保持 1.0.0);此后对元数据 schema 的字段增删同样受本策略约束。
  • 适配旧 Python 输出必须通过显式适配器;原始报告可并存以便追溯差异。
  • 未决事项:报告与导出 JSON 的重复键与 NaN/Infinity 解析级拒绝落点(SX-004/011,T10; 元数据侧已由 SX-008 加载器执行)。
  • 元数据内容与来源许可已由 SX-008 落地(data/metadata/);redistributable 非 yes 的来源在 SX-018 发布复核前不得直接打包。

已知限制(本任务未宣称) ​

  • 未运行 pnpm tauri dev 实机界面;前端仅以类型检查与构建验证可消费性。
  • schema 校验与 Rust 反序列化双层校验为互补证据,而非形式化证明;夹具覆盖的用例见上表, 未出现在夹具中的字段组合仍可能在后续任务中暴露问题(发现即修正并升级契约)。
  • metadata.bad-url.json 的 URL 协议规则由 JSON Schema 与 SX-008 加载器双层执行 (加载器另校验主机名、空白与长度)。
  • SX-008 加载器按“一组文件”校验引用完整性;单独加载三个数据文件之一会因跨文件引用 未解析而失败(见 data/metadata/README.md)。
  • 元数据计数作用域合计(counter_scope_totals)的交叉检查接线已由 SX-009 的组装逻辑 完成(见“分析管线与命令行”一节);作用域比较依赖元数据 counts_toward 定义, 新增或变更合并口径必须重新评审。
  • CLI 的 Windows 路径安全(ACL、符号链接/重解析点、读取竞态与稳定快照)已按 SX-012/SX-013 验收(路径策略、稳定快照与受控输出提交见“平台适配层”; 桌面后端与 CLI 共用同一实现)。资源上限为默认值(压缩/解压 64 MiB、 128 个批量文件),可按后续任务评审调整。
  • 桌面命令层(SX-014)通过 MockRuntime IPC 集成测试 (apps/desktop/src-tauri/tests/{ipc_commands,permission_audit}.rs); 真实 WebView2 启动、CSP 强制、原生选择器与真实浏览器打开留待 SX-017 实机验收。
  • 桌面元数据以内嵌 data/metadata 三文件(编译期)加载,manifest.json 哈希由 测试对照;CLI 仍通过 --metadata 指定目录。运行时替换/独立升级未在 SX-014 开放。
  • 差分回归(SX-010)的样本不随仓库分发:公开 20 样本需按来源恢复(清单与 ZIP/文件哈希 见 tests/parity/fixtures/public_samples.json),私有五文件仅存在于开发工作站; 夹具缺失时回归 skip 并记录覆盖缺口,不记为通过。恢复或归档公开样本时保持来源核对, 不把样本字节提交进仓库。

代码采用 MIT;游戏数据事实与地图素材各自保留原权利。