接口契约 1.0.0(SX-003 冻结)
本文件描述 sottr-explorer 的版本化接口契约。契约由以下内容共同冻结,任何字段含义、 枚举或单位的变更都必须升级 schema_version(见文末“版本策略”):
| 载体 | 位置 |
|---|---|
| JSON Schema | schemas/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_digest | object | compressed_sha256、expanded_sha256(可空)、压缩/解压字节数;个人绝对路径不是契约字段 |
context | object | platform(可空)、mode、build(可空,不虚构)、dlc(可空数组)、source、limitations |
capabilities | object | 分列 container、sections、inventory、counters、reference_difference、location,每项 status + reason_codes |
coverage | object | supported_category_ids、reference_category_ids、supported_region_counter_keys、unavailable_reason_codes、evidence_refs;43 项稀疏计数不用数组填充变成完整矩阵 |
observations | object | container、sections、overall_completion、inventory、region_counters |
assessments | array | 逐项 internal_id + observation + assessment + rule_id + evidence_refs + limitations |
locations | array | LocationHint(见下) |
evidence | array | Evidence(见下) |
diagnostics | array | code、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 不丢弃,伴随字不赋状态语义。
三组概念相互独立(不得合并为单一“徽标”)
| 概念 | 字段 | 取值 |
|---|---|---|
| 业务观测 | observation | present、reference_present_current_absent、unknown |
| 业务评估 | assessment | likely_collected、likely_missing、unknown、verified_collected、verified_uncollected |
| 证据等级 | evidence_level | observed、community_correlated、game_validated |
| 上下文 | context.mode / context.source | campaign/profile/unknown/unsupported;user_declared/fixture_provenance/unknown |
| 定位精度 | locations[].precision | region、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同向。 - 因此首版变换是纯比例映射,不含平移、旋转与镜像:
u = x · W v = y · Hx = 0, y = 0→ 图片左上角;x = 1, y = 1→ 图片右下角。- 不同子关卡的
[0,1]空间互不相通,不存在全局地图坐标。
Transform 对象(唯一形态)
{
"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" | 首版只允许仿射;不提供单应 |
matrix | 6 个有限数 [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,最终语法):
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_percent | observations.overall_completion.value | evidence_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_differences | assessments[](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 负责 目标校验、快照时钟与两阶段写盘。
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.json | u64 仅字符串往返;数字形式必须被拒绝 |
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 并记录覆盖缺口,不记为通过。恢复或归档公开样本时保持来源核对, 不把样本字节提交进仓库。