#175:生成 CustomerUpload / CloudAtlas / NetFlow 的 Run 级资产比较结果
状态:已由用户确认,并授权同步本 Issue 正文及开始实现。允许在当前任务分支实现、验证及本地提交;代码推送、合并、发布与关闭 Issue 仍需另行授权。
Outcome 与交付边界
提供后端内部读取能力:按明确的 tenant / Project / Run,返回每个 Resource 的确定性三来源资产比较结果。
- 基于 PostgreSQL 中已发布、已固定的事实派生,不要求另建比较事实表。
- 可读取符合条件的历史 Run,不只读取 latest Run。
- 必须接入真实 Run 数据读取路径,不能仅交付未接入的纯函数。
- 不新增公开 API、前端页面、report-v2、Evidence target 或风险评分。
- 比较计算只读,不修改 Finding,也不改变现有报告与发布行为;聚合完成证据必须满足下文的原子发布要求。
资源集合与来源语义
结果资源集合精确为:本 Run CustomerUpload / CloudAtlas 关联的 Resource,与本 Run NetFlow 正向活动关联的已有受管 Resource 的并集。
- 全部关联严格属于同 tenant / Project / Run。
- 每个 Resource 恰好一条结果;重复 Observation 不增加结果条数。
- 沿用 Canonical IP;IPv4-mapped IPv6 复用既有规范化规则。
- 不为陌生公网 Peer 创建 Resource。
- 不加入本 Run 三来源均无证据的历史资源。
- 不使用 Project 当前资源全集、当前输入选择或当前 Finding 状态重构历史结果。
- CustomerUpload / CloudAtlas 的出现状态取自本 Run 已固定的 Observation 与解析关系;不把 CloudAtlas 状态字段解释为是否出现。
输出字段
每条结果包含:
- resource_id
- canonical_ip
- customer_upload_present
- cloudatlas_present
- netflow_status
- classification
- classification_reason
- netflow_reason
- content_hash
tenant / Project / Run 与比较合同版本放在结果集外层;结果集包含有序结果数组与 output_hash。
分类合同
| CustomerUpload |
CloudAtlas |
NetFlow |
classification |
classification_reason |
| 出现 |
出现 |
ACTIVE / UNKNOWN |
matched |
observed_in_both_sources |
| 出现 |
未出现 |
ACTIVE / UNKNOWN |
customer_upload_only |
observed_in_customer_upload_only |
| 未出现 |
出现 |
ACTIVE / UNKNOWN |
cloudatlas_only |
observed_in_cloudatlas_only |
| 未出现 |
未出现 |
ACTIVE |
neither_source_observed |
not_observed_in_either_source |
| 未出现 |
未出现 |
UNKNOWN |
不生成记录 |
不适用 |
matched 仅表示两来源均出现该 Canonical IP,不表示属性一致、合规或安全。only 仅比较 CustomerUpload 与 CloudAtlas。
NetFlow 状态与原因
| 依据 |
netflow_status |
netflow_reason |
| 本 Run 存在该 Resource 的有效正向活动事实 |
ACTIVE |
positive_activity_observed |
| 本 Run 明确未选择 NetFlow |
UNKNOWN |
netflow_input_absent |
| 本 Run 已完成 NetFlow 聚合,但没有该 Resource 的正向活动事实 |
UNKNOWN |
no_positive_activity_evidence |
零记录或缺少某资产活动,不推出无流量、不活跃、不存在或完整覆盖。原因只返回稳定代码;展示文案不参与 Hash。
聚合完成证据与历史兼容
活动表零行不能单独证明聚合完成。
对于 present NetFlow 的 Run:
- 必须有持久事实证明该 Run 已完成受支持合同下的聚合,零结果也可被证明。
- 完成证据与正向活动事实必须具有一致的发布边界;失败回滚不得留下可读取的已完成假象。
- Retry 不得重复事实或产生矛盾的完成证据。
- 不以部署时间、当前代码版本或没有报错代替持久证明。
| Run 情况 |
行为 |
| 双来源事实完整,NetFlow 明确 absent |
可派生,NetFlow 为 UNKNOWN |
| 双来源事实完整,NetFlow present,且聚合完成可证明 |
可派生,包括合法零聚合结果 |
| NetFlow 输入尚未建模,或历史聚合执行情况无法证明 |
明确返回不支持 |
| 声明支持且已完成,但必需事实损坏或自相矛盾 |
完整性错误,不降格成 UNKNOWN 或不支持 |
不读取原始文件补算历史聚合,不回填或改写历史 Run。
可见性与错误合同
仅成功发布且 completed_at 非空的 Run 可读取;支持的成功状态为 COMPLETED、COMPLETED_WITH_WARNINGS,仍须满足相应事实合同。
稳定区分以下失败原因,不伪装成成功空结果:
- run_not_found:在调用方 scope 内找不到 Run;不泄露其他 scope 的存在性。
- run_not_published:Run 尚未成功发布。
- comparison_contract_unsupported:历史输入或处理合同不足以支持比较。
- comparison_facts_invalid:应存在的事实缺失、损坏或不一致。
本票不改动既有公开读取入口的兼容规则。
排序与 Hash
排序:IPv4 在 IPv6 前,各自按地址数值升序。不按文本字典序、classification、reason 或风险优先级排序。输入行顺序、数据库返回顺序与重复 Observation 不影响最终结果顺序。
比较输出使用固定版本 ip-source-comparison/v1。
- content_hash:SHA-256,覆盖合同版本、tenant / Project / Run 身份,以及该条结果除 Hash 自身外的全部字段。
- output_hash:SHA-256,覆盖合同版本、scope,以及按上述规则排序的完整结果数组。
- Canonical 编码使用 UTF-8 JSON、键排序、无额外空白;UUID 使用标准小写字符串。
- 不包含计算时间、展示文案、当前 Finding、Project 当前选择或查询分页参数。
- 合法空结果也返回明确的 output_hash。
- 同 Run、同固定事实重复读取必须得到相同结果与 Hash。新 Run 的 Hash 因 Run 身份不同而不同,不要求跨 Run 相等。
验收场景
| 场景 |
必须观察到的结果 |
| 完整组合 |
三种双来源分类 × ACTIVE/UNKNOWN,加 neither + ACTIVE,共七种合法组合均符合表格 |
| 历史受管 IP 本次仅有 NetFlow 活动 |
纳入结果;neither + ACTIVE;不新增 Finding |
| 历史资源本次三来源均无证据 |
不纳入结果 |
| NetFlow absent |
双来源资源正常比较;UNKNOWN + netflow_input_absent |
| 已完成聚合且结果为空 |
可读取;双来源资源为 UNKNOWN + no_positive_activity_evidence |
| 历史上没有执行聚合或无法证明执行 |
不支持;不能当作空聚合 |
| 重复、输入乱序、IPv4-mapped IPv6 |
唯一资源、分类、顺序与 Hash 稳定 |
| 跨 tenant / Project / Run |
无数据混入或越界泄露 |
| 后续新增资源、切换输入选择、改变 Finding |
旧 Run 比较结果与 Hash 不变 |
| 发布失败、Retry、成功后重复读取 |
不暴露半发布结果;无重复事实;同 Run 结果稳定 |
| 合同支持但必需事实损坏 |
明确完整性错误,不返回 UNKNOWN |
| 真实端到端验证 |
通过真实 PostgreSQL / Runner 产生已发布 Run,再经内部入口读取比较结果;现有双来源报告与 Finding 行为保持不变 |
实现核对说明(非验收替代)
用户已确认持久事实应能区分聚合完成且为空与历史未执行聚合;该能力按上述条款验收。只读调查定位了发布状态、输入合同与正向活动事实,但尚未定位证明聚合完成且为空的具体持久字段;不得声称现有实现已通过该验收,也不得虚构历史完成标记。
相关实现位置:backend/app/domain/ip_consistency.py、ip_results.py、netflow_activity.py、governance_runs.py、models.py。既有行为与测试只作实现依据,不能覆盖本文已确认的业务预期。
#175:生成 CustomerUpload / CloudAtlas / NetFlow 的 Run 级资产比较结果
状态:已由用户确认,并授权同步本 Issue 正文及开始实现。允许在当前任务分支实现、验证及本地提交;代码推送、合并、发布与关闭 Issue 仍需另行授权。
Outcome 与交付边界
提供后端内部读取能力:按明确的 tenant / Project / Run,返回每个 Resource 的确定性三来源资产比较结果。
资源集合与来源语义
结果资源集合精确为:本 Run CustomerUpload / CloudAtlas 关联的 Resource,与本 Run NetFlow 正向活动关联的已有受管 Resource 的并集。
输出字段
每条结果包含:
tenant / Project / Run 与比较合同版本放在结果集外层;结果集包含有序结果数组与 output_hash。
分类合同
matched 仅表示两来源均出现该 Canonical IP,不表示属性一致、合规或安全。only 仅比较 CustomerUpload 与 CloudAtlas。
NetFlow 状态与原因
零记录或缺少某资产活动,不推出无流量、不活跃、不存在或完整覆盖。原因只返回稳定代码;展示文案不参与 Hash。
聚合完成证据与历史兼容
活动表零行不能单独证明聚合完成。
对于 present NetFlow 的 Run:
不读取原始文件补算历史聚合,不回填或改写历史 Run。
可见性与错误合同
仅成功发布且 completed_at 非空的 Run 可读取;支持的成功状态为 COMPLETED、COMPLETED_WITH_WARNINGS,仍须满足相应事实合同。
稳定区分以下失败原因,不伪装成成功空结果:
本票不改动既有公开读取入口的兼容规则。
排序与 Hash
排序:IPv4 在 IPv6 前,各自按地址数值升序。不按文本字典序、classification、reason 或风险优先级排序。输入行顺序、数据库返回顺序与重复 Observation 不影响最终结果顺序。
比较输出使用固定版本 ip-source-comparison/v1。
验收场景
实现核对说明(非验收替代)
用户已确认持久事实应能区分聚合完成且为空与历史未执行聚合;该能力按上述条款验收。只读调查定位了发布状态、输入合同与正向活动事实,但尚未定位证明聚合完成且为空的具体持久字段;不得声称现有实现已通过该验收,也不得虚构历史完成标记。
相关实现位置:backend/app/domain/ip_consistency.py、ip_results.py、netflow_activity.py、governance_runs.py、models.py。既有行为与测试只作实现依据,不能覆盖本文已确认的业务预期。