From 979a1deeb4e6abe24c910c9f17248fd5c6c0aff8 Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Fri, 4 Sep 2026 15:59:17 +0800 Subject: [PATCH] docs: investigate next production field source stage --- ...TEP-052_正式数据源接入下一阶段调查报告_v1.md | 225 ++++++++++++++++++ ...£式数据源接入下一阶段调查_Codex完成报告_v1.md | 72 ++++++ 2 files changed, 297 insertions(+) create mode 100644 docs/20_调查/STEP-052_正式数据源接入下一阶段调查报告_v1.md create mode 100644 docs/报告/STEP-052_正式数据源接入下一阶段调查_Codex完成报告_v1.md diff --git a/docs/20_调查/STEP-052_正式数据源接入下一阶段调查报告_v1.md b/docs/20_调查/STEP-052_正式数据源接入下一阶段调查报告_v1.md new file mode 100644 index 000000000..8ad0ff0f5 --- /dev/null +++ b/docs/20_调查/STEP-052_正式数据源接入下一阶段调查报告_v1.md @@ -0,0 +1,225 @@ +# STEP-052 正式数据源接入下一阶段调查报告 + +## 1. Git / 调查基线 + +- 仓库:`MapLibreParticleLab` +- 分支:`particle/main` +- Starting HEAD:`e4ab1b9d421a4559fa3627df07ee91dcd9b680e4` +- `origin/particle/main`:`e4ab1b9d421a4559fa3627df07ee91dcd9b680e4` +- STEP-049:`CLOSED / PASS` +- STEP-051:`CLOSED / PASS` +- 调查性质:代码与既有文档只读;本任务不修改功能代码。 + +## 2. 结论摘要 + +STEP-051 已把一条真实可验证的 local production-like 链路接到 Core: + +`asset metadata + binary payload → LocalParticleFieldFeed → ParticleFieldDescriptor → ParticleFieldDataAdapter → ParticleFieldSnapshot → RasterArraySource → Core Timeline/Runtime Field Window → Renderer`。 + +但它仍不是正式生产数据源。当前 local metadata 是新的 `maplibre-particle-field/v1`,payload 仍复用仓库已有的 canonical `particle-fixtures/*/field.f32`;`RasterArraySource` 直接拥有资源请求、candidate 收集、解析和 Timeline 初始化。仓库没有 Particle 专属的 feed coordinator、forecast catalog/manifest、异步 field-arrival API、retry/timeout/stale policy 或专用 transport cache。 + +MapLibre 通用 `FileSource` 已提供 asset、filesystem、HTTP、offline database 等资源能力,但这不等于已有 Weather Service。当前最合理的下一实现阶段不是直接绑定天气供应商,而是先建立 provider-neutral feed/manifest 与低频 arrival coordinator,并用 Local/File/Mock transport 验证。之后再单独接入真实 HTTP/provider。 + +## 3. STEP-051 后的真实数据链路 + +### 3.1 Local feed 起点与终点 + +入口和 contract: + +- `include/mbgl/particle/field/local_particle_field_feed.hpp` +- `src/mbgl/particle/field/local_particle_field_feed.cpp` +- metadata:`particle-production/field-00.json` 至 `field-03.json` +- Android metadata:`platform/android/MapLibreAndroidTestApp/src/main/assets/integration/particle-production/` + +`LocalParticleFieldFeed::load(metadataText, payload)` 只处理 provider-neutral metadata 和内存 bytes,调用唯一正式 `ParticleFieldDataAdapter::decode()`。它不打开文件、不请求网络、不保存缓存,也不认识 Android、Timeline 或 Renderer。 + +STEP-051 的 Android `ParticleCoreLayerValidationActivity.kt` 使用 `production-fields` style source,metadata 走 `asset://integration/particle-production/field-*.json`,binary 仍来自既有 `asset://integration/particle-fixtures/*/field.f32`。这已经脱离 fixture metadata/wrapper,但 payload 内容尚是验证用 canonical binary,而非远程生产数据。 + +### 3.2 Provider-neutral 边界 + +正式 neutral 边界位于: + +`ParticleFieldDescriptor + payload bytes → ParticleFieldDataAdapter → immutable ParticleFieldSnapshot`。 + +`include/mbgl/particle/field/field_data_adapter.hpp` 的 Descriptor/Snapshot 不含 URL、token、HTTP client、provider name、Android object 或 GPU ownership。Descriptor/Snapshot 当前满足 WIND/GLOBAL 的 Runtime 最小输入;Ocean-only field、mask、更多物理变量尚未进入本阶段 contract。 + +当前仍留在上游/Source 的内容是资源 URL、metadata 文本、文件请求、asset 读取和 candidate 编排。它们不应进入 `RasterParticleLayer` 或 `ParticleFieldSnapshot`。 + +## 4. Adapter、Snapshot 与 Source 现状 + +### 4.1 Adapter / Snapshot + +- `ParticleFieldDataAdapter::decode(const ParticleFieldDescriptor&, const std::vector&, uint64_t)` 是唯一正式 decode API。 +- `ParticleFieldDescriptor` 已有 `fieldID`、`fieldType`、`displayDomain`、`generation`、`validTime`、dimensions、bounds、`noData`、`dataVersion`、`byteLength`、stride、layout、encoding、可选 checksum。 +- `ParticleFieldSnapshot` 自有 metadata、velocity 和 valid arrays;不拥有外部 bytes、FileSource、GPU texture 或 Timeline。 +- generation 必须由 producer/source 在 adapter 前提供,adapter 不把 0 改成 1;`fieldId` 与 `validTime` 由 producer metadata 提供。 +- `OfflineFixtureParticleAdapter` 仍存在,但只是 fixture-v1 validation wrapper,不是正式 production feed。 + +### 4.2 RasterArraySource / submit + +实际文件: + +- `include/mbgl/style/sources/raster_array_source.hpp` +- `src/mbgl/style/sources/raster_array_source.cpp` +- `src/mbgl/style/sources/raster_array_source_impl.hpp` +- `src/mbgl/style/conversion/source.cpp` + +STEP-051 新增 `RasterArrayLocalFieldDescriptor`、local candidates 和 `submitInitialRuntimeFieldWindow(T0, T1)`。Source 对每个 metadata/field URL 使用 `FileSource::request(Resource::source(...))`,收齐后交给 `LocalParticleFieldFeed`,再由 bridge 建立 authoritative initial T0/T1。后续 Timeline 从 local candidates 构造 entries,使用 Snapshot 的 producer `validTime`。 + +已有 `updateRuntimeFieldWindow(ParticleFieldSnapshot)` 是已有 T0/T1 之后的最短 submit 路径,继续实现 `OLD_T1 → NEW_T0`、only NEW_T1、Duplicate NOOP 和 fail-closed。它不是一个独立的外部 feed queue,也没有 feed-level arrival state、manifest index 或持久化生命周期。 + +### 4.3 Timeline / Playback + +`RasterArrayTimelineRuntime` 位于 `raster_array_source.hpp/.cpp`,只消费 immutable Snapshot entries 和严格递增 timestamp,持有 Core-owned cursor/speed/paused/gap/invalid 状态;cache capacity 当前为 8。 + +这属于 Timeline memory cache,不是 transport/download cache,也不是 persistent field cache。Timeline 已足够消费外部 Snapshot,下一阶段不应重写其 state machine 或 Playback ownership。缺口在 Source/feed producer 如何按时间目录、到达、失败和预取 Snapshot。 + +## 5. 仓库已有 transport / network / cache 能力 + +### 已有但属于 MapLibre 通用基础设施 + +- `include/mbgl/storage/file_source.hpp`:异步 `request()`、`Resource`、cache-only 能力、pause/resume 和资源转发。 +- `include/mbgl/storage/resource.hpp`:`asset/file/network` 资源请求语义、loading method、storage policy、etag/expiry 等 HTTP/cache 元数据。 +- `src/mbgl/storage/file_source_manager.cpp`:按类型与配置复用 FileSource 实例。 +- `platform/android/MapLibreAndroid/src/cpp/asset_manager_file_source.cpp`:Android `asset://` 读取。 +- `platform/android/MapLibreAndroid/src/cpp/http_file_source.cpp` 与 `include/mbgl/storage/http_file_source.hpp`:Android 通用 HTTP FileSource。 +- `include/mbgl/storage/database_file_source.hpp`:MapLibre ambient cache/offline database 的 put、invalidate、clear、容量和 revalidation API。 +- `include/mbgl/util/lru_cache.hpp`:通用内存 LRU 工具。 +- `include/mbgl/storage/response.hpp`:通用成功、NotFound、Server、Connection、RateLimit、etag、expires、retryAfter 等 Response/Error 字段。 + +这些能力可以作为 STEP-053 的底层 transport 适配点,但目前没有 Particle field 的 manifest、字段语义、时间序列目录、payload checksum policy、retry/stale policy 或 adapter 错误映射。 + +### 当前不存在的 Particle 正式能力 + +搜索当前 `src/`、`include/`、Android JNI/Host 和测试区域,没有发现: + +- Weather Service client、Open-Meteo/Windy/vendor client; +- Particle provider abstraction、provider registry 或 provider-specific decoder; +- GRIB/NetCDF/external weather decoder; +- Particle remote manifest/forecast endpoint contract; +- Particle 专用 download queue、transport coordinator、persistent field cache; +- Particle retry、timeout、stale-data、gap inventory 或 availability state policy; +- token/auth/config 与 Particle field service 的绑定。 + +因此不能把 MapLibre 的通用 HTTP/FileSource 误认成 Weather Service 已接入。 + +## 6. Cache、preload 与数据生命周期 + +当前存在四种不同层次,不能合并: + +1. `RasterArrayTimelineRuntime` 的 entries/cache capacity 8:已经解码的 Snapshot memory cache,服务 Timeline cursor。 +2. `RasterArraySource` 的 local/runtime candidate vectors:Source-owned preload candidates,服务初始窗口和后续 boundary。 +3. MapLibre `FileSource`/`DatabaseFileSource` ambient/offline cache:通用资源缓存,按 Resource URL/HTTP metadata 工作,不理解 fieldId、validTime、generation 或 Snapshot。 +4. Renderer GPU slots:RasterParticleLayer/validation layer 的 T0/T1 texture 与 simulation state,不是下载缓存,不能由数据源层直接管理。 + +STEP-051 当前是四个资源请求全部到达后构建 Timeline;没有独立的 manifest-first inventory、有限并发下载、field dedup、retry 或 stale policy。已有 MapLibre cache 可以降低资源获取成本,但不能替代 Particle feed lifecycle。 + +建议下一阶段先定义低频 field arrival/availability 状态和 manifest 顺序,明确:manifest 可用但 payload 缺失、metadata reject、payload mismatch、gap、stale、retry exhausted 时如何保持 last valid Field。Persistent cache 不应在没有 feed key/manifest 生命周期之前先做;第一步只需要 memory/injected transport validation,是否接 DatabaseFileSource 应后置。 + +## 7. Forecast manifest/catalog 最小模型 + +不要把所有未来业务字段塞入 Descriptor。进入正式 feed 必要的最小分层如下。 + +### 已属于 `ParticleFieldDescriptor/Snapshot` + +- field identity:`fieldID` +- producer revision:`generation` +- forecast `validTime` +- `fieldType` / `displayDomain` +- grid width/height、bounds +- vector layout/encoding/noData +- expected byte length、stride、data version、可选 payload checksum + +这些字段是解码、插值兼容性、fail-closed 和 Renderer canonical input 所必需的。 + +### 应属于上游 manifest/catalog,而不是 Snapshot/Renderer + +- forecast `runTime`/cycle +- `leadTime`(可由 runTime 与 validTime 推导,但目录可显式记录) +- ordered snapshot list / window membership +- grid identity、resolution、coverage version +- metadata URL 与 payload URL/resource key +- source revision、availability、retry hint、expiry/stale policy + +manifest 可以有 source/provider identity,但它不应穿过 Adapter 进入 Snapshot,也不应被 Renderer 或 Runtime Window 解释为业务 provider。 + +### 目前不应新增 + +不要仅为“以后可能需要”加入 level、ensemble、unit system、auth token、HTTP headers、vendor schema、download path、cache database key 或 ocean mask 字段。若未来 Wave/Tide/Current 需要这些语义,应单独设计并遵守 Ocean-only 与 mask 边界。 + +## 8. Weather Service 直接接入判断 + +当前不具备安全的直接接入条件。虽然 Android 有 `HTTPFileSource`,MapLibre 有通用 Response/cache/error,当前 Particle 仍缺少: + +- provider-specific metadata/payload decoder 与其到 neutral Descriptor 的映射; +- forecast run/lead/valid-time inventory; +- provider-independent feed API 和 arrival ordering; +- retry/timeout/stale/gap policy; +- service auth/config ownership; +- 可在不改 Renderer/Timeline 的情况下将网络数据稳定送入 Snapshot submit。 + +直接接入会把 HTTP/provider 选择、目录和失败策略塞进 `RasterArraySource` 或 Android Host,容易越过冻结边界。结论:先建立 provider-neutral feed/manifest coordinator,再以独立阶段接 Weather Service;真实 network 不是 STEP-052 后的立即实现目标。 + +## 9. 候选下一阶段 + +| 候选 | 目标 | 新架构/模块 | Network/Provider | Timeline/Renderer | Android | 主要风险 | +|---|---|---|---|---|---|---| +| A. 直接 Weather Service | 立即接真实远程天气接口 | 新增 client、decoder、auth、生命周期 | 需要 | 不应修改,但 Source 会膨胀 | 需要一次高成本 build/test | 缺少 manifest、retry、provider 边界,容易把业务逻辑塞入 Core/Host | +| B. Provider-neutral Feed/Manifest Coordinator | 先定义 manifest、低频 arrival、Snapshot submit 和错误生命周期,用 Local/File/Mock transport 验证 | 一个薄跨平台 feed/coordinator;不建立 Provider Framework | 不需要;可注入 Local/Mock transport | 不改 state machine;不改 Renderer | 若只做 Core 可不需要;若补入口则合并一次 | arrival ordering、initial T0/T1 和 last-valid 语义需冻结 | +| C. Transport Cache/Preload | 先建立持久缓存、重试、过期和预取 | 新增 cache policy/lifecycle | 可不需要 | 不改 Renderer,但会依赖未定义 feed key | 建议需要 | 过早把 MapLibre ambient cache 当 Particle cache,生命周期和 key 尚未稳定 | +| D. Provider Decoder | 先做 GRIB/NetCDF 或某一供应商 decoder | 明显 provider-specific 模块 | 通常需要 | 输出应是 neutral Snapshot | 视入口而定 | 当前无真实 provider contract,容易锁死错误 schema | + +候选 B 能以最小修改面冻结真正缺口,并兼容未来 Local/File、HTTP 和 provider decoder;C 应在 B 的 manifest/key/error contract 之后做,A/D 应后置。 + +## 10. 唯一推荐的下一实现阶段 + +### Recommended Next Implementation: STEP-053 Provider-neutral Field Feed / Manifest 与 Arrival Coordinator + +目标范围: + +1. 定义最小 provider-neutral field manifest/catalog 与 ordered snapshot arrival contract。 +2. 将 metadata/payload transport 与 `ParticleFieldDataAdapter`、`ParticleFieldSnapshot`、现有 initial T0/T1/update submit 接口连接起来。 +3. 以 injected Local/File/Mock transport 验证 initial T0/T1、subsequent T1、strict validTime ordering、duplicate、gap、invalid、retry exhausted 和 last-valid hold。 +4. 只做低频异步 callback/future 或等价 arrival API,不做 Android per-frame JNI,不拥有 Timeline cursor/speed/pause。 +5. 建立结构化 feed availability/error 分类,明确 metadata reject、payload mismatch、not-yet-available、gap 和 stale 的边界。 +6. 保持 provider/vendor identity、URL、auth、HTTP headers 和 cache policy 在上游 feed/transport 层,不进入 Snapshot、Runtime 或 Renderer。 + +输入:manifest/catalog entry、metadata bytes、payload bytes,或注入的 transport response。 + +输出:已通过正式 Adapter 的 `ParticleFieldSnapshot`/arrival result;初始窗口和后续窗口仍调用现有 Core submit/update 语义。 + +允许修改:新增薄的 Core-neutral feed/coordinator、Source 的最小接线、定向 C++ tests、local/mock transport test seam 和必要日志/文档。若增加 Android validation 入口,应与同一阶段其他功能合并一次 build/install。 + +禁止修改:Timeline state machine、Playback、Runtime Field Window 语义、RasterParticleLayer/GPU simulation/trail、WeatherColorLayer、Adapter 合法性、Provider Framework、Weather Service、HTTP client、vendor decoder、第二套 Snapshot/Timeline/Window。 + +STEP-053 默认不需要真实 network、服务器、真实 provider 或 Android APK;先完成 Linux/Core/OpenGL 精确验证即可。只有要验证 Android Host 的低频 feed arrival/错误可观测性时才需要一次 Android build/install。 + +## 11. 建议的后续 2~4 阶段顺序 + +1. **STEP-053 Provider-neutral Field Feed / Manifest 与 Arrival Coordinator**:先冻结 manifest、arrival、错误和 last-valid 生命周期,使用 Local/File/Mock transport。 +2. **STEP-054 HTTP Transport 与 Weather Service Adapter**:在不改变 neutral Snapshot/Runtime/Renderer 的前提下,把真实 HTTP/provider decoder 置于 feed 上游;此阶段才处理 auth/config 和真实服务契约。 +3. **STEP-055 Field Cache、Retry、Stale 与 Preload Hardening**:基于已冻结的 manifest key、generation、validTime 和 error policy,决定 memory/cache/database 组合、重试和容量。 + +这三个阶段足够表达当前真实缺口,不提前规划更多 provider 或 Ocean field 变体。 + +## 12. 平台 / Core / Renderer 责任边界 + +- Transport/platform:负责 FileSource/HTTP/asset 的资源获取、平台凭据配置和底层 request cancellation;不生成 Timeline 状态。 +- Feed/coordinator(建议跨平台 Core-owned seam):负责 manifest 顺序、低频 arrival、metadata/payload 配对、调用正式 Adapter、错误分类和 submit 意图;不负责 GPU。 +- `ParticleFieldDataAdapter`:只做 descriptor/payload contract validation 与 canonical Snapshot construction。 +- `RasterArraySource`:保持 Source-owned authoritative T0/T1、Timeline 接线和已有 Window update;不变成 provider client。 +- Timeline/Playback:Core-owned,消费 Snapshot 时间和 low-frequency feed 结果;不请求网络。 +- Renderer:只消费规范化 Snapshot/Window,继续执行 GPU upload/reuse/simulation/trail;不包含 provider/network/vendor/cache policy。 +- Android/Kotlin:只提供低频用户意图和可选资源配置;不拥有 Timeline、Window、feed state 或 per-frame JNI。 + +## 13. Android 成本、技术债与风险 + +后续实现应遵守:先 Linux/Core/OpenGL precise build 与定向测试,再由用户在 Windows 11 对 `192.168.1.146` 用一次 APK 完成综合验证;若使用 ADB,必须显式 `adb -s 192.168.1.146 ...`。不为单个 MIX 文本问题单独触发 Android 周期。 + +Validation UI 的 auto MIX 文本滞后、WAITING/INVALID 视觉提示不足继续是 `NON-BLOCKING observability`,不阻塞正式 feed 架构,不推荐作为 STEP-053 主任务。Rotation/Viewport 自动恢复继续保留技术债;STEP-053 若不触及 lifecycle/surface/source reload,不合并修复。 + +主要风险是把 manifest/catalog 与 Descriptor 重复建模,或让 Source 直接拥有下载/重试/缓存策略。应优先薄化并用测试锁定:Snapshot immutable、validTime strict ordering、invalid/gap fail-closed、last valid Field、Window reuse、MIX property-only 和 Renderer 分离。 + +## 14. 调查结论 + +当前可以进入 STEP-053 实现 MD 编写,但不能跳过 provider-neutral feed/manifest 与 arrival 生命周期直接实现 Weather Service。STEP-051 production-like local feed 作为已验证过渡层保留;STEP-052 本身不修改代码、不引入网络、不新增缓存或 Provider。 diff --git a/docs/报告/STEP-052_正式数据源接入下一阶段调查_Codex完成报告_v1.md b/docs/报告/STEP-052_正式数据源接入下一阶段调查_Codex完成报告_v1.md new file mode 100644 index 000000000..7d3794bc7 --- /dev/null +++ b/docs/报告/STEP-052_正式数据源接入下一阶段调查_Codex完成报告_v1.md @@ -0,0 +1,72 @@ +# STEP-052 正式数据源接入下一阶段调查 — Codex 完成报告 + +## 基线 + +- Starting HEAD:`e4ab1b9d421a4559fa3627df07ee91dcd9b680e4` +- branch:`particle/main` +- origin/particle/main 起点:`e4ab1b9d421a4559fa3627df07ee91dcd9b680e4` +- 调查性质:docs-only、代码只读。 + +## 调查范围 + +实际检查了: + +- `PARTICLE_BASELINE.md`、`docs/00_项目规范/` +- `include/mbgl/particle/field/field_data_adapter.hpp` +- `src/mbgl/particle/field/field_data_adapter.cpp` +- `include/mbgl/particle/field/local_particle_field_feed.hpp` +- `src/mbgl/particle/field/local_particle_field_feed.cpp` +- `include/mbgl/style/sources/raster_array_source.hpp` +- `src/mbgl/style/sources/raster_array_source.cpp` +- `src/mbgl/style/sources/raster_array_source_impl.hpp` +- `src/mbgl/style/conversion/source.cpp` +- `src/mbgl/renderer/layers/render_particle_validation_layer.cpp` +- `src/mbgl/renderer/layers/render_raster_particle_layer.hpp` +- `platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/particle/ParticleCoreLayerValidationActivity.kt` +- `platform/android/MapLibreAndroid/src/cpp/asset_manager_file_source.cpp` +- `platform/android/MapLibreAndroid/src/cpp/http_file_source.cpp` +- `include/mbgl/storage/file_source.hpp` +- `include/mbgl/storage/resource.hpp` +- `include/mbgl/storage/response.hpp` +- `include/mbgl/storage/database_file_source.hpp` +- `src/mbgl/storage/file_source_manager.cpp` +- `include/mbgl/util/lru_cache.hpp` +- `particle-production/*`、`particle-fixtures/*`、`test/style/source.test.cpp` + +## 调查结论摘要 + +STEP-051 已建立 `metadata + binary → Descriptor → Adapter → Snapshot → RasterArraySource → Timeline/Runtime` 的 local production-like 路径,但 payload 仍是既有 canonical fixture binary,Source 仍直接编排资源请求和 candidates。当前没有 Particle Weather Service、provider contract、forecast manifest、feed coordinator、专用 retry/stale policy 或 Particle transport cache。 + +MapLibre 通用 `FileSource`/HTTP/ambient cache 可以复用为底层资源设施,但不能视为 Weather Service 已接入。Timeline cache、Runtime candidate cache、FileSource/Database cache 和 GPU T0/T1 slots 是不同层次,当前不能合并。 + +## Recommended Next Implementation + +**STEP-053 Provider-neutral Field Feed / Manifest 与 Arrival Coordinator**。 + +先以 Local/File/Mock transport 冻结 manifest、ordered arrival、metadata/payload pairing、错误分类、last-valid hold 和 initial/subsequent submit;不需要真实 network、服务器或 provider。完成 Linux/Core/OpenGL 验证后,如确需 Android 观测,再合并一次 build/install。 + +建议顺序:STEP-053 feed/manifest → STEP-054 HTTP Transport 与 Weather Service Adapter → STEP-055 Field Cache、Retry、Stale 与 Preload Hardening。 + +## 架构判断 + +- 不需要推翻 STEP-049/051 contract。 +- 不建议下一步直接接 Weather Service;中间缺少 feed lifecycle、manifest、错误/重试/stale 语义和 provider-neutral transport boundary。 +- STEP-053 不应修改 Timeline state machine、Runtime Field Window、Renderer、WeatherColorLayer 或 Android Timeline ownership。 +- Validation UI MIX 滞后继续为 `NON-BLOCKING observability`;Rotation/Viewport 继续为独立技术债。 + +## 输出与 Git + +- 调查报告:`docs/20_调查/STEP-052_正式数据源接入下一阶段调查报告_v1.md` +- 完成报告:本文件 +- 是否修改功能代码:否。 +- 是否执行 Android Gradle/APK/ADB:否。 +- 是否执行 Jest:否。 +- `git diff --check`:提交前执行并应为 PASS。 +- commit hash:待提交后回填。 +- commit message:`docs: investigate next production field source stage` +- push result:待执行。 +- remote branch:`origin/particle/main` +- Final HEAD:待 push 后回填。 +- 无关 untracked 文件:保持未修改、未 stage、未提交。 + +阻塞项:无调查阻塞;STEP-053 实现前需冻结 manifest/arrival API 与错误生命周期,避免把 Source 扩展成 Weather Service 或 cache orchestrator。