Files
Agent-System-Documents/docs/proposals/2026-10-18-ext-20-regulations-provider.md
T
2026-10-18 09:57:34 +00:00

21 KiB
Raw Blame History

EXT-20:共享法规检索与网络证据扩展点

状态:已获用户授权(2026-10-18);owner:weichen;主任务:Dev 仓 initiative feature/regulations-kb。 来源计划:Dev 仓 docs/superpowers/plans/2026-10-18-regulations-knowledge-base-plan.md 的 KTD1、KTD14 与 U9(本文是 U9 的基座公开契约)。 规范依据:扩展面兼容与演进(§6 扩展点申请模板);同类先例:EXT-19 账号目录(受信插件注册、Host 可选接口消费)。 实现位置:Backend 分支 feature/regulations-kb-ext20(pkg/extension/regulations.go、internal/app/regulations.go、internal/app/compiled.go;本文核对的提交为 499ad47,SELF·源码级)。 撰写日期:2026-10-18。

1. 申请四要素(compat-policy §6)

  • 场景:Agent-System-RDM 处理涉及法规、标准或审评指导原则的问题时,必须同时查询受控法规语料库和真实网络搜索(双源核验), 并把每条法规结论绑定到固定语料修订、法规版本、条款节点与原文片段。当前两条腿都走不通: 共享法规库没有公开检索入口;RDM 现有网络腿由模型提出 URL 和摘录后再抓取比对,无法证明真实搜索发生过,基座也没有搜索能力。
  • 希望符号:pkg/extension 新增可选能力 RegulationsHost(分发 Host 侧消费)、RegulationsRegistrar(进程内 Registry 侧注册)、 提供方接口 RegulationsProvider、请求/结果 DTO、状态常量、RegulationsSchemaVersion 与 ErrRegulationsUnavailable。 Host 接口本身不加方法。
  • 为何现有注册点组合不出来:见第 2 节。
  • 兼容:纯加法的可选能力;SurfaceVersion 由 2.14.0-experimental 升为 2.15.0-experimental,见第 5 节。

2. 为何现有扩展点组合不出来

现有扩展点 不能承载的原因
Host.KnowledgeSearch / KnowledgeWrite owner 作用域的项目知识检索,返回 core.RetrievalHit。共享法规不属于任何 owner:写进每个 owner 的知识库会复制语料、失去修订不可变性;其返回类型没有语料修订、法规/版本/节点稳定 ID、原文哈希、定位与披露状态,加字段也改变不了 owner 隔离语义。计划 KTD14 明确禁止经它承载共享法规
项目作用域知识(projectID 参数、项目授权) 共享法规访问不应随项目扩大或缩小;借项目 ID 访问会让项目成员关系变成法规访问面,也让同一条款在不同项目下出现不同可见性
AccountDirectoryRegistrar 机制相同(受信插件注册、Host 可选接口消费),但语义专用于账号目录候选判定,不能复用为法规检索
通用工具 / 命令总线 / KV / 后台任务 / 审计 都不能让一个插件把只读能力安全地提供给另一个插件:没有可归属的真实搜索记录,也不在发往外网前做查询最小化;证据无法钉住语料修订;任何插件都可以注册同名命令冒充提供方,不存在“单一受信提供方”
下游直接导入法规仓 绕过基座的调用方身份、查询最小化与审计边界,违反 RDM“只经基座公开扩展”的约束

因此需要一个新扩展点同时满足四点:共享的非项目语料;可归属、做过查询最小化的真实网络搜索;钉住语料修订的不可变证据;单一受信提供方。

3. 公开符号(pkg/extension/regulations.go)

3.1 常量与错误

  • RegulationsSchemaVersion = 1:基座唯一接受的请求 schema;其他值在到达提供方前被拒绝。
  • 法规检索状态:RegulationsStateOK = "ok"、RegulationsStateEmpty = "empty"、RegulationsStateDegraded = "degraded"、RegulationsStateUnavailable = "unavailable"。
  • 法规披露级别:RegulationDisclosureFull = "full"、RegulationDisclosureExcerpt = "excerpt"、RegulationDisclosureMetadataOnly = "metadata_only"、RegulationDisclosureWithheld = "withheld"。
  • 网络搜索状态:WebSearchStateOK = "ok"、WebSearchStateEmpty = "empty"、WebSearchStateUnavailable = "unavailable"、WebSearchStateRateLimited = "rate_limited"、WebSearchStateRejected = "rejected"、WebSearchStateError = "error"。
  • 网页抓取状态:WebCaptureStateCaptured = "captured"、WebCaptureStateRejected = "rejected"、WebCaptureStateFailed = "failed"。
  • 网页留存级别:WebRetentionFull = "full"、WebRetentionExcerpt = "excerpt"、WebRetentionMetadataOnly = "metadata_only"。
  • ErrRegulationsUnavailable("extension: regulations provider unavailable"):能力不可用。调用方不得回退到 KnowledgeSearch。

3.2 接口

// 由受信的法规知识插件(插件 ID "kbreg")实现并注册;基座不内置实现。
type RegulationsProvider interface {
    SearchRegulations(context.Context, RegulationsCaller, RegulationsSearchRequest) (RegulationsSearchResult, error)
    ResolveRegulationEvidence(context.Context, RegulationsCaller, RegulationEvidenceRequest) (RegulationEvidence, error)
    SearchWeb(context.Context, RegulationsCaller, WebSearchRequest) (WebSearchResult, error)
    CaptureWeb(context.Context, RegulationsCaller, WebCaptureRequest) (WebCapture, error)
}

// 编译进程内 Registry 的可选能力;只有插件 ID "kbreg" 可注册,且只能注册一次。
type RegulationsRegistrar interface {
    RegisterRegulationsProvider(RegulationsProvider) error
}

// 分发 Host 句柄的可选能力(类型断言获取);缺失即不可用,调用方不得回退到 KnowledgeSearch。
type RegulationsHost interface {
    SearchRegulations(context.Context, RegulationsSearchRequest) (RegulationsSearchResult, error)
    ResolveRegulationEvidence(context.Context, RegulationEvidenceRequest) (RegulationEvidence, error)
    SearchWeb(context.Context, WebSearchRequest) (WebSearchResult, error)
    CaptureWeb(context.Context, WebCaptureRequest) (WebCapture, error)
}

RegulationsHost 与 RegulationsProvider 方法一一对应,区别只在 Host 侧没有 RegulationsCaller 参数:调用方身份由基座填写,调用方无法注入。

3.3 DTO 字段

JSON 名为 camelCase。下表“必出”指没有 omitempty、零值也会序列化的字段,其余字段均为 omitempty。 最小 WebSearchRequest{SchemaVersion: 1, Query: "q"} 序列化为 {"schemaVersion":1,"query":"q"}。

RegulationsCaller(基座从可信分发上下文填写)

字段 JSON 含义
PluginID pluginId(必出) 发起调用的插件 ID(消费方,例如 rdm),不是提供方 ID
Actor actor 本次分发的操作主体
Owner owner(必出) 本次分发的 owner

刻意不含项目 ID:共享法规访问既不随项目扩大,也不随项目缩小。

RegulationsSearchRequest

字段 JSON 含义
SchemaVersion schemaVersion(必出) 必须等于 RegulationsSchemaVersion
Query query(必出) 检索文本
CorpusRevision corpusRevision 要检索的语料修订;空表示当前已发布修订,结果回显实际检索的修订
Kinds kinds 文献类别过滤(法规、标准、指导原则等,取值由提供方定义)
CurrentOnly currentOnly 只返回现行版本
DocNo docNo 按文号过滤
ClauseNo clauseNo 按条款号过滤
Limit limit 结果数上限,由提供方解释

RegulationsSearchResult

字段 JSON 含义
CorpusRevision corpusRevision(必出) 实际检索的语料修订
State state(必出) ok / empty / degraded / unavailable
DegradedReasons degradedReasons 降级原因(例如向量索引不可用)
Hits hits(必出) 命中列表 []RegulationHit

RegulationHit

字段 JSON 含义
RegulationID / EditionID / NodeID regulationId / editionId / nodeId(必出) 法规、版本、条款节点的稳定 ID,重新建索引后不变
DocNo / Title docNo / title 文号与标题
EditionLabel / LegalStatus editionLabel / legalStatus 版本标签与法律效力状态(现行、废止等)
ClauseNo / HeadingPath clauseNo / headingPath 条款号与章节标题路径
Excerpt / ExcerptStart / ExcerptEnd excerpt / excerptStart / excerptEnd 按披露级别允许返回的原文片段及其偏移
NodeSHA256 / SourceAssetSHA256 nodeSha256 / sourceAssetSha256 节点文本哈希与来源资产(原件)哈希
PageStart / PageEnd pageStart / pageEnd 原件页码定位
Disclosure disclosure(必出) full / excerpt / metadata_only / withheld
WithheldReason withheldReason 不披露原文的原因(例如来源权利限制)
IsSummary isSummary 命中的是摘要而不是原文
MatchKinds matchKinds 命中方式(全文、向量、编号等,取值由提供方定义)
Score score 相关度分数

RegulationEvidenceRequest(取回钉住修订的节点原文片段)

字段 JSON 含义
SchemaVersion schemaVersion(必出) 必须等于 RegulationsSchemaVersion
CorpusRevision corpusRevision(必出) 证据所钉住的语料修订
NodeID nodeId(必出) 条款节点 ID
Start / End start / end 可选的片段偏移

RegulationEvidence

字段 JSON 含义
CorpusRevision corpusRevision(必出) 证据所属修订
RegulationID / EditionID regulationId / editionId 法规与版本 ID
NodeID nodeId(必出) 条款节点 ID
ClauseNo clauseNo 条款号
Excerpt / Start / End excerpt / start / end 原文片段与偏移
NodeSHA256 / SourceAssetSHA256 nodeSha256 / sourceAssetSha256 节点文本哈希与来源资产哈希
PageStart / PageEnd pageStart / pageEnd 页码定位
Disclosure disclosure(必出) 同 RegulationHit.Disclosure
WithheldReason withheldReason 不披露原因
HashVerified hashVerified(必出) 提供方报告的哈希校验结果;基座原样透传,不自行校验

WebSearchRequest(真实网络搜索)

字段 JSON 含义
SchemaVersion schemaVersion(必出) 必须等于 RegulationsSchemaVersion
Query query(必出) 发往外网的搜索词;基座先去首尾空白再校验
Exclusions exclusions 调用方声明的机密词(项目名、编号、人员等);查询含任一词即由基座拒绝
PreferOfficial preferOfficial 优先官方来源
Limit limit 结果数上限,由提供方解释

WebSearchResult

字段 JSON 含义
Provider provider 实际使用的搜索提供方
SentQuery sentQuery 实际发往外网的查询
SearchedAt searchedAt(必出) 搜索时间
State state(必出) ok / empty / unavailable / rate_limited / rejected / error
Reason reason 非 ok 状态的原因
Results results 排序结果 []WebSearchHit

WebSearchHit:Rank(rank,必出,排名)、URL(url,必出)、Title(title)、Snippet(snippet,搜索摘要,不是已核验证据)、Publisher(publisher,发布机构)、Official(official,是否被提供方判定为官方来源)。

WebCaptureRequest(抓取先前 SearchWeb 找到的页面)

字段 JSON 含义
SchemaVersion schemaVersion(必出) 必须等于 RegulationsSchemaVersion
URL url(必出) 要抓取的地址
SearchProvider / SearchQuery / SearchRank searchProvider / searchQuery / searchRank 关联的来源搜索(提供方、查询与排名),用于证据溯源

WebCapture

字段 JSON 含义
CaptureID captureId 抓取记录 ID
FinalURL finalUrl 跟随重定向后的最终地址
FetchedAt fetchedAt(必出) 抓取时间
ContentType / ByteSize / SHA256 contentType / byteSize / sha256 内容类型、字节数与内容哈希
Retention retention full / excerpt / metadata_only
Text text 页面全文,仅当来源权利允许全文留存时出现
Candidates candidates 从页面解析出的条款候选 []WebClauseCandidate
State state(必出) captured / rejected / failed
Reason reason 非 captured 状态的原因

WebClauseCandidate:ClauseNo(clauseNo)、Excerpt(excerpt,必出)、Start / End(start / end,必出,片段在抓取文本中的偏移)。

4. 基座强制行为(internal/app/regulations.go、compiled.go)

4.1 注册

  1. 只有插件 ID 为 kbreg 的编译进程内插件可注册提供方;其他插件 ID 或 nil 提供方返回错误。
  2. 只能注册一次:重复注册返回错误,已注册的第一个提供方保持不变。
  3. 声明式 Registry(compiledRegistry)一律拒绝 RegisterRegulationsProvider。
  4. 宿主未装配提供方槽时,注册返回“registration unavailable”错误;带清单记录的包装 Registry(recordRegistry)只把注册转发给内层 RegulationsRegistrar。

4.2 每次 Host 调用

按以下顺序执行,四个方法一致:

  1. 先做账号有效性守卫(requireEffective);停用等无效账号直接返回守卫错误,提供方不会被调用。
  2. 没有已注册提供方或没有审计存储时返回 ErrRegulationsUnavailable。调用绝不在无审计的情况下执行;这类不可用调用不写审计。
  3. RegulationsCaller 由基座填写为 {PluginID: 调用插件, Actor: 分发主体, Owner: 分发 owner},不含项目 ID。
  4. SchemaVersion != RegulationsSchemaVersion 时拒绝并返回错误,提供方不会被调用;写一条 state=invalid 的审计。
  5. 只有 SearchWeb 做查询最小化校验:
    • 先去掉 Query 首尾空白,提供方收到的是去空白后的查询;
    • 去空白后为空、不是合法 UTF-8 或超过 256 个 rune 时返回错误(1-256 按 rune 计,不按字节计),写 state=invalid 审计;
    • 查询包含任一 Exclusions 词时(每个词先去首尾空白,空词忽略,按小写子串匹配,大小写不敏感),基座直接返回 WebSearchResult{State: "rejected", Reason: "query contains an excluded term"} 且 error 为 nil,提供方不会收到该查询,审计记 state=rejected。
  6. 提供方返回的 State、Disclosure、Retention、DegradedReasons、HashVerified 等全部字段原样透传;基座不把 degraded、withheld、rejected、rate_limited 或失败包装成成功,提供方返回的 error 原样返回。
  7. CaptureWeb 在基座侧不校验 URL:抓取安全控制(私有/保留地址、重定向、大小、权利留存)属于提供方职责(计划 U5)。

4.3 审计

每次到达 schema 校验的调用恰好写一条基座审计事件:owner 为分发 owner,Actor 为 plugin:<调用插件>,Target 为 kbreg。

方法 动作 revision count state querySha256
SearchRegulations regulations.search 成功时为结果修订,否则为请求修订 len(Hits) 提供方 State Query 原文的 SHA-256
ResolveRegulationEvidence regulations.evidence 结果修订非空时取结果,否则为请求修订 成功为 1 成功为 ok 不记
SearchWeb web.search 不记 len(Results) 提供方 State,或基座的 rejected 去空白后查询的 SHA-256
CaptureWeb web.capture 不记 len(Candidates) 提供方 State 去空白后 SearchQuery 的 SHA-256
  • 审计证据 JSON 只有 revision、count、state、querySha256 四个字段(revision 与 querySha256 为空时省略);查询明文、法规片段、搜索摘要与网页全文都不进审计。
  • 失败时 state 取 invalid(schema 或查询校验失败)或 error(提供方返回 error),count 为 0。
  • 提供方 error 优先返回;调用成功但审计写入失败时,结果被扣留并返回审计错误。

5. 兼容

  • 可选能力:RegulationsHost 与 RegulationsRegistrar 都经类型断言获取,Host 与 Registry 接口没有新增方法;旧插件、第三方 fake Host 与旧 Registry 实现照常编译。断言失败即能力不可用。
  • 纯加法:新增 3 个接口、12 个 DTO、20 个状态常量、RegulationsSchemaVersion 与 ErrRegulationsUnavailable;surface.txt 快照同步增加这些顶层名与接口方法。按 compat-policy“每次加法升次版本号”,SurfaceVersion 由 2.14.0-experimental 升为 2.15.0-experimental。
  • 不改 KnowledgeSearch:owner 作用域项目知识检索的签名与语义不变;法规能力缺失时调用方不得回退到它。
  • 组合方式:法规插件以编译进程内插件注册(extension.RegisterCompiled,插件 ID kbreg),与 RDM 同二进制组合。当前只有进程内 inprocHost 实现 RegulationsHost;sidecar /host/v1 HTTP 路由不在本扩展范围,出现真实需求再申请。
  • 请求演进:请求 schema 以 RegulationsSchemaVersion 显式版本化;新增可选字段一律带 omitempty,不兼容的请求变更须升 schema 版本并另行申请。

6. 验收(对应 Backend 测试)

验收条件 测试
能力可选:空 Host/Registry 不暴露能力;schema 版本为 1;错误值已定义;下游仅凭 pkg/extension 即可实现提供方 pkg/extension TestRegulationsContractIsOptional
状态常量的线上取值固定;可选字段省略;RegulationsCaller 序列化不含 project pkg/extension TestRegulationsWireNames
无提供方或无提供方槽时四个方法都返回 ErrRegulationsUnavailable,且不写审计 internal/app TestRegulationsUnavailableWithoutProvider
只有 kbreg 可注册;nil 被拒;重复注册被拒且不替换;无槽时注册被拒 TestRegulationsRegistrationOnlyTrustedKbregOnce
声明式 Registry 拒绝注册;记录型包装 Registry 转发 kbreg 注册 TestRegulationsDeclarativeRegistrationRejected
提供方收到的 Caller 由分发上下文填写 TestRegulationsCallerFilledFromDispatchContext
schema 版本 0 与 2 被拒,提供方不被调用 TestRegulationsRejectsOtherSchemaVersions
空查询、纯空白查询、257 rune 查询被拒;256 rune 查询通过且提供方收到去空白后的查询 TestRegulationsWebQueryBounds
含排除词(大小写不敏感)的查询返回 rejected、提供方不被调用、审计不含该词;空排除词不拦截 TestRegulationsExclusionRejectedBeforeProvider
提供方的 degraded、withheld、metadata_only、rate_limited、rejected 与 error 原样透传 TestRegulationsProviderStatesPassThroughUnchanged
每次调用一条审计,只含 revision、count、state、querySha256,不含查询明文、片段或网页文本 TestRegulationsAuditEvidenceOmitsQueryAndText
账号无效时四个方法返回守卫错误,提供方不被调用 TestRegulationsRequireEffectiveAccount
只用 pkg/extension 的测试插件以 kbreg 经编译插件路径注册,冒充插件注册失败,另一编译插件经 Host 断言调用成功并留下基座审计 TestRegulationsCompiledPluginCompatibility
未编入提供方时消费方命令不会提交成功 TestRegulationsCompiledConsumerWithoutProvider

另需 pkg/extension 的 surface 快照测试通过(surface.txt 与导出面一致)。

7. 分工

  • Backend(本扩展,计划 U9):只负责公开契约、基座强制行为与兼容测试;不内置提供方、不实现搜索或抓取、不实现 RDM 适配器。
  • Agent-System-KB-Regulations(计划 U2–U5):实现并以插件 ID kbreg 注册 RegulationsProvider,负责语料修订、检索、披露级别、真实搜索提供方适配、网页抓取安全控制与权利留存。
  • Agent-System-RDM(计划 U6/U7):经 RegulationsHost 消费本能力,把语料检索与网络搜索作为两条独立证据腿做双源核验;负责项目授权、引用渲染与业务审计投影。能力不可用时必须显式降级,不得回退到 KnowledgeSearch,也不得宣称完成双源核验。
  • Dev(计划 U8):组合 pin 与组合验证。

8. 不在本文范围

  • sidecar /host/v1 HTTP 路由与远程提供方形态。
  • 搜索提供方选择、实网联调与费用(需用户另行确认提供方与凭据)。
  • RDM 证据批次模型、引用展示与篡改检测记录(计划 U6/U7)。