Skip to content

feat(evidence): 统一 Source → Evidence ↔ Claim 证据契约 - #104

Open
Theater-ahyeon wants to merge 3 commits into
helsome:mainfrom
Theater-ahyeon:feat/unified-evidence-contract
Open

Theater-ahyeon wants to merge 3 commits into
helsome:mainfrom
Theater-ahyeon:feat/unified-evidence-contract

Conversation

@Theater-ahyeon

Copy link
Copy Markdown

改了什么

按 issue #100 的「版本化、增量式证据契约」方向,新增统一契约的第一个增量切片:契约类型 + 三条既有证据路径的确定性投影 + bundle 组装/序列化。不改动任何现有类型、持久化格式与生产路径。

新增文件

文件 内容
packages/core/src/evidence-contract.ts EvidenceSource / EvidenceItem / EvidenceClaim / EvidenceBundle 契约类型与 folio-evidence-contract/v1 版本常量
packages/shared/src/evidence/contract.ts 四个投影函数 + buildEvidenceBundle + 序列化/守卫
packages/shared/src/evidence/contract.test.ts 13 个聚焦测试
docs/evidence-contract.md 契约文档:关系模型、身份语义、投影一览、明确约定

修改文件(各 1 行导出接线)

  • packages/core/src/index.tspackages/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,保留各自 provenance
    • claimId 由表述 + instrument 作用域派生:不同 run 的相同论点在 bundle 中合并并并集 evidenceIds —— 多对多映射由此自然成立
  • 多对多:Claim 单向持有 evidenceIds[],Evidence 不反向命名 Claim;一条证据可支撑多个论点。
  • 不伪造 URLstructured_finance 来源无公开文档,canonicalUrl 恒为空,身份由 publisher + dataset + query 承担;authority 元数据只在实际已知时填写(如监管备案)。
  • 向后兼容:投影只读输入、从不修改;已有持久化记录零迁移、保持可读。金融证据保留 metric/unit/currency/period/asOf/originalValue 语义,文档证据保留 excerpt/location 语义。
  • 显式状态:冲突/不可用是 availability 枚举值,不允许静默丢弃。

验证

环境:Bun 1.4.2 / Windows 10 (10.0.26200)

bun test packages/shared/src/evidence/contract.test.ts packages/shared/src/evidence/financial-evidence.test.ts
→ 17 passed / 0 failed(61 expect calls)

bun test packages/shared packages/core
→ 954 passed / 0 failed(89 files,3693 expect calls)

cd packages/core && bun run typecheck  → exit 0
cd packages/shared && bun run typecheck → exit 0

验收标准覆盖情况:

  • ✅ 契约文档化并存在于核心/共享代码(evidence-contract.ts + docs/evidence-contract.md
  • ✅ 三种证据来源投影到同一契约:结构化金融事实、网页/新闻、文档/备案(projectFinancialEvidence / projectNewsItems / projectTextEvidence;研究论点路径 projectEvidenceRefs 覆盖 tool 证据)
  • ✅ evidenceId/sourceId 在组装 → 序列化 → 反序列化全程稳定(round-trip 相等性测试)
  • ✅ 多对多映射(claim 合并 + 一证多 claim 测试)
  • ✅ 不为结构化金融数据伪造 URL(专门断言)
  • ✅ 已有持久化记录保持可读(投影只读 + 输入不变性测试)
  • ✅ 身份稳定性 / 多对多 / 序列化重载 / 混合证据类型的聚焦测试
  • ✅ 可复现集成示例:mixed-source integration 用例在单个 bundle 同时携带结构化金融证据、tool 论点证据、新闻摘录、监管备案摘录并验证重载一致

无可见 UI 变化

纯契约/共享层新增,不改任何 UI、IPC 或持久化行为。

已知未完成项(后续增量 PR)

  • claim verifier 接入(更新 verification / verifiedBy,契约不变)
  • Source Inspector / 引用检查器消费统一投影
  • run/retrieval 元数据写入 bundle provenance 的生产接线

新增 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 helsome left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

方向非常值得保留,而且 #100 需要的核心形状基本已经出来了;测试报告也满足要求。当前我只卡两个会进入长期证据契约的数据语义问题,建议在 v1 落地前修掉:

  1. projectNewsItems 目前把 NewsItem.timestamp 同时写进 publishedAtretrievedAt,且直接原值写入。现有 core 明确约定 NewsItem.timestampepoch seconds,而新契约把 EvidenceSource.retrievedAt/publishedAt 定义为 epoch ms;这里会产生 1000 倍时间偏差。同时 NewsItem 只有来源时间,并没有真实 retrieval time,不能用发布时间冒充抓取时间。请让调用方显式提供 retrieval timestamp(或把未知 retrieval 表达为 unknown/optional),并在投影处做统一单位转换,补一个能抓住 seconds↔ms 的测试。

  2. projectFinancialEvidencefinancial.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 重复改。

@Theater-ahyeon

Copy link
Copy Markdown
Author

已按评审意见修复两处数据语义问题:

  1. News 时间语义与抓取时间校准NewsItem.timestamp(epoch 秒)在投影到 EvidenceSource.publishedAt 时显式转换为 epoch 毫秒;新增 NewsItemProjectionOptions.retrievedAt,允许调用方显式传入真实抓取时间戳,缺省回退至当前时间戳,不再使用发布时间冒充抓取时间。
  2. 统一金融证据 asOf 语义projectFinancialEvidence 统一收敛为 resolvedAsOf = value.asOf ?? envelope.asOf,使 financial.asOffreshness.asOf 保持完全一致。
  3. 测试覆盖:新增 3 个聚焦测试覆盖秒↔毫秒转换、显式与缺省 retrievedAt、以及 value.asOfenvelope.asOf 的优先级解析用例。bun test packages/shared/src/evidence 20 passed 全部通过,typecheck 全部 exit 0。

Theater-ahyeon added a commit to Theater-ahyeon/folio that referenced this pull request Sep 21, 2026
- 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
Theater-ahyeon added a commit to Theater-ahyeon/folio that referenced this pull request Sep 21, 2026
新增 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#104helsome#105
@helsome
helsome dismissed their stale review September 22, 2026 00:12

该 review 的两个 correctness blocker 已在当前 head 处理:News 时间由 epoch seconds 显式转 ms 且 retrievedAt 现在要求调用方显式提供;financial asOffreshness.asOf 统一使用同一个 resolvedAsOf,并补了对应回归测试。旧 blocker 不再适用于 head 128289e

@helsome helsome left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

重新审查当前 head 128289eadf5e938b892f4ba49437f96aedfd2ff5:此前两个 correctness blocker 已实际消失。News 投影把 epoch seconds 转为 ms,并且 retrievedAt 现在是显式必需输入,不再用发布时间冒充抓取时间;financial item 的 financial.asOffreshness.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 通过。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants