feat: package provider delivery as NavSea zip

This commit is contained in:
OpenAI Codex
2026-10-06 10:28:22 +08:00
parent 804a764109
commit 405f6e01d5
10 changed files with 965 additions and 28 deletions

View File

@@ -2362,3 +2362,33 @@
- CM95 通过用户指定 Style、Sprite 与三块真实 PBF 样本执行完成,复用权威人工 assignment;2,077 occurrence、102 个实际 Visual Object,Package 校验 `PASS_WITH_WARNINGS` 并导出。警告保留未映射项和样本限制。
- NavSea 的当前本地 Style 可读,但 Sprite 与三块候选 PBF 对 `192.168.200.184:80` 均返回 connection refused;NavSea 端到端未完成,未伪造样本或 PASS。
- 测试、Python 编译、CLI help 与 `git diff --check` 结果记录在本任务完成报告。
## 2026-10-06 PBF Provider Pipeline v1 双 Provider 最终实跑补充
- 前一条记录中的 NavSea 阻塞原因已确认:旧 `192.168.200.184:80` LAN 资源端点当前未监听;不是 Pipeline 逻辑或 Codex sandbox 独有问题。
- 保留 X220 当前本地 `style.navsea-delivery-full-semantic-extentfix.json` 作为 NavSea Style 分析输入,不用公开 Style 替换它;仅在 `providers/navsea/manifest.json` 显式覆盖 PBF / Sprite / Glyph 为当前可用的 `https://pbf.jpcn.ccwu.cc/` 同数据集资源入口。
- NavSea 已 fresh 重跑成功:
- 三个 z12 样本 `12/3531/1641`、`12/3526/1643`、`12/3638/1613` 全部下载成功;
- 8,484 occurrence、85 个 distinct Actual Visual Object;
- Mapping:VISUAL_OBJECT_MATCH 27、LAYER_ONLY 7、AMBIGUOUS 11、UNKNOWN 40;
- PBF-01~PBF-05 PASS,PBF-06 `PASS_WITH_WARNINGS`,PBF-07 `EXPORTED`。
- CM95 已清理临时产物后 fresh 重跑成功:
- 三个指定 z12 样本全部成功;
- 2,077 occurrence、102 个 distinct Actual Visual Object;
- Mapping:VISUAL_OBJECT_MATCH 9、LAYER_ONLY 47、AMBIGUOUS 2、UNKNOWN 44;
- PBF-01~PBF-05 PASS,PBF-06 `PASS_WITH_WARNINGS`,PBF-07 `EXPORTED`。
- 两套 warnings 都来自 `mapping:unresolved_present` 与 `limitations:present`;未将 UNKNOWN/AMBIGUOUS 静默映射为标准 Layer。
- 最终双 Provider 端到端结果记录在 `docs/报告/PBF_Provider_Pipeline_v1_Codex完成报告_v2.md`。
## 2026-10-06 PBF-08 NavSea Delivery ZIP
- 在 PBF-07 Provider Package Export 后新增 PBF-08。
- PBF-08 自动生成单文件 `.navsea-provider.zip`,用户交付给 NavSea 时只需要复制该 ZIP,不再手工挑选多个文件。
- ZIP 使用严格 9 文件白名单:provider-package.json、provider-policy.json、mapping-layer-assignment.json、resources-style.json、resources-sprite.json、resources-sprite.png、validation.json、validation.md、MANIFEST.json。
- 任一必需文件缺失时禁止生成 ZIP;ZIP 完成后自动核对精确文件列表、CRC 和 SHA-256,并写入 pipeline-report.json。
- NavSea 实跑生成 `navsea-delivery-full-semantic-extentfix-1.0.0.navsea-provider.zip`,SHA-256 `54aff8d8b882211ced6e77b3a93e8e793c780b44a1faa982ed8fc64693124fd5`,PBF-08 PACKAGED。
- CM95 实跑生成 `cm95-s52-full-1.0.0.navsea-provider.zip`,SHA-256 `603cc004833bcff5571a32843d8ceedc7858b2fc4b28c41056db6807c7d511ee`,PBF-08 PACKAGED。
- 两个 ZIP 均经外部 `unzip -t` 验证 9/9 文件 OK。
- 自动测试从 6 项增加到 8 项,8/8 PASS;新增覆盖严格白名单与缺文件拒绝打包。
- 详细结果:`docs/报告/PBF_Provider_Pipeline_v1_Codex完成报告_v3.md`。

View File

@@ -30,6 +30,8 @@ PBF-05 Provider Policy Compile
PBF-06 Provider Package Validate
↓
PBF-07 NavSea Provider Package Export
↓
PBF-08 NavSea Delivery ZIP
```
Pipeline 正式终点是 `NavSea Provider Package`,不是 `ProviderExecutionPlan`。
@@ -370,7 +372,46 @@ MANIFEST 至少包含:
只有 PASS 或明确允许发布的 PASS_WITH_WARNINGS 才能正式 export。
## 10. NavSea Provider Package Contract
## 10. PBF-08 NavSea Delivery ZIP
目标:把 PBF-07 已验证的最终 NavSea 正式输入打成一个单文件 ZIP,避免人工复制时漏文件、错版本或混入生产中间产物。
PBF-08 不改变 Provider Package Contract,只增加交付封装。ZIP 使用严格白名单,只允许包含:
```text
provider-package.json
provider-policy.json
mapping-layer-assignment.json
resources-style.json
resources-sprite.json
resources-sprite.png
validation.json
validation.md
MANIFEST.json
```
默认输出:
```text
dist/<provider-id>/<dataset-id>/
<provider-id>-<dataset-id>-<version>.navsea-provider.zip
```
ZIP 内部使用同名顶层目录,避免多个 Provider 包解压后文件互相覆盖。
Gate:
- PBF-07 必须已经成功导出;
- validation 必须为 PASS 或 PASS_WITH_WARNINGS;
- 白名单中的 9 个文件必须全部存在;
- ZIP 生成后必须重新读取并核对精确文件列表;
- ZIP 必须通过 CRC 校验;
- Pipeline 必须计算 ZIP SHA-256 并写入 pipeline-report.json;
- 任何临时 PBF、`.tmp`、工具源码、测试、人工审核中间文件或未声明文件不得进入 ZIP。
因此用户实际交付给 NavSea 时,只需要复制这一份 `.navsea-provider.zip`。
## 11. NavSea Provider Package Contract
公开 Contract 至少覆盖:
@@ -396,7 +437,7 @@ MANIFEST 至少包含:
公开 Contract 不要求第三方使用官方 PBF 工具。
## 11. NavSea 接收侧
## 12. NavSea 接收侧
```text
NavSea Provider Package
@@ -418,7 +459,7 @@ NavSea Runtime
NavSea 不再负责 CM95/S-52 语义研究、Visual Object 人工分类、Provider-specific 图标研究或 Provider Policy 推理生成。
## 12. 当前工具迁移关系
## 13. 当前工具迁移关系
`tools/style-visual-object/style_vo_tool.py` → PBF-02。
@@ -432,7 +473,7 @@ NavSea 不再负责 CM95/S-52 语义研究、Visual Object 人工分类、Provid
`validate_cm95_classification.py` 暂保留为 CM95 基线校验;后续增加 Provider-neutral validator。
## 13. 实施顺序
## 14. 实施顺序
第一优先级:冻结 Provider Policy Schema v1 和 Provider Package Schema v1。
@@ -442,7 +483,7 @@ NavSea 不再负责 CM95/S-52 语义研究、Visual Object 人工分类、Provid
第四优先级:调整 NavSea `help/chart-profile-tools/`,收敛为读取公开 Provider Package → Validate → Profile → ProviderCompiler。
## 14. 核心原则
## 15. 核心原则
```text
PBF = 数据生产者

View File

@@ -0,0 +1,276 @@
# PBF Provider Pipeline v1 实现任务
日期:2026-10-06
## 0. 任务目标
在当前独立 worktree `/root/sourceserver/pbf-provider-pipeline-v1` 中,把 `docs/PBF_Pipeline_正式流程_v1.md` 定义的 PBF-01 ~ PBF-07 落成可运行、Provider-neutral 的正式 Pipeline,并实际用 NavSea 与 CM95 两套 Style/资源跑通。
最终终点必须是:
```text
NavSea Provider Package
```
不得生成 NavSea Runtime 的 `ProviderExecutionPlan`。
## 1. 开工前必须读取
先完整阅读:
- `AGENTS.md`
- `docs/PBF_Pipeline_正式流程_v1.md`
- `STEP_RECORD.md`
- `tools/style-visual-object/README.md`
- `tools/style-visual-object/style_vo_tool.py`
- `tools/style-visual-object/output/navsea-standard/standard-catalog.json`
- `tools/style-visual-object/output/cm95-manual-review/assignment/cm95-layer-assignment-v1.json`
- `tools/style-visual-object/output/cm95-manual-review/validation-report-v1.json`
- `tools/visual-object-instantiation/README.md`
- `tools/visual-object-instantiation/instantiate_pbf_visual_objects.py`
- `tools/visual-object-matcher/README.md`
- `tools/visual-object-matcher/visual_matcher_experimental.py`
先按真实文件结构和字段工作,不凭记忆臆造。
## 2. 必做项
- [ ] PBF-01:实现 Provider Source Intake,支持本地路径和 HTTP(S) Style;记录 provider/dataset identity、资源、hash/fingerprint、source snapshot。
- [ ] PBF-02:复用现有 Style Visual Object Tool;必要时补薄包装,但不得破坏已冻结 NavSea Standard Catalog。
- [ ] PBF-03:把 `visual-object-instantiation` 正规化并 Provider-neutral 化。删除主逻辑中的 CM95 硬编码,包括固定 `source: cm95`、固定 catalog_name 等;Provider 信息必须来自参数/manifest。支持对选定 XYZ sample tiles 下载到工作目录后实例化。不得按屏幕位置拼接对象。
- [ ] PBF-04:提供统一 Mapping ingestion/normalization。CM95 读取现有人工 assignment;NavSea 从 frozen Standard Catalog 的真实结构生成自身 Provider mapping。自动 matcher 只能作为建议,不能覆盖人工结果。
- [ ] PBF-05:新增 `tools/provider-policy-compiler/`,定义并实现统一 Provider Policy Schema v1、编译器、validator。NavSea 与 CM95 必须使用完全相同 Schema;主逻辑禁止 `provider == cm95` 等分支。
- [ ] PBF-06:实现 Provider Package Schema v1 与 Validator,结果严格为 PASS / PASS_WITH_WARNINGS / FAIL;FAIL 禁止 export。
- [ ] PBF-07:实现正式 Provider Package Export,并生成 MANIFEST / fingerprints / validation report。
- [ ] 新增一个 Provider-neutral 端到端 CLI/orchestrator,单条命令可从 manifest 执行 Pipeline。应能选择阶段或复用已存在的审核/assignment 输入,但最终输出结构一致。
- [ ] 把两个当前未跟踪的 `tools/visual-object-instantiation/`、`tools/visual-object-matcher/` 中本任务需要的源码正规纳入版本控制;不要提交 `__pycache__`。
- [ ] 增加最小但足够的自动测试,覆盖 schema、fingerprint 稳定性、Provider-neutral 行为、FAIL 阻止 export、NavSea/CM95 两套 contract 同构。
- [ ] 文档全部中文;工具字段名/Schema key 可用英文。
- [ ] 更新 `STEP_RECORD.md`,但只追加本任务进度,禁止重写历史。
- [ ] 生成中文完成报告到 `docs/报告/PBF_Provider_Pipeline_v1_Codex完成报告_v1.md`。
## 3. 公开 Contract 的最低字段
Provider Package/Policy 至少覆盖:
- provider identity
- dataset identity
- package/version identity
- Style resource
- PBF resource
- Sprite
- Glyph
- NavSea Standard Layer mapping
- Provider Policy
- Display
- Pickup
- Planning
- Danger
- capabilities
- validation requirements
- hash / version / fingerprint
- unresolved
- limitations
- provenance
第三方不需要使用本仓库工具,只要满足公开 Contract 即可。
## 4. Provider-neutral 硬要求
正式 Pipeline 主逻辑不得:
- 根据 providerId 写 `if cm95` / `if navsea`
- 根据 source-layer 名称直接决定 NavSea Standard Layer
- 根据 Provider 字段名直接决定 NavSea Standard Layer
- 根据单个 icon 名称硬推完整对象语义
- 静默把 AMBIGUOUS/UNKNOWN 变成某个 Layer
Provider-specific 差异必须进入:
- manifest
- declarative mapping/policy
- provenance
- 已确认人工 assignment
## 5. 实际运行输入
### NavSea
Style 优先使用 X220 当前真实本地文件:
```text
/root/sourceserver/pbf/src/pbf/style.navsea-delivery-full-semantic-extentfix.json
```
该文件是实际当前 Style 输入,只用于运行测试,不要把原主工作区文件纳入本 worktree Git。
其 Style 当前包含:
- vector source `navsea_delivery`
- tile template `http://192.168.200.184/pbf-delivery-full-20260418-rebuild/{z}/{x}/{y}.pbf`
- sprite `http://192.168.200.184/newpec/sprite-semantic/sprite`
- glyphs `http://192.168.200.184/newpec/fonts/{fontstack}/{range}.pbf`
冻结 Standard Catalog:
```text
tools/style-visual-object/output/navsea-standard/standard-catalog.json
```
### CM95
Style:
```text
http://192.168.200.184:18889/s52/full-style.json
```
PBF:
```text
http://192.168.200.184:18889/pbf/{z}/{x}/{y}.pbf
```
Sprite:
```text
http://192.168.200.184:18889/s52/sprite
```
建议 sample tiles:
```text
12/3531/1641
12/3526/1643
12/3638/1613
```
CM95 人工权威 assignment:
```text
tools/style-visual-object/output/cm95-manual-review/assignment/cm95-layer-assignment-v1.json
```
## 6. NavSea sample tile
从 NavSea Style 的真实 vector source 自动获取 tile template。若 manifest 未指定 sample tile,CLI 应允许显式传 sample tiles。
为了本任务实际跑通,先通过 HEAD/GET 或目录可用性选择 1~3 个能成功下载的 NavSea z12 sample tile;不得伪造成功。如果某候选 404,继续选择真实存在的 tile,并在报告记录最终使用的 tile。
## 7. 依赖与运行
当前复制进 worktree 的旧 instantiation 脚本依赖 `mapbox_vector_tile` 和 Pillow;当前系统 Python 已观察到 `mapbox_vector_tile` 可能未安装。
要求:
- 明确声明 Python 依赖,例如独立 requirements 文件。
- 优先创建/使用 worktree 内 `.venv`,不要污染系统 Python。
- 依赖安装失败时明确报告,不得伪造运行成功。
- 输出/缓存目录应通过 .gitignore 避免把大体积临时 PBF/Sprite/cache 提交进 Git。
- 正式示例/小型 JSON 报告可提交;大 PBF、PNG cache 不提交。
## 8. 两套实际端到端验收
实现完成后必须实际运行:
### A. NavSea
目标:
```text
NavSea style + real PBF sample + frozen Standard Catalog
→ Provider Policy
→ validated NavSea Provider Package
```
必须至少证明:
- Style intake 成功
- Sprite 获取/快照成功
- 至少一个真实 vector PBF sample 成功
- PBF-03 实例化成功
- NavSea mapping 成功
- Policy Schema validate 成功
- Package Schema validate 成功
- Export 成功
- validation result 非 FAIL
### B. CM95
目标:
```text
CM95 full-style + real PBF sample + existing manual assignment
→ Provider Policy
→ validated CM95 Provider Package
```
必须至少证明:
- Style intake 成功
- Sprite 获取/快照成功
- 三个指定 sample tiles 尽可能全部成功;若单个不可用,明确记录
- PBF-03 实例化成功
- 使用现有人工 assignment,不能用 matcher 覆盖
- Policy Schema validate 成功
- Package Schema validate 成功
- Export 成功
- validation result 非 FAIL
## 9. 输出建议
建议正式输出:
```text
tools/provider-pipeline/
tools/provider-policy-compiler/
providers/navsea/
providers/cm95/
contracts/
dist/
```
具体目录可根据仓库现状调整,但职责必须清晰。
最终至少保留可复现的 manifest/example 配置,让后续可再次执行两套 Provider。
## 10. Git 与工作区约束
当前 worktree 是隔离分支:
```text
feature/provider-pipeline-v1
```
不要访问或修改主工作区中的既存脏文件,除“读取 NavSea style 输入”外。
只提交本任务新增/修改文件。
完成前执行:
- 相关 Python tests
- CLI help/基础 smoke
- NavSea 实际 Pipeline
- CM95 实际 Pipeline
- `git diff --check`
- `git status --short`
成功后在 feature branch 创建清晰 commit。不要自行 merge main;由外部流程审核后处理。
## 11. 完成报告
`docs/报告/PBF_Provider_Pipeline_v1_Codex完成报告_v1.md` 必须逐项报告:
- 已完成 / 未完成 / 无法验证
- 实际命令
- NavSea 运行结果
- CM95 运行结果
- 每阶段产物
- Schema/Contract 版本
- validation result
- warnings / unresolved / limitations
- tests
- Git commit
- 最终实际使用的完整 Codex 模型配置(若运行环境可确认)
不得把“测试未跑”写成“通过”。

View File

@@ -0,0 +1,265 @@
# PBF Provider Pipeline v1 完成报告 v2
日期:2026-10-06
分支:`feature/provider-pipeline-v1`
状态:**PBF-01~PBF-07 已实现;NavSea 与 CM95 两套真实 Style/PBF/Sprite 端到端运行完成。**
## 1. 最终结论
PBF Provider Pipeline v1 已完成。正式边界保持为:
```text
Provider Source
→ PBF-01 Intake
→ PBF-02 Style Visual Object
→ PBF-03 Actual Visual Object
→ PBF-04 NavSea Standard Mapping
→ PBF-05 Provider Policy
→ PBF-06 Provider Package Validate
→ PBF-07 Provider Package Export
```
Pipeline 的终点是 `NavSea Provider Package`,没有生成 `ProviderExecutionPlan`。
两套真实验收均完成:
- NavSea:`PASS_WITH_WARNINGS`
- CM95:`PASS_WITH_WARNINGS`
两者 warnings 均为 `mapping:unresolved_present` 与 `limitations:present`。这表示仍有未知/歧义 Mapping,以及本轮只使用三个样本瓦片;不是 Pipeline 执行错误,也没有把 UNKNOWN/AMBIGUOUS 静默映射成某个 Layer。
## 2. 实现内容
已加入正式 Provider-neutral Pipeline runner、Provider Policy / Package v1 Schema 与 validator、NavSea/CM95 manifest、自动测试、依赖说明及中文 README。
现有 `tools/visual-object-instantiation/` 已正式纳入 Git,并移除主逻辑固定 CM95 provider/catalog 名称。PBF-03 额外输出 occurrence 级追溯,包括 tile、feature identity、属性证据、matched style layers、geometry 和 render components。
正式 Mapping 状态统一为:
- `VISUAL_OBJECT_MATCH`
- `LAYER_ONLY`
- `COMPONENT_ONLY`
- `AMBIGUOUS`
- `UNKNOWN`
`FAIL` 或其他非允许 validation 状态禁止 PBF-07 export。
## 3. NavSea 真实端到端结果
分析输入仍使用 X220 当前本地 Style:
```text
/root/sourceserver/pbf/src/pbf/style.navsea-delivery-full-semantic-extentfix.json
```
Style SHA-256:
```text
2ffde6af6043be2567a861a8d06d075a87b532091686bb5ac6ce889e45827e33
```
原 Style 内旧 LAN `192.168.200.184:80` 的 PBF/Sprite 服务当前拒绝连接。为保持本地 Style 内容不变,同时完成真实资源验收,`providers/navsea/manifest.json` 将资源显式覆盖到同数据集当前可用的公开 HTTPS 入口:
```text
PBF: https://pbf.jpcn.ccwu.cc/pbf/pbf-delivery-full-20260418-rebuild/{z}/{x}/{y}.pbf
Sprite: https://pbf.jpcn.ccwu.cc/sprite/sprite
Glyph: https://pbf.jpcn.ccwu.cc/fonts/{fontstack}/{range}.pbf
```
因此本轮没有用公开 Style 替换本地 Style;Style 分析输入仍是上述 X220 本地文件,资源获取由 Provider manifest 显式声明。
NavSea Source fingerprint:
```text
27a09da2a31746cff8e874fb5cd48bef7226c322fb2f193bc1b7fa5a01187f9c
```
NavSea Sprite:
- JSON SHA-256:`e48aed662580ad437a5270966acdc906039b0d97da7892d518710d10f518f75c`
- PNG SHA-256:`fca794509a41eda3f4a9fe6db5e39467aac7cdda07de553c97a33e31b4c7b981`
三个真实 z12 PBF 样本全部成功:
| XYZ | 字节数 | SHA-256 |
|---|---:|---|
| `12/3531/1641` | 181,263 | `99018718a886cd5af73554f9b453a476308a51e38591758ef2b1eaf723c6486a` |
| `12/3526/1643` | 119,246 | `8f32aa41ab9af90122b45bda0bed9f02faed1c10e0eb3ab09109e88882704922` |
| `12/3638/1613` | 305,970 | `25373c570684e5b06a6cdea56e8202541aab5c6aa4ec885b23c39bb9d807fd5c` |
PBF-03:
- decoded styled-layer feature:8,486
- matched occurrence:8,484
- occurrence 输出:8,484
- distinct Actual Visual Object:85
- group:34
- unsupported component:0
- missing sprite component:48
PBF-04 Mapping 共 85 个 Actual Visual Object:
- VISUAL_OBJECT_MATCH:27
- LAYER_ONLY:7
- AMBIGUOUS:11
- UNKNOWN:40
- COMPONENT_ONLY:0
阶段结果:
```text
PBF-01 PASS
PBF-02 PASS
PBF-03 PASS
PBF-04 PASS
PBF-05 PASS
PBF-06 PASS_WITH_WARNINGS
PBF-07 EXPORTED
```
导出目录:
```text
dist/navsea/delivery-full-semantic-extentfix/1.0.0/
```
## 4. CM95 真实端到端结果
Style:
```text
http://192.168.200.184:18889/s52/full-style.json
```
Style SHA-256:
```text
b1d3d0060012a8e4b55a6053231231d742ec415b3a8398f9ae0867bbdfa5290b
```
Source fingerprint:
```text
aa42eb9fd575dde1b651d537ccc5b52c9dafac1bd8a3d6c5166c49a9581c9363
```
Sprite:
- JSON SHA-256:`51519fb9a64f11dc100f894b2b54d14f56770ac9d511f15ae574ff5867af857d`
- PNG SHA-256:`90cf4da31e01abb0676b538ebd2b5c951b6d737a31d23062ec6eb06383d729a2`
Style 未声明 Glyph,Package 明确记录 Glyph 不可用并保持 `capabilities.glyphs=false`。
三个指定真实 z12 PBF 样本全部成功:
| XYZ | 字节数 | SHA-256 |
|---|---:|---|
| `12/3531/1641` | 423,777 | `1b9521e5dd990c92c71f22a4d56bfbd1ba33e5f236ddbf2af5ce51191e2efab8` |
| `12/3526/1643` | 285,154 | `92147b621b213a0c1fe80c2fa28ab71292b79d133b98272977defb41424ec39c` |
| `12/3638/1613` | 82,075 | `7eeb420fe809b09f8e03b892668830efd3db3e957bf32a80fd1b92b2d90e1dba` |
PBF-03:
- decoded styled-layer feature:5,210
- matched occurrence:2,077
- occurrence 输出:2,077
- distinct Actual Visual Object:102
- group:25
- unsupported component:0
- missing sprite component:0
PBF-04 使用现有 CM95 人工 assignment,没有由 matcher 覆盖人工结论。Mapping 共 102 个 Actual Visual Object:
- VISUAL_OBJECT_MATCH:9
- LAYER_ONLY:47
- AMBIGUOUS:2
- UNKNOWN:44
- COMPONENT_ONLY:0
阶段结果:
```text
PBF-01 PASS
PBF-02 PASS
PBF-03 PASS
PBF-04 PASS
PBF-05 PASS
PBF-06 PASS_WITH_WARNINGS
PBF-07 EXPORTED
```
导出目录:
```text
dist/cm95/s52-full/1.0.0/
```
## 5. 验证
自动测试:
```text
.venv/bin/python -m unittest discover -s tests -v
```
结果:6/6 PASS。
CM95 固定分类基线:
```text
.venv/bin/python tools/style-visual-object/validate_cm95_classification.py
```
结果:`RESULT: PASS`。既有 7 条人工决定与 S-52 语义建议不一致 WARN 保留,WARN 不覆盖人工结果。
其他检查:
- Python compileall:PASS
- Pipeline CLI help:PASS
- `git diff --check`:PASS
- NavSea fresh end-to-end run:PASS_WITH_WARNINGS / EXPORTED
- CM95 fresh end-to-end run:PASS_WITH_WARNINGS / EXPORTED
## 6. Git 与 Codex
Pipeline 正式流程文档此前已保存到 main:
- `f3da1a6 docs: define PBF provider pipeline v1`
Codex 实现阶段:
- 实现提交:`aea9862 feat: implement provider-neutral PBF pipeline v1`
- 初版完成报告提交:`804a764 docs: record provider pipeline v1 completion`
随后由外部 X220 shell 完成 NavSea HTTPS 资源入口修正及 NavSea/CM95 fresh end-to-end 验收。本报告 v2 记录最终实际状态。
Codex 本轮实际启动横幅确认配置:
```text
Model: GPT-6 Luna
Reasoning effort: High
```
## 7. 当前限制
1. 两套验收都只采样三个 z12 tile,不代表全量数据覆盖。
2. NavSea 本次 85 个 Actual Visual Object 中仍有 UNKNOWN/AMBIGUOUS;这些状态被明确保留,没有猜测 Layer。
3. CM95 本次 102 个 Actual Visual Object 中仍有 44 UNKNOWN、2 AMBIGUOUS;仍以现有人工 assignment 为最终语义权威。
4. `dist/` 与 `.tmp/` 为可重建运行产物,按设计不纳入 Git。
5. NavSea 本地 Style 内仍保留旧 LAN URL;Pipeline 通过 Provider manifest 的资源覆盖使用当前可用公开 HTTPS 镜像。后续若 Style 本体正式更新 URL,可再移除 manifest 覆盖。
## 8. 复现命令
NavSea:
```bash
.venv/bin/python tools/provider-pipeline/run_provider_pipeline.py \
--manifest providers/navsea/manifest.json
```
CM95:
```bash
.venv/bin/python tools/provider-pipeline/run_provider_pipeline.py \
--manifest providers/cm95/manifest.json
```

View File

@@ -0,0 +1,141 @@
# PBF Provider Pipeline v1 完成报告 v3
日期:2026-10-06
分支:`feature/provider-pipeline-v1`
主题:PBF-08 NavSea Delivery ZIP
## 结论
在既有 PBF-01~PBF-07 基础上新增 PBF-08。PBF-07 仍负责导出可审计的 Provider Package 目录,PBF-08 自动把 NavSea 最终正式输入打成一个单文件 `.navsea-provider.zip`。
用户后续向 NavSea 交付时,不再需要手工选择目录中的多个文件,只需要复制对应 Provider 的 ZIP。
## PBF-08 白名单
ZIP 固定且只包含 9 个文件:
```text
provider-package.json
provider-policy.json
mapping-layer-assignment.json
resources-style.json
resources-sprite.json
resources-sprite.png
validation.json
validation.md
MANIFEST.json
```
任何白名单文件缺失时,PBF-08 直接 FAIL,不生成 ZIP。
ZIP 生成后自动执行:
1. 精确文件列表校验;
2. ZIP CRC 校验;
3. ZIP SHA-256 计算;
4. 将 PBF-08 状态、路径、文件名、SHA-256、字节数、内部根目录和文件列表写入 `pipeline-report.json`;
5. CLI 输出 `deliveryZip` 和 `deliveryZipSha256`。
临时 PBF、`.tmp`、工具源码、测试、人工审核中间文件以及其他未声明文件不会进入 ZIP。
## 自动测试
```text
.venv/bin/python -m unittest discover -s tests -v
```
结果:8/8 PASS。
新增覆盖:
- ZIP 只包含规定的 NavSea 交付文件;
- 多余文件不会进入 ZIP;
- 缺少任意必需文件时拒绝生成 ZIP。
Python compileall:PASS。
`git diff --check`:PASS。
## NavSea 实际结果
Pipeline:
```text
PBF-01 PASS
PBF-02 PASS
PBF-03 PASS
PBF-04 PASS
PBF-05 PASS
PBF-06 PASS_WITH_WARNINGS
PBF-07 EXPORTED
PBF-08 PACKAGED
```
ZIP:
```text
dist/navsea/delivery-full-semantic-extentfix/
navsea-delivery-full-semantic-extentfix-1.0.0.navsea-provider.zip
```
ZIP SHA-256:
```text
54aff8d8b882211ced6e77b3a93e8e793c780b44a1faa982ed8fc64693124fd5
```
ZIP 大小:933,404 bytes。
外部 `unzip -t`:9 个文件全部 OK,无压缩数据错误。
## CM95 实际结果
Pipeline:
```text
PBF-01 PASS
PBF-02 PASS
PBF-03 PASS
PBF-04 PASS
PBF-05 PASS
PBF-06 PASS_WITH_WARNINGS
PBF-07 EXPORTED
PBF-08 PACKAGED
```
ZIP:
```text
dist/cm95/s52-full/
cm95-s52-full-1.0.0.navsea-provider.zip
```
ZIP SHA-256:
```text
603cc004833bcff5571a32843d8ceedc7858b2fc4b28c41056db6807c7d511ee
```
ZIP 大小:742,008 bytes。
外部 `unzip -t`:9 个文件全部 OK,无压缩数据错误。
## 使用规则
以后 PBF Pipeline 正常执行完成后:
```bash
.venv/bin/python tools/provider-pipeline/run_provider_pipeline.py \
--manifest providers/<provider>/manifest.json
```
只要 PBF-06 为 `PASS` 或 `PASS_WITH_WARNINGS`,Pipeline 会自动完成 PBF-07 和 PBF-08。
交给 NavSea 时,只复制 CLI 输出的:
```text
deliveryZip
```
即可。
`--no-export` 模式继续只运行到 Package 校验,PBF-07 与 PBF-08 都会标记为 `SKIPPED_BY_REQUEST`,不会生成 ZIP。

View File

@@ -1,23 +1,71 @@
{
"schemaVersion": "pbf-provider-source-manifest/v1",
"provider": {"providerId": "navsea", "name": "NavSea", "version": "1"},
"dataset": {"datasetId": "delivery-full-semantic-extentfix", "name": "Delivery Full Semantic ExtentFix"},
"package": {"packageId": "navsea-delivery-full", "version": "1.0.0"},
"provider": {
"providerId": "navsea",
"name": "NavSea",
"version": "1"
},
"dataset": {
"datasetId": "delivery-full-semantic-extentfix",
"name": "Delivery Full Semantic ExtentFix"
},
"package": {
"packageId": "navsea-delivery-full",
"version": "1.0.0"
},
"resources": {
"style": {"uri": "/root/sourceserver/pbf/src/pbf/style.navsea-delivery-full-semantic-extentfix.json"},
"sprite": {"uri": "http://192.168.200.184/newpec/sprite-semantic/sprite"},
"glyphs": {"uri": "http://192.168.200.184/newpec/fonts/{fontstack}/{range}.pbf"}
"style": {
"uri": "/root/sourceserver/pbf/src/pbf/style.navsea-delivery-full-semantic-extentfix.json"
},
"sprite": {
"uri": "https://pbf.jpcn.ccwu.cc/sprite/sprite"
},
"glyphs": {
"uri": "https://pbf.jpcn.ccwu.cc/fonts/{fontstack}/{range}.pbf"
}
},
"tileSource": {"sourceId": "navsea_delivery"},
"sampleTileCandidates": ["12/3531/1641", "12/3526/1643", "12/3638/1613"],
"tileSource": {
"sourceId": "navsea_delivery",
"template": "https://pbf.jpcn.ccwu.cc/pbf/pbf-delivery-full-20260418-rebuild/{z}/{x}/{y}.pbf"
},
"sampleTileCandidates": [
"12/3531/1641",
"12/3526/1643",
"12/3638/1613"
],
"probeGlyph": false,
"mapping": {"mode": "frozen-standard-visual", "standardCatalog": "../../tools/style-visual-object/output/navsea-standard/standard-catalog.json"},
"policy": {
"display": {"enabled": true, "source": "provider-style"},
"pickup": {"enabled": false, "rules": []},
"planning": {"enabled": false, "rules": []},
"danger": {"enabled": false, "rules": []},
"capabilities": {"vectorTiles": true, "sprite": true, "glyphs": true, "display": true, "pickup": false, "planning": false, "danger": false}
"mapping": {
"mode": "frozen-standard-visual",
"standardCatalog": "../../tools/style-visual-object/output/navsea-standard/standard-catalog.json"
},
"limitations": ["只使用 manifest 列出的 z12 样本瓦片进行实例化;未据此推断全数据集覆盖率。"]
"policy": {
"display": {
"enabled": true,
"source": "provider-style"
},
"pickup": {
"enabled": false,
"rules": []
},
"planning": {
"enabled": false,
"rules": []
},
"danger": {
"enabled": false,
"rules": []
},
"capabilities": {
"vectorTiles": true,
"sprite": true,
"glyphs": true,
"display": true,
"pickup": false,
"planning": false,
"danger": false
}
},
"limitations": [
"只使用 manifest 列出的 z12 样本瓦片进行实例化;未据此推断全数据集覆盖率。"
]
}

View File

@@ -4,11 +4,12 @@ import json
import sys
import tempfile
import unittest
import zipfile
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "tools/provider-policy-compiler"))
from provider_policy import build_policy, export_package, fingerprint, validate_package, validate_policy
from provider_policy import DELIVERY_FILES, build_policy, create_delivery_zip, export_package, fingerprint, validate_package, validate_policy
class ProviderPipelineContractTests(unittest.TestCase):
@@ -70,6 +71,42 @@ class ProviderPipelineContractTests(unittest.TestCase):
self.assertIn("style", manifest["resources"])
self.assertIn("sprite", manifest["resources"])
def test_delivery_zip_contains_only_required_navsea_files(self):
with tempfile.TemporaryDirectory() as tmp:
export_dir = Path(tmp) / "1.0.0"
export_dir.mkdir()
for name in DELIVERY_FILES:
path = export_dir / name
if name == "validation.json":
path.write_text(json.dumps({"result": "PASS_WITH_WARNINGS"}), encoding="utf-8")
elif name.endswith(".png"):
path.write_bytes(b"png")
else:
path.write_text(name, encoding="utf-8")
(export_dir / "not-for-navsea.tmp").write_text("exclude me", encoding="utf-8")
info = create_delivery_zip(export_dir, "provider-a", "dataset-1", "1.0.0")
self.assertEqual(info["validationResult"], "PASS_WITH_WARNINGS")
self.assertEqual(len(info["sha256"]), 64)
with zipfile.ZipFile(info["path"], "r") as archive:
expected = [f"provider-a-dataset-1-1.0.0/{name}" for name in DELIVERY_FILES]
self.assertEqual(archive.namelist(), expected)
self.assertNotIn("provider-a-dataset-1-1.0.0/not-for-navsea.tmp", archive.namelist())
def test_delivery_zip_fails_when_required_file_is_missing(self):
with tempfile.TemporaryDirectory() as tmp:
export_dir = Path(tmp) / "1.0.0"
export_dir.mkdir()
for name in DELIVERY_FILES:
if name == "resources-sprite.png":
continue
path = export_dir / name
if name == "validation.json":
path.write_text(json.dumps({"result": "PASS"}), encoding="utf-8")
else:
path.write_text(name, encoding="utf-8")
with self.assertRaises(ValueError):
create_delivery_zip(export_dir, "provider-a", "dataset-1", "1.0.0")
if __name__ == "__main__":
unittest.main()

View File

@@ -9,8 +9,41 @@ python tools/provider-pipeline/run_provider_pipeline.py --manifest providers/nav
python tools/provider-pipeline/run_provider_pipeline.py --manifest providers/cm95/manifest.json
```
也可重复传入 `--sample-tile z/x/y` 覆盖 manifest 中的样本候选;Pipeline 复用 manifest 声明的现有人工 assignment 或冻结 Catalog,不会用自动 matcher 覆盖审核结果。已有视觉目录和实例化目录会从 `.tmp/provider-pipeline/` 复用;需要从头重算时删除对应 work 目录。`--no-export` 只运行到 Package 校验。
也可重复传入 `--sample-tile z/x/y` 覆盖 manifest 中的样本候选。Pipeline 复用 manifest 声明的现有人工 assignment 或冻结 Catalog,不会用自动 matcher 覆盖审核结果。已有视觉目录和实例化目录会从 `.tmp/provider-pipeline/` 复用;需要从头重算时删除对应 work 目录。`--no-export` 只运行到 Package 校验,因此不会执行 PBF-07 和 PBF-08。
PBF-01 保存原 Style、Sprite 和真实 PBF 样本并计算 SHA-256 / source fingerprint。PBF-02 直接复用现有 Style Visual Object Tool。PBF-03 接收 provider/dataset 参数并为每个 feature 写 occurrence 及可追溯证据。PBF-04 从冻结 Standard Catalog 的视觉 hash 对照,或读取声明的人工 assignment。PBF-05/06 使用共享契约和校验逻辑。只有 PASS 或 PASS_WITH_WARNINGS 才能 PBF-07 导出。
## 阶段
输出目录默认 `dist/<provider>/<dataset>/<version>/`,运行缓存位于 `.tmp/`,不纳入 Git。Pipeline 不生成 ProviderExecutionPlan。
PBF-01 保存原 Style、Sprite 和真实 PBF 样本并计算 SHA-256 / source fingerprint。PBF-02 复用现有 Style Visual Object Tool。PBF-03 接收 provider/dataset 参数并为每个 feature 写 occurrence 及可追溯证据。PBF-04 从冻结 Standard Catalog 的视觉 hash 对照,或读取声明的人工 assignment。PBF-05/06 使用共享契约和校验逻辑。只有 PASS 或 PASS_WITH_WARNINGS 才能 PBF-07 导出。
PBF-08 在 PBF-07 成功后自动生成一个可直接交给 NavSea 的单文件 ZIP。ZIP 使用严格白名单,缺少任何规定文件都会直接失败;临时 PBF、.tmp、工具源码、人工审核中间文件和其他未声明文件不会进入 ZIP。
PBF-08 ZIP 固定包含:
```text
provider-package.json
provider-policy.json
mapping-layer-assignment.json
resources-style.json
resources-sprite.json
resources-sprite.png
validation.json
validation.md
MANIFEST.json
```
默认输出:
```text
dist/<provider>/<dataset>/
<provider>-<dataset>-<version>.navsea-provider.zip
```
ZIP 内部还有一个同名顶层目录,避免解压多个 Provider 包时文件混在一起。Pipeline 在完成 ZIP 后会重新打开 ZIP,核对文件列表、CRC,并计算 ZIP SHA-256;这些信息写入 `pipeline-report.json` 的 `PBF-08` 节点,同时打印 `deliveryZip` 和 `deliveryZipSha256`。
PBF-07 的可审计目录仍保留在:
```text
dist/<provider>/<dataset>/<version>/
```
运行缓存位于 `.tmp/`,不纳入 Git。Pipeline 不生成 ProviderExecutionPlan。

View File

@@ -17,7 +17,7 @@ from typing import Any
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT / "tools" / "provider-policy-compiler"))
from provider_policy import build_package, build_policy, fingerprint, validate_package, validate_policy, export_package
from provider_policy import build_package, build_policy, create_delivery_zip, fingerprint, validate_package, validate_policy, export_package
def read_resource(location: str, timeout: int = 60) -> bytes:
@@ -279,7 +279,7 @@ def main() -> int:
"capabilities": manifest.get("policy", {}).get("capabilities", {}),
"validationRequirements": {"requireStyle": True, "requirePbfSample": True, "requireSprite": True, "minimumMappingCount": 1},
"unresolved": unresolved, "limitations": manifest.get("limitations", []),
"provenance": {"manifest": str(manifest_path), "mappingSource": mapping_source, "pipelineVersion": "1.0.0", "generatedAt": datetime.now(timezone.utc).isoformat()},
"provenance": {"manifest": str(manifest_path), "mappingSource": mapping_source, "pipelineVersion": "1.1.0", "generatedAt": datetime.now(timezone.utc).isoformat()},
"fingerprints": fingerprints,
}
record["stages"]["PBF-05"] = {"status": "PASS", "schemaVersion": "navsea-provider-policy/v1"}
@@ -301,11 +301,15 @@ def main() -> int:
shutil.copy2(intake / "sprite/sprite.png", export_dir / "resources-sprite.png")
shutil.copy2(mapping_path, export_dir / "mapping-layer-assignment.json")
record["stages"]["PBF-07"] = {"status": "EXPORTED", "path": str(export_dir)}
delivery_zip = create_delivery_zip(export_dir, provider_id, dataset_id, version)
record["stages"]["PBF-08"] = {"status": "PACKAGED", **delivery_zip}
else:
delivery_zip = None
record["stages"]["PBF-07"] = {"status": "SKIPPED_BY_REQUEST"}
record["stages"]["PBF-08"] = {"status": "SKIPPED_BY_REQUEST"}
report_path = work / "pipeline-report.json"
report_path.write_text(json.dumps(record, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
print(json.dumps({"providerId": provider_id, "datasetId": dataset_id, "validationResult": validation["result"], "sampleTiles": [x["tile"] for x in successful_tiles], "output": None if args.no_export else str(export_dir), "report": str(report_path)}, ensure_ascii=False, indent=2))
print(json.dumps({"providerId": provider_id, "datasetId": dataset_id, "validationResult": validation["result"], "sampleTiles": [x["tile"] for x in successful_tiles], "output": None if args.no_export else str(export_dir), "deliveryZip": None if delivery_zip is None else delivery_zip["path"], "deliveryZipSha256": None if delivery_zip is None else delivery_zip["sha256"], "report": str(report_path)}, ensure_ascii=False, indent=2))
return 0 if validation["result"] != "FAIL" else 2

View File

@@ -4,6 +4,8 @@ from __future__ import annotations
import hashlib
import json
import re
import zipfile
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
@@ -12,6 +14,18 @@ POLICY_SCHEMA = "navsea-provider-policy/v1"
PACKAGE_SCHEMA = "navsea-provider-package/v1"
ASSIGNMENT_STATUSES = {"VISUAL_OBJECT_MATCH", "LAYER_ONLY", "COMPONENT_ONLY", "AMBIGUOUS", "UNKNOWN"}
DELIVERY_FILES = (
"provider-package.json",
"provider-policy.json",
"mapping-layer-assignment.json",
"resources-style.json",
"resources-sprite.json",
"resources-sprite.png",
"validation.json",
"validation.md",
"MANIFEST.json",
)
def canonical(value: Any) -> bytes:
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode("utf-8")
@@ -160,3 +174,51 @@ def export_package(output: Path, package: dict[str, Any], policy: dict[str, Any]
}
(output / "MANIFEST.json").write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
return output
def _safe_delivery_component(value: str) -> str:
cleaned = re.sub(r"[^A-Za-z0-9._-]+", "-", str(value)).strip("-._")
return cleaned or "provider"
def create_delivery_zip(export_dir: Path, provider_id: str, dataset_id: str, version: str) -> dict[str, Any]:
"""把 PBF-07 的正式 NavSea 输入打成一个严格白名单 ZIP。"""
export_dir = Path(export_dir)
missing = [name for name in DELIVERY_FILES if not (export_dir / name).is_file()]
if missing:
raise ValueError(f"PBF-08 缺少正式交付文件,禁止生成 ZIP: {missing}")
validation = json.loads((export_dir / "validation.json").read_text(encoding="utf-8"))
if validation.get("result") not in {"PASS", "PASS_WITH_WARNINGS"}:
raise ValueError(f"PBF-08 只接受 PASS/PASS_WITH_WARNINGS,当前为 {validation.get('result')}")
stem = "-".join(_safe_delivery_component(x) for x in (provider_id, dataset_id, version))
zip_path = export_dir.parent / f"{stem}.navsea-provider.zip"
root_name = stem
if zip_path.exists():
zip_path.unlink()
with zipfile.ZipFile(zip_path, "w", compression=zipfile.ZIP_DEFLATED, compresslevel=9) as archive:
for name in DELIVERY_FILES:
archive.write(export_dir / name, arcname=f"{root_name}/{name}")
with zipfile.ZipFile(zip_path, "r") as archive:
actual = archive.namelist()
expected = [f"{root_name}/{name}" for name in DELIVERY_FILES]
if actual != expected:
zip_path.unlink(missing_ok=True)
raise ValueError(f"PBF-08 ZIP 内容校验失败: expected={expected}, actual={actual}")
bad = archive.testzip()
if bad is not None:
zip_path.unlink(missing_ok=True)
raise ValueError(f"PBF-08 ZIP CRC 校验失败: {bad}")
return {
"path": str(zip_path),
"filename": zip_path.name,
"sha256": file_fingerprint(zip_path),
"bytes": zip_path.stat().st_size,
"root": root_name,
"files": list(DELIVERY_FILES),
"validationResult": validation["result"],
}