diff --git a/docs/proposals/2026-10-18-ext-20-regulations-provider.md b/docs/proposals/2026-10-18-ext-20-regulations-provider.md new file mode 100644 index 0000000..52b468e --- /dev/null +++ b/docs/proposals/2026-10-18-ext-20-regulations-provider.md @@ -0,0 +1,276 @@ +# 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 的基座公开契约)。 +规范依据:[扩展面兼容与演进](2026-10-01-extension-compat-policy.md)(§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 接口 + +```go +// 由受信的法规知识插件(插件 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)。 diff --git a/docs/proposals/INDEX.md b/docs/proposals/INDEX.md index ee8369c..e7036cc 100644 --- a/docs/proposals/INDEX.md +++ b/docs/proposals/INDEX.md @@ -7,6 +7,9 @@ [分层分支交付与规范精简 r1](2026-10-18-delivery-model-v3.md):2026-10-18 经用户批准实施; 由 Dev 治理大任务 `feature/delivery-model-v3` 推进,实时进度见该任务卡与 PR。 +[EXT-20 共享法规检索与网络证据扩展点](2026-10-18-ext-20-regulations-provider.md):法规知识库计划 U9 的基座公开契约; +2026-10-18 经用户授权,由 Dev initiative `feature/regulations-kb` 推进,实时进度见该任务卡与 PR。 + 待评审(收录不是批准):[svc 插件组合部署(--with)方案 r1](2026-10-14-svc-plugin-compose-deploy.md); [分层 Python 环境方案 r1](2026-10-15-layered-python-env.md)(8788 无法生成 docx 的环境层;白名单自动批准,其余审批)。