21 KiB
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 注册
- 只有插件 ID 为
kbreg的编译进程内插件可注册提供方;其他插件 ID 或nil提供方返回错误。 - 只能注册一次:重复注册返回错误,已注册的第一个提供方保持不变。
- 声明式 Registry(
compiledRegistry)一律拒绝RegisterRegulationsProvider。 - 宿主未装配提供方槽时,注册返回“registration unavailable”错误;带清单记录的包装 Registry(
recordRegistry)只把注册转发给内层RegulationsRegistrar。
4.2 每次 Host 调用
按以下顺序执行,四个方法一致:
- 先做账号有效性守卫(
requireEffective);停用等无效账号直接返回守卫错误,提供方不会被调用。 - 没有已注册提供方或没有审计存储时返回
ErrRegulationsUnavailable。调用绝不在无审计的情况下执行;这类不可用调用不写审计。 RegulationsCaller由基座填写为{PluginID: 调用插件, Actor: 分发主体, Owner: 分发 owner},不含项目 ID。SchemaVersion != RegulationsSchemaVersion时拒绝并返回错误,提供方不会被调用;写一条state=invalid的审计。- 只有
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。
- 先去掉
- 提供方返回的
State、Disclosure、Retention、DegradedReasons、HashVerified等全部字段原样透传;基座不把degraded、withheld、rejected、rate_limited或失败包装成成功,提供方返回的 error 原样返回。 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,插件 IDkbreg),与 RDM 同二进制组合。当前只有进程内inprocHost实现RegulationsHost;sidecar/host/v1HTTP 路由不在本扩展范围,出现真实需求再申请。 - 请求演进:请求 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):实现并以插件 IDkbreg注册RegulationsProvider,负责语料修订、检索、披露级别、真实搜索提供方适配、网页抓取安全控制与权利留存。- Agent-System-RDM(计划 U6/U7):经
RegulationsHost消费本能力,把语料检索与网络搜索作为两条独立证据腿做双源核验;负责项目授权、引用渲染与业务审计投影。能力不可用时必须显式降级,不得回退到KnowledgeSearch,也不得宣称完成双源核验。 - Dev(计划 U8):组合 pin 与组合验证。
8. 不在本文范围
- sidecar
/host/v1HTTP 路由与远程提供方形态。 - 搜索提供方选择、实网联调与费用(需用户另行确认提供方与凭据)。
- RDM 证据批次模型、引用展示与篡改检测记录(计划 U6/U7)。