Files
qinglong/docs/adr/ADR-0450-closed-window-legacy-shadow-terminal-difference-audit.md
T

6.6 KiB
Raw Blame History

ADR-0450:闭合窗口的 Legacy Shadow 终态差异审计

  • 状态:Accepted
  • 日期:2026-08-18
  • 关联 RFCQL-RFC-0001 D-02、D-358、PR-4
  • 关联 ADRADR-0002、ADR-0448、ADR-0449
  • AmendsADR-0449 的跨测量窗口待办

上下文

ADR-0448/0449 只证明一次启动时 active Shadow Run 的恢复结果,不能回答一个明确时间段内已经写入的 Shadow Run 是否与 Legacy 执行终态一致。 直接用 startup scanned 计算比例会把未知尾页和未稳定执行混入分母;把全部 RunningInstances 当成 Shadow 应写集合也不成立,因为 QingLong 2.x 表没有可信的 execution originmanual、system crond 与其他 Legacy 路径可能复用同一 Cron。

部署跨度还要求同一能力既能在低性能路由设备运行,也能在 standalone 节点处理较大窗口。审计不能增加启动查询、常驻 timer、第二数据库 authority、 无界数组或高基数指标,更不能为了“补齐”历史数据而改写 Run/Attempt/RunningInstance。

决策

  1. 新增显式、只读、一次性的 audit:legacy-shadow-terminal:ql3 运维入口。调用方必须给出至少一个已支持 Shadow origin 以及 [windowStartMs, windowEndMs);窗口 cohort 以 legacy-owned Shadow Run.created_at_ms 为准,不自动选择“最近一段时间”。
  2. 窗口只有在 windowEndMs <= observedAtMs - minimumSettlingAgeMs 时闭合,默认 settling age 为五分钟。窗口未闭合、候选页未扫完或 Legacy evidence 达到硬上限时,不输出任何比例。
  3. Source 使用 (created_at_ms, run_id) 稳定 keyset,复用现有 Runs(project_id, created_at_ms) 索引;Run 与 latest Attempt 在同一有界查询中读取。 每页再执行一次有界 RunningInstance evidence 查询,不逐 candidate 发出 N+1 查询。单页候选硬上限 64、Legacy evidence 硬上限 512。
  4. Profile 默认预算保持与 startup recovery 一致:edge 8 × 1 pagestandalone 32 × 4 pages。查询不安装 timer、watcher、线程、进程、连接池 常驻生命周期或后台续扫;预算耗尽返回 incomplete,操作者缩小窗口后重试。
  5. 关联优先级固定为 direct Attempt/Run reference,其次同 Cron 下的 opaque log artifact ID、PID,最后才允许唯一且在容差内的 started time。 多个候选或 evidence 截断一律归入 ambiguous,不用“最近一条”猜测。原始 log path 只在 adapter 内哈希,不进入 application/report。
  6. 每个 Shadow candidate 必须且只能归入八类之一:matchedshadow_not_terminalshadow_attempt_missingshadow_attempt_ambiguouslegacy_evidence_missinglegacy_evidence_ambiguousstatus_mismatchfield_mismatch。 aggregate 与按 origin matrix 都必须守恒。
  7. status、exit code、started time、finished time 与 log artifact 使用固定 dimension counters 记录 compared/matched/mismatched/unavailable。 时间默认允许两秒精度差,硬上限一分钟;terminalAgreementPermillefullyComparablePermille 只在窗口闭合、候选与 evidence 都完整且分母非零时出现。
  8. 报告 schema 为 qinglong/legacy-shadow-terminal-difference-report@v1。报告只包含 Profile、窗口、预算、固定计数、最多七条 origin matrix 和 matched/empty/differences_found/window_open/incomplete assessment;不输出 Project/Run/Attempt/Cron/PID/log/task/user/error identity。
  9. 报告明确声明 direction=shadow_to_legacylegacyWithoutShadow=not_measured。当前数据模型无法可靠证明“存在 Legacy 执行但 Shadow Run 未写入”, 因而本报告不能单独作为 Primary gateD-360 必须组合 observer failure/capture evidence,或先引入可信 origin-scoped admission ledger。
  10. 审计不写数据库、不修复差异、不启动 Executor、不改变 Legacy 返回值。--fail-on-difference 只把非 matched assessment 映射为进程退出码 1 供人工 rollout/CI gate 使用。

资源与部署影响

  • 不新增 package、生产依赖、schema、migration、表、索引、数据库 authority、HTTP/gRPC 路由、timer、watcher、worker、容器或 Kubernetes 对象。
  • edge 默认最多读取 8 个 Shadow candidate 与 64 条 Legacy evidencestandalone 默认最多 128 个 candidate 与每页 256 条 evidence。
  • CLI 数据库连接固定只读、单连接池;报告为固定字段 aggregate,不保留 candidate identity,也不建立进程内累计 registry。
  • Runs(project_id, created_at_ms) 负责 cohort keysetRunningInstance 查询每页一次且结果有硬上限。若未来固定设备证明表扫描 CPU 不可接受,应通过新 migration 增加 (cron_id, started_at, id) 索引并单独评审升级写放大,而不是在本 ADR 中静默修改已发布 migration checksum。

被拒绝的替代方案

双向扫描所有 RunningInstances 并计算 Shadow 捕获率

拒绝。2.x RunningInstances 没有可信 execution origin,无法知道某条记录是否属于已开启 Shadow 的入口;把它们都放进分母会制造假阴性。

在每次 HTTP startup 自动审计历史窗口

拒绝。它会重复 D-356 的启动查询、延迟路由设备监听,并需要隐式选择时间窗口。历史证据必须由操作者显式触发。

为每个 Shadow candidate 单独查 RunningInstances

拒绝。即使 candidate 数有上限,N+1 仍会让低性能 SQLite 设备承担不必要的查询调度和重复表扫描。

只输出差异明细 ID 方便排障

拒绝。运维日志会泄露高基数任务身份;需要明细时应新增受认证、Project-scoped 的诊断产品面,而不是扩大默认 CLI 报告。

验证

  • 真实 SQLite 覆盖完全一致、status/exit field 差异、missing/ambiguous evidence、active Shadow、窗口未闭合、edge 页预算耗尽、evidence overflow、 双 origin 守恒、空分母和只读 CLI 脱敏输出。
  • 真实 SQLite/CLI 聚焦测试 14/14、Legacy/Shadow 串行扩展 91/91build:back、完整 backend 1,455 pass / 0 fail / 2 conditional skip、18-package clean build/test、14/14 static audit 与 14/14 artifact audit 全部通过。
  • edge/standalone 的 base、adopted、application、application-api、AI、application+AI、MCP 产物分别为 2,589,998 / 2,590,0762,809,293 / 2,809,4163,632,877 / 3,632,9973,800,430 / 3,800,5743,069,251 / 3,069,3414,493,151 / 4,493,2837,315,930 / 7,316,038 bytes,与 D-357 完全一致。
  • 本切片不修改 PostgreSQL、容器或 Kubernetes 部署面,因此物理 PostgreSQL HA/K3s 门不作为本切片的新证据;D-359 仍负责 edge/standalone 资源压力与 Shadow-off 回滚演练。