82 lines
3.7 KiB
Markdown
82 lines
3.7 KiB
Markdown
# Phase 0 技术设计
|
||
|
||
契约本体见父任务 `design.md` §1,本文件只补 Phase 0 自身的实现设计。
|
||
|
||
## 1. 确定性核查的具体做法
|
||
|
||
编译器的潜在非确定性来源,按可疑度排序:
|
||
|
||
| 来源 | 表现 | 处置 |
|
||
|---|---|---|
|
||
| `fs.mkdtempSync(path.join(pipelineDir, "native-road-"))` | staging 目录名随机 | 若名字出现在 `compiled.json` 的 `source` 字段里,需归一化 |
|
||
| `compiled.json` 内的绝对路径 | `source.osm` / `source.overrides` / `source.trafficSignals` 是绝对路径 | 归一化为相对 repo root |
|
||
| 时间戳 | 未确认是否存在 | 核查 `compile-native-roads.js` 写入的 metadata |
|
||
| `Map` / `Set` 迭代顺序 | JS 保证插入序,若插入源本身有序则确定 | 观测两次运行 `orderHash` 是否一致即可暴露 |
|
||
| 浮点累加顺序 | 几何坐标末位抖动 | 若出现,说明存在顺序依赖,属真 bug,需修 |
|
||
| `crypto` 随机(`traffic-signals.js` require 了 `crypto`) | 信号 ID 可能含随机成分 | 重点核查 —— `native-traffic-signals.json` 是 `loadOrGenerate`,已存在则复用,首次生成可能不确定 |
|
||
|
||
**核查顺序**:
|
||
1. 备份两个有效区域的 `native-road/` 与 `native-traffic-signals.json`
|
||
2. 删除 `native-road/`(保留 signals,因为 `loadOrGenerate` 语义是已存在则复用)
|
||
3. 跑第一次 → snapshot A
|
||
4. 再删 `native-road/`,跑第二次 → snapshot B
|
||
5. diff A/B → volatile 字段清单
|
||
6. 额外一轮:删掉 signals 文件也重生成一次,确认 signal ID 是否确定
|
||
|
||
## 2. `scripts/road-parity.js` 设计
|
||
|
||
```
|
||
用法:
|
||
node scripts/road-parity.js --config <area-config> --snapshot <out.json>
|
||
node scripts/road-parity.js --config <area-config> --compare <baseline.json>
|
||
```
|
||
|
||
### 归一化规则(写死在脚本里,并回写进契约文档)
|
||
|
||
1. **路径**:任何绝对路径 → 相对 repo root;staging 目录名 → 字面量 `<staging>`
|
||
2. **JSON**:递归按 key 排序后 `JSON.stringify`,再 hash
|
||
3. **GeoJSON**:
|
||
- `contentHash` = features 按 `properties.native_id`(缺失则按几何首坐标)排序后 hash
|
||
- `orderHash` = 原序 hash
|
||
- 两者都记录;比对时 `contentHash` 不一致 = 语义变化(严重),
|
||
仅 `orderHash` 不一致 = 顺序抖动(需解释但可能可接受)
|
||
4. **浮点**:不做舍入。坐标变化就是变化,不允许用容差掩盖
|
||
(拆分是纯搬迁,任何坐标位移都是 bug)
|
||
|
||
### 输出形状
|
||
|
||
```json
|
||
{
|
||
"contract": "native-road-package/v1",
|
||
"areaId": "fengshu-er-road",
|
||
"files": {
|
||
"compiled.json": { "contentHash": "…", "bytes": 12345 },
|
||
"diagnostics.json": { "contentHash": "…", "bytes": 678, "count": 42 },
|
||
"layers/road_surface.geojson": {
|
||
"contentHash": "…", "orderHash": "…", "features": 128 }
|
||
},
|
||
"volatileExcluded": ["source.osm", "source.overrides", "<staging>"]
|
||
}
|
||
```
|
||
|
||
`count` / `features` 是冗余的人类可读字段 —— hash 变了但计数没变,
|
||
说明是内容变化;计数也变了,说明是增删。加速归因。
|
||
|
||
## 3. 基线目录布局
|
||
|
||
```
|
||
.trellis/tasks/08-25-road-compiler-extraction/baseline/
|
||
fengshu-er-road.json
|
||
nantaizi-lake-innovation-valley.json
|
||
README.md ← 记录生成时的 git commit、命令、volatile 字段清单
|
||
```
|
||
|
||
`README.md` 必须记录生成基线时的 commit hash,否则将来无法判断
|
||
"基线是在哪个代码状态下产生的"。
|
||
|
||
## 4. 为什么校验脚本放宿主而不是直接放未来的编译器仓库
|
||
|
||
Phase 0 时编译器仓库还不存在。脚本先落宿主 `scripts/`,
|
||
Phase 2 随编译器一起搬走,届时宿主保留一个薄封装(或直接调编译器包的 CLI)。
|
||
这样 Phase 1 的 parity 验证不需要等仓库拆分。
|