feat(evidence): 统一 Source → Evidence ↔ Claim 证据契约 - #104
Theater-ahyeon wants to merge 3 commits into
Conversation
新增 folio-evidence-contract/v1 统一契约(core 类型 + shared 投影), 把 FinancialEvidenceEnvelope、EvidenceRef、NewsItem 三套并行证据抽象 投影到同一条 Source → Evidence ↔ Claim 关系链: - core/evidence-contract.ts: EvidenceSource / EvidenceItem / EvidenceClaim / EvidenceBundle 类型,全部 ID 确定性派生(sha256,键序无关), 组装 → 持久化 → 重载全程稳定 - shared/evidence/contract.ts: 四个投影函数(结构化金融事实 / 研究论点 / 新闻 / 通用文档备案)+ buildEvidenceBundle 多对多合并 + 序列化往返守卫 - 结构化金融来源不伪造 canonicalUrl;authority 元数据只在已知时填写; 投影只读输入,现有持久化记录无需迁移 - docs/evidence-contract.md: 身份与生命周期语义文档 对应 helsome#100(契约整合部分;claim verifier / Source Inspector 接入留作 后续增量 PR)
helsome
left a comment
There was a problem hiding this comment.
方向非常值得保留,而且 #100 需要的核心形状基本已经出来了;测试报告也满足要求。当前我只卡两个会进入长期证据契约的数据语义问题,建议在 v1 落地前修掉:
-
projectNewsItems目前把NewsItem.timestamp同时写进publishedAt和retrievedAt,且直接原值写入。现有 core 明确约定NewsItem.timestamp是 epoch seconds,而新契约把EvidenceSource.retrievedAt/publishedAt定义为 epoch ms;这里会产生 1000 倍时间偏差。同时NewsItem只有来源时间,并没有真实 retrieval time,不能用发布时间冒充抓取时间。请让调用方显式提供 retrieval timestamp(或把未知 retrieval 表达为 unknown/optional),并在投影处做统一单位转换,补一个能抓住 seconds↔ms 的测试。 -
projectFinancialEvidence中financial.asOf会优先使用value.asOf ?? envelope.asOf,但同一 EvidenceItem 的freshness.asOf只写envelope.asOf。当 metric 有更细粒度value.asOf时,同一条证据内部会出现两个不同的时间语义。请统一为同一个 resolved as-of,并补value.asOf存在、envelope asOf 缺失/不同的用例。
这两个点属于 #100 最核心的“不要让不同来源在统一层丢失/伪造时间语义”,不是要求扩大 scope。其余 Source/Evidence/Claim、确定性 ID、多对多、structured finance 不伪造 URL 的方向我认可。#105/#106 是 stacked 在本 PR 上,先把这里修正即可,不需要三个 PR 重复改。
|
已按评审意见修复两处数据语义问题:
|
- evidence/contract.ts 新增 buildReportEvidenceBundle:把报告各 section 的 EvidenceRef 确定性投影为统一契约 bundle(tool 来源 + tool_result 证据 + unverified 论点),仅依赖报告本身,可随时重建 - ResearchReportRepository.saveReport 保存报告时自动派生并落盘 research/evidence/<id>.json,正常完成与恢复两条路径自动覆盖,调用方 零改动;旧报告无 bundle 文件时读取返回 undefined,完全向后兼容 - 新增 getEvidenceBundle(reportId):损坏/缺失文件容错返回 undefined, 报告本身始终是权威数据源 - 新增 7 个聚焦测试:生产路径 ID 稳定性、跨 section 相同论点合并、 旧格式兼容、损坏容错、非法 ID、空 bundle 对应 helsome#100 增量(生产路径组装→持久化→重载稳定性),依赖 helsome#104。
新增 buildAnswerEvidenceTrace:为单次 Copilot 回答按需构建确定性溯源 文档,串起 问题 → 回答块引用(fe_ 证据 id)→ 统一契约 evidence/source: - answer-trace.ts:扫描回答中的 folio-block 类型块(与渲染器相同的 fence 规则),把块内 evidenceIds 经 provenance.envelopeId 映射到契约 evidence 项;引用了本回合不存在证据的 id 显式记入 unmappedEvidenceIds, 不静默丢弃;未闭合 fence(流式中)与非法块安全跳过 - 契约增量:EvidenceItem.provenance 新增可选 envelopeId(原始 fe_ 信封 id),让契约前时代的引用可确定性 join 到契约项;projectFinancialEvidence 自动填充 - 追溯文档不新增持久化:问题/回答/工具调用/fe_ 信封都已持久化在消息上, trace 按需计算即可重建,零漂移风险 - 5 个聚焦测试:映射与缺口、bundle 一致性、确定性、空证据回合、 未闭合/非法块容错 对应 helsome#101 第一片(确定性溯源层;UI 呈现与 LLM 引用质量归后续 PR), 依赖 helsome#104、helsome#105。
该 review 的两个 correctness blocker 已在当前 head 处理:News 时间由 epoch seconds 显式转 ms 且 retrievedAt 现在要求调用方显式提供;financial asOf 与 freshness.asOf 统一使用同一个 resolvedAsOf,并补了对应回归测试。旧 blocker 不再适用于 head 128289e。
helsome
left a comment
There was a problem hiding this comment.
重新审查当前 head 128289eadf5e938b892f4ba49437f96aedfd2ff5:此前两个 correctness blocker 已实际消失。News 投影把 epoch seconds 转为 ms,并且 retrievedAt 现在是显式必需输入,不再用发布时间冒充抓取时间;financial item 的 financial.asOf 与 freshness.asOf 都使用同一 resolvedAsOf = value.asOf ?? envelope.asOf,测试覆盖秒↔毫秒与 value/envelope asOf 优先级。Source/Evidence/Claim 的确定性 identity、多对多和 structured finance 不伪造 URL 的整体方向可接受,作者报告 Bun 1.4.2 / Windows 10、17/0 focused、954/0 core+shared、core/shared typecheck exit 0。
APPROVE 仅针对 #104 当前代码。它属于 #104→#105→#106 stack;#105/#106 当前提交历史仍未包含 #104 最新的 128289e 修正,因此本轮不分别 squash/不直接合并栈,先让栈顶同步最终祖先后再按最终状态处理。当前 head Actions 是首次 fork action_required,没有把它描述为 CI 通过。
改了什么
按 issue #100 的「版本化、增量式证据契约」方向,新增统一契约的第一个增量切片:契约类型 + 三条既有证据路径的确定性投影 + bundle 组装/序列化。不改动任何现有类型、持久化格式与生产路径。
新增文件
packages/core/src/evidence-contract.tsEvidenceSource/EvidenceItem/EvidenceClaim/EvidenceBundle契约类型与folio-evidence-contract/v1版本常量packages/shared/src/evidence/contract.tsbuildEvidenceBundle+ 序列化/守卫packages/shared/src/evidence/contract.test.tsdocs/evidence-contract.md修改文件(各 1 行导出接线)
packages/core/src/index.ts、packages/shared/src/evidence/index.ts为什么要改
当前存在三套并行演化的证据抽象:Copilot 的
FinancialEvidenceEnvelope(结构化金融事实)、Deep Research 的EvidenceRef(论点引用)、NewsItem(网页/新闻)。同一事实/来源因来自不同子系统而获得不同身份与元数据(语义碎片化)。本 PR 用一条 Source → Evidence ↔ Claim 关系链统一它们,且是投影整合而非新框架:现有类型仍是生产方的权威表示。对应 Issue
#100(契约整合部分)。claim verifier(#13)、Source Inspector UI(#30)、检索去重(#39)的接入留作后续增量 PR。
设计要点
sourceId/evidenceId/claimId全部由 sha256 确定性派生(截断 24 位,src_/ev_/claim_前缀,与现有fe_风格一致);哈希输入经键排序的stableJson序列化,身份永不依赖对象键序。sourceId不含检索时间:同一文档/查询被再次观察仍是同一来源evidenceId含检索时间:同一事实稍后再次观察是同源新 observation,保留各自 provenanceclaimId由表述 + instrument 作用域派生:不同 run 的相同论点在 bundle 中合并并并集 evidenceIds —— 多对多映射由此自然成立evidenceIds[],Evidence 不反向命名 Claim;一条证据可支撑多个论点。structured_finance来源无公开文档,canonicalUrl恒为空,身份由 publisher + dataset + query 承担;authority元数据只在实际已知时填写(如监管备案)。availability枚举值,不允许静默丢弃。验证
环境:Bun 1.4.2 / Windows 10 (10.0.26200)
验收标准覆盖情况:
evidence-contract.ts+docs/evidence-contract.md)projectFinancialEvidence/projectNewsItems/projectTextEvidence;研究论点路径projectEvidenceRefs覆盖 tool 证据)mixed-source integration用例在单个 bundle 同时携带结构化金融证据、tool 论点证据、新闻摘录、监管备案摘录并验证重载一致无可见 UI 变化
纯契约/共享层新增,不改任何 UI、IPC 或持久化行为。
已知未完成项(后续增量 PR)
verification/verifiedBy,契约不变)