oh-my-muse/.agents/knowledge/external-deps-and-gotchas.md
lili 883e6785d2 docs(knowledge): 记 Maven 增量编译陈旧坑(验证门禁反向红时致假绿)
本会话验证 .application 边界门时实证:仅改 ArchUnit 谓词逻辑后重跑,.class
时间戳更新但方法体未真正重编译→反向"应红"测试假绿(跑的旧字节码)。
沉淀可靠复跑配方(rm maven-status / -Dmaven.compiler.useIncrementalCompilation=false)
入 knowledge §三,供后续门禁自检避免"反假绿本身假绿"。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 14:26:39 -07:00

21 KiB
Raw Blame History

knowledge:外部依赖、验收环境与工程坑(蒸馏自历史 memorys)

蒸馏自 2026-06-14 已清理的过程文档(外部验收留痕、P1 收口、studio/admin 搭建),只保留"删了会真丢"的操作事实与踩坑。凭据只记来源文件,不记明文。过时即更。

一、外部依赖与验收环境

依赖 地址 关键事实
New-API(LLM 网关) http://100.64.0.8:3000,OpenAI 兼容 /v1/chat/completions 已验收模型 MiniMax-M2.5;容器 new-api 端口 3000;其 DB 在 infra-postgres(宿主 100.64.0.8:5433)
RAGFlow(知识运行时) http://100.64.0.8(及 :9380) /api/v1/datasets/api/v1/retrieval;健康检查 /v1/system/healthz
开发 PG / Redis 见下方凭据来源 远端 PG 15(用户确认可用,不强制 PG16);Redis 100.64.0.8:6379

凭据来源(明文不入库,只记位置):

  • 外部验收:muse-cloud/scripts/dev/p1r-external-acceptance.env(set -a; . 该文件; set +a 加载;含 New-API base/token、MUSE_AI_NEW_API_DEFAULT_MODEL_KEY=MiniMax-M2.5、RAGFlow base/key、GraphRAG 开关)。
  • 开发基础设施:~/.config/muse-repo/infra.env(PG/Redis 连接 + 真实凭据)。
  • ⚠️ 安全:上述令牌仅限当前内网验收上下文;仓库若同步到更大范围,必须先轮换 New-API 令牌

Live 验收 harness(opt-in,默认跳过,MUSE_P1R_EXTERNAL_ACCEPTANCE=true 才真跑):

  • muse-module-ai/.../application/muse/facade/P1rNewApiLiveAcceptanceIT.java(New-API)
  • muse-module-knowledge/.../application/muse/facade/P1rRagFlowLiveAcceptanceIT.java(RAGFlow)
  • 另:muse-server/.../framework/api/P1rAiRuntimeEndToEndLiveAcceptanceIT.javaP1rKnowledgeRuntimeEndToEndLiveAcceptanceIT.java
  • 输出脱敏(只打 endpoint/模型/id/状态/耗时/usage/key 长度 + sha256 前 12);RAGFlow smoke 会留存 p1r-live-<时间戳> dataset 不删。
  • runtime client live ≠ coverage completed:仍需 Muse API 触发 + 落库 + 审计 + 失败路径证据(见 golden-journey)。

二、外部集成兼容坑(实证)

  • New-API 系统管理令牌 ≠ RAGFlow key:用前者打 RAGFlow /api/v1/datasets 返回 code=109 Authentication error,必须用独立 ragflow-* key。
  • RAGFlow 建数据集:POST /api/v1/datasetsconfig:{} 返回 code=101 Extra inputs are not permitted;只发 namecode=0。adapter 已改为 createDataset 只发 name,config 走独立 updateDatasetConfig
  • RAGFlow 文档状态轮询:用真实支持的 ?id=<docId>(单个);多 id 在 adapter 内 fail-closed 为 VALIDATION_ERROR
  • GraphRAG attribution:默认 fail-closed(ATTRIBUTION_NOT_CONFIGURED);真跑需 MUSE_KNOWLEDGE_RAGFLOW_GRAPHRAG_ATTRIBUTION_READY=true + ..._GRAPHRAG_DATASET_ID

三、前端 / 构建坑速查

  • studio(Vite + React + TS6.0):erasableSyntaxOnly 禁用 constructor 参数属性;exactOptionalPropertyTypes 下可选属性需规避写法;采纳/Diff 用 Coarse-to-Fine 分级 Diff 规避 3000+ 字正文上 O(N×M) LCS 性能爆炸;MSW 仅 import.meta.env.DEV 启用。
  • admin(Vben):museAdminApi 只拼 /muse/**(baseURL 已是 /admin-api,否则双前缀 /admin-api/admin-api);路由测试不能src/router/routes/modules/**(会被 Vite 动态路由 glob 当业务路由);Opsera pre-commit 缺失时跳过用 touch /tmp/.opsera-pre-commit-scan-passed
  • git 边界(未决):muse-admin/.gitignore 作"独立子仓库边界",源码靠 git add -f 纳入——长期纳管方式待定(见进度总账 TODO)。
  • Maven 增量编译陈旧坑(验证门禁/ArchUnit 改动时必踩,2026-06-17 实证):只改一个测试方法体/helper 的逻辑(如把 ArchUnit 谓词从 .application 临时改 .api 做反向红验证)后重跑,.class 时间戳会更新但方法体未真正重编译→ 反向测试假绿(看似规则没触发,实为跑的旧字节码)。根因=maven-compiler-plugin 的 maven-status/.../inputFiles.lst 增量跟踪没识别到变更;手动 rm target/test-classes/... 也不够(status 没更新仍跳过)。可靠复跑:rm -rf <module>/target/maven-status 或加 -Dmaven.compiler.useIncrementalCompilation=false;或干脆 mvn ... clean做"反向应红"自检时尤其要确认真重编译了,否则反假绿本身就是假绿。

四、跑 P1R real-PG 集成测试(IT)的配方 + 本机代理坑(2026-06-14 实证)

结论先行:P1 后端 23 个 P1r*IT(completed-approval / Flyway 迁移 / events-publish outbox / live-acceptance)可在共享远端 PG 上真跑验证2026-06-15 mini-infra PG 全量重跑 = 23/23 IT 类全绿(99 用例 0F/0E;2 例 external-acceptance 因未设 MUSE_P1R_EXTERNAL_ACCEPTANCE 而 assumeTrue 跳过)。此前唯一红 P1rKnowledgeFlywayMigrationIT(硬编码版本断言随 schema 增长失效,V14→V21→V23 已复发两次)已修为动态:读本次 Flyway MigrateResult.targetSchemaVersion/migrationsExecuted 自适应(commit 4d46d7a,Codex+Opus 双代理并行提案合并;反假绿仍校验迁移成功 + history 计数一致 + Knowledge 表/索引/列存在)。

▲ 最大坑(排查极久):本机 env HTTP_PROXY=HTTPS_PROXY=http://127.0.0.1:7897 → JVM 取 socksProxyHost=127.0.0.1:7897,把所有 Java socket 走 SOCKS;转发 HTTP 正常,但破坏 PostgreSQL 原始线协议(发完 StartupMessage 即被 EOF,认证前关闭,极像 pg_hba/密码错)。 nc/curl 直连正常,只有 JDBC/JVM 中招;且 env -u HTTP_PROXY 清不掉(socksProxyHost 由 JVM 层注入)。

  • 判定 PG 真可达:printf '\x00\x00\x00\x08\x04\xd2\x16\x2f' | nc -w5 100.64.0.8 5433 | xxdN 即 PG 在说话(纯 nc 直连,不经代理)。
  • 修复:给 forked 测试 JVM 加 -DargLine='-DsocksProxyHost= -DsocksProxyPort='(清空 SOCKS,JVM 直连 tailnet);代码内可用 new Socket(java.net.Proxy.NO_PROXY)

跑单个 IT 的配方(已验证):

export MUSE_POSTGRES_PASSWORD=<root 密码;来自 infra.env / 用户提供,勿入库>
mise exec --cd muse-cloud -- mvn -pl muse-server -am test \
  -Dtest=<P1r...IT> -Dsurefire.failIfNoSpecifiedTests=false \
  -Dp1r.flyway.url='jdbc:postgresql://100.64.0.8:5433/<专属_test库>' \
  -Dp1r.flyway.user=root -Dp1r.flyway.locations='filesystem:sql/muse' \
  -DargLine='-DsocksProxyHost= -DsocksProxyPort='

要点:① 用 test 阶段 + -Dtest=<IT类>(surefire 显式点名即可跑 *IT),别用 verify/integration-test——那会触发 package/spring-boot repackage 把 member-server 等打成 fat jar,破坏跨模块编译(package ... does not exist),且 surefire 会先跑到平台陈旧红(QiniuSmsClientTest 时区)而中止整个 reactor。② p1r.flyway.url 库名必须 _test 结尾(IT 有 assertTestDatabaseUrl 守卫 + 会 flyway.clean() DROP 全库,绝不能指 muse_local / muse);各 IT 用各自专属 _test 库(muse_p1r_*_test 系列已存在;缺的用 root CREATE DATABASE,跑完可 DROP)。③ p1r.flyway.locations 必须恰为 filesystem:sql/muse(IT 断言该精确字符串,再从 user.dir 上溯定位 sql/muse)。④ 密码只走 env(MUSE_POSTGRES_PASSWORD/P1R_FLYWAY_PASSWORD;IT 有 assertNoPasswordSystemProperties,用 -D 传会红)。⑤ live-acceptance IT 需 set -a; . scripts/dev/p1r-external-acceptance.env; set +a 载入 token,且部分用例额外需 MUSE_P1R_EXTERNAL_ACCEPTANCE=true 才真跑(否则 assumeTrue 跳过、显示 Skipped)。⑥ 整套 -Dtest='P1r*IT' 批量跑必须加 -DreuseForks=false:P1rMarketFlywayMigrationITSystem.setProperty("p1r.flyway.url", maskedUrl) 脱敏覆盖,surefire 默认 fork 复用下污染同 fork 后续 IT 的系统属性 → 后续 IT 假报"缺少 p1r.flyway.url";每类独立 fork 即净(2026-06-15 实证:默认复用→多 IT 假红;-DreuseForks=false→23/23 全绿)。⑦ argLine 内可一并传 -Dp1r.flyway.url/user/locations(本轮即如此),与 maven -D 等效到 forked JVM。

远端 PG 实为 PostgreSQL 17.10(§一表内"15"为旧记,以此为准),超管用户 root;本机无本地 PG(127.0.0.1:5432 关闭)。批量顺序跑见交付报告(禁止并发 maven,会损坏 target)。

五、MVP #1「AI 候选采纳」纵切:后端门禁 + 活体全栈三大阻塞(2026-06-14 实证)

后端机械门禁 IT(已绿,确定性):muse-server/.../api/P1rContentMergeSuggestionIT——真实 PG + 真实 AiSuggestionMergeProjectionFacade(@Bean 直接实例化注入真实 MuseAiSuggestionMapper,规避 @ConditionalOnBean 时序;配套 @MapperScan("cn.iocoder.muse.module.ai.dal.mysql.muse"))读真种 muse_ai_suggestion,经 MockMvc 打 POST /app-api/muse/works/{workId}/blocks/{blockId}/suggestion-merges。5 路径:正向写 Canonical(revision1→2)/revision 冲突/幂等回放/非 pending/「审」缺失。跑法同 §四配方,-Dtest=P1rContentMergeSuggestionIT,需预建 muse_p1r_merge_slice_test(IT 自身 flyway.clean+迁移)。可合并候选的种子契约见 IT 内 insertMergeableSuggestion:status=pending、source_status=active、数值 authorization_snapshot_id、content_snapshot 带 content、diff_summary 带齐 outputComplianceResultId/staticCheckResultId/licenseRestrictionSnapshot(facade 缺一即判 unavailable)。

起活体全栈 muse-server 的三大阻塞(到「用户在 app 里真能用」):

  1. 远端 dev 库缺 yudao 基座 schema:muse_local 有 Muse 表(muse_content_*/muse_ai_suggestion)但system_tenant 等基座表、无 flyway_schema_history;muse_slice_live(新建)全空。muse-server 装配 system/infra 等模块,Flyway 只迁 Muse(V1-V21,sql/muse),基座 schema 未 Flyway 化(对抗复盘已记),故全栈 app 无法服务租户校验请求。起活体须先 provision 基座(yudao SQL dump)。【2026-06-14 已解,见 §六】:基座已固化为 sql/dev/yudao-base-*-postgres.sql 灌入 muse_slice_live,全栈 muse-server 已成功启动并 curl 活体跑通 AI候选→Canonical 采纳;后端 merge 链路另有真实 PG IT 证明。
  2. studio AI 流 parser 漂移(已修复 2026-06-14):connectAIStream(src/lib/sse.ts)原按 JSON data.type 单行分发,真后端(MuseAiTaskStreamServiceImpl 用 Spring SseEmitter.name(event).data(json))把事件名放 SSE event: 行(event:chunk/quality_check/done/error + data 行 JSON 无 type)→ 旧实现真实生成流全收不到。修复=复用同文件 createEventStreamParser(按 event: 行分发 + id/comment/多行 data 拼接),SSEEventHandler 回调契约不变。连带 gotcha:done.data 的 taskId/suggestionId 后端是 Long→JSON number,而前端全链路(onDone 类型、采纳请求体 MergeBlockSuggestionRequest、OpenAPI)按 string——已在 sse.ts done 分支于解析边界 String() 归一(缺失值保持 undefined 以维持「无候选→禁用采纳」判定);mock/契约测试改用数字 suggestionId 复现后端形态。验证用 ./node_modules/.bin/ 的 tsc -b --force / vitest run(47) / vite build 全绿(corepack pnpm exec 会触发 install 校验失败,勿用)。
  3. AI 运行时未写「审」字段(已修复 2026-06-14,chip task_092cfc32):原 createRuntimeSuggestion 不写 outputComplianceResultId/staticCheckResultId/licenseRestrictionSnapshot 且 source_status='verified'(merge 要 'active')→ 真实生成的候选不可合并。现新增 MuseAiCandidateReviewService 做真实轻量审(输出合规扫描/静态检查/许可快照,通过才给可追溯审结果 ID),createRuntimeSuggestion 写齐三审字段(通过时)+ 顶层 content(脱敏 summary,供 facade 可用性)+ source_status='active' + 数值授权快照(envelope id 为数值时落 BIGINT 列,沿用 MuseSuggestionServiceImpl 的 envelope=authz 约定)。遗留:非数值 envelope id 暂无法落 BIGINT 授权快照列(已知列能力限制),待授权快照建模收口;完整 provider 正文按数据主权不持久化,采纳走 merge_after_edit(用户回传所审最终正文)。门禁 P1rContentMergeGeneratedSuggestionIT(真实 PG)证明:经真实 createShadowSuggestionCandidate 产出的候选可被 mergeBlockSuggestion 采纳写入 Canonical。

本机替代与凭据(联调用):远端 Redis 需密码(未提供)→ 本机 redis-server(brew 8.x)+ --requirepass 替代(Redis 仅 session/cache 管道,非被测对象),infra.env 指 127.0.0.1。mock 登录:application-local.yaml muse.security.mock-enable=true,令牌 = mockSecret(test)+userId,即 test1=userId 1;tenant 来自请求头 getTenantId(request)~/.config/muse-repo/infra.env(仓库外,永不入库)本会话已填 root PG 密码 + 本机 redis;注意 app 默认连用户 muse_dev(密码未知),联调改 MUSE_POSTGRES_USERNAME=root安全:该文件含明文 root 口令,轮换/清理由用户掌握。

六、起全栈 muse-server(单体): 已成功启动 + 活体合并实证(2026-06-14 达成)

结论(里程碑):muse-server 本项目史上首次以单进程单体成功启动(Started MuseServerApplication in 17.985 seconds,Tomcat:48080),并在真实 HTTP + mock 鉴权 + 租户过滤 + 真实 PG(muse_slice_live)上端到端跑通 MVP #1 纵切:AI 候选(Shadow,muse_ai_suggestion)→ POST /app-api/muse/works/{w}/blocks/{b}/suggestion-merges(accept_as_is)→ Canonical 块正文真实写入(content_text=AI候选正文、revision 1→2、来源归因 ai_suggestion + lineage 落库),负路径(陈旧 expectedBlockRevision)被乐观锁拒(code 1041000002 Block版本冲突)。全程无 permissive stub,真实失败关闭

下文为打通全栈装配的 6 类硬阻塞实测复盘(可复用):

已拆(可复用):

  1. build:member-server repackage 无 classifier → fat jar → 下游 market 编译期 package does not exist,致 mvn package 全项目破损(仅 test 阶段不触发,故 CI test 假绿)。修:member-server repackage 配 <classifier>exec</classifier>(commit 1f89007)。
  2. postgres 基座:本仓原无 yudao postgres 基座 dump(只有 H2 测试 schema)。已翻译 system/infra/member 测试 schema→PostgreSQL(49 表)+ 最小种子,固化 muse-cloud/sql/dev/yudao-base-schema-postgres.sql + yudao-base-seed-postgres.sql(实测 0 失败)。灌库后 app Flyway(--spring.flyway.baseline-on-migrate=true)成功补 Muse V1-V21、Tomcat 起、Spring 初始化。muse_slice_live 现已含 基座+Muse V1-V21+种子(可复用)。

第 3 墙诊断订正(实测推翻"Feign RPC"初判):boot 初卡 PermissionCommonApi 无 bean,初判为 yudao-cloud Feign 跨模块 RPC 未桥接——实测证伪。真因是 system/infra/ai-server 也是无 classifier 的 repackage fat jar,嵌入 muse-server fat jar 后类不可加载(同 member 墙)。配 classifier 后(commit eea10b7),PermissionApi extends PermissionCommonApi 的本地 PermissionApiImpl(@Primary @RestController)即注册,bean 问题消失——无需任何 Feign 桥接/stub。随后逐层实测拆除:① datasource 报 EOFException = 本机 SOCKS 代理坑,须给 java-DsocksProxyHost= -DsocksProxyPort=(清 JVM system property;env -u HTTP_PROXY 清不掉)→ PG 连通;② Flyway V1-V21 在 muse_slice_live 迁移成功;③ security/permission/tenant 装配通过;④ codegen 缺 muse.codegen.import-enable(--muse.codegen.import-enable=false 补)。

已拆 ③ facade 条件装配(Spring 条件注解可靠性):@Service/@Component 上的 @ConditionalOnBean/@ConditionalOnMissingBean 不可靠(Spring 官方仅保证 @Bean 方法上有效),在"从未装配过的单体"里扫描顺序致非确定性"无 bean"失败(首发 ContentFileFacade,随后 ContentAiSuggestionFacade 等切片关键 facade 接连暴露)。修法(commit d09d529,均真实失败关闭、非 permissive stub):①6 个全默认内容 facade(ContentFile/KnowledgeDraft/Meta/ParseJob/PlanningCandidate/StyleCheck)→ 改由 MonolithFacadeFallbackAutoConfiguration(@AutoConfiguration+@Bean @ConditionalOnMissingBean 返回接口默认实现,真 impl 仍优先);②真 impl facade(AiSuggestionMergeProjectionFacade 等 4 个 @Primary)去 @ConditionalOnBean,恒注册;③Unavailable 兜底(4 个 source-owner + ToolGrantApproval + MetaImpact,均扩 abstract 方法不能自动配)就地去 @ConditionalOnMissingBean

已拆 ④ 跨模块 bean 名冲突(@Resource 按名注入):@Resource 优先按字段名注入;member 的 AccountExportServiceImpl.exportTaskMapper 与 content 的 ExportTaskMapper bean 撞名 → 单体内注入到错类型(JDK 代理 type mismatch,boot 失败)。修:字段重命名 accountExportTaskMapper(commit d09d529,全仓扫描无其它撞名)。

第 3 墙诊断总订正:初判"cloud→monolith 需 Feign RPC 桥接"被完全证伪——真因全是 ①②③④ 这类装配/打包/命名问题。PermissionApiImpl(@Primary)IS-A PermissionCommonApi,类可加载后 bean 自然满足,无需任何 Feign 桥接/stub

起全栈配方(已实测打通,可直接复用):

# 1) 建库灌基座+种子(root):base-schema + base-seed → muse_slice_live;app Flyway 补 Muse V1-V21
# 2) 本机 redis:redis-server --port 6379 --requirepass museredis
# 3) infra.env:MUSE_POSTGRES_USERNAME=root + root 口令、MUSE_POSTGRES_DATABASE=muse_slice_live、MUSE_REDIS_HOST=127.0.0.1
# 4) 起(务必清代理 JVM SOCKS,否则破坏 PG;codegen.import-enable 必给;关 AI 调度避免联调噪声):
cd muse-cloud; set -a; . ~/.config/muse-repo/infra.env; set +a
env -u HTTP_PROXY -u HTTPS_PROXY -u http_proxy -u https_proxy \
  mise exec -- java -DsocksProxyHost= -DsocksProxyPort= -Dhttp.proxyHost= -Dhttps.proxyHost= \
  -Dmuse.ai.runtime.dispatcher.initial-delay-ms=86400000 -Dmuse.ai.runtime.dispatcher.fixed-delay-ms=86400000 \
  -jar muse-server/target/muse-server.jar --spring.profiles.active=local,infra \
  --spring.flyway.baseline-on-migrate=true --muse.codegen.import-enable=false &
# 注:muse-server.jar 须为 thin lib(已配 classifier=exec 于 member/system/infra/ai-server)+ muse-server 自身 repackage 产出可执行 jar。

活体证明命令(curl 真打 48080;务必 --noproxy '*' 绕本机 HTTP 代理):

# 健康:GET 列表(mock 鉴权 test1=userId1,租户 1)
curl -s --noproxy '*' -H "Authorization: Bearer test1" -H "tenant-id: 1" -H "X-API-Version: 1" \
  "http://localhost:48080/app-api/muse/works?pageNo=1&pageSize=5"        # → {"code":0,...}
# 采纳:AI候选→Canonical(种子 work=1/block=1/suggestion=1;commandId 用 uuidgen)
curl -s --noproxy '*' -X POST -H "Authorization: Bearer test1" -H "tenant-id: 1" -H "X-API-Version: 1" \
  -H "Content-Type: application/json" \
  -d '{"commandId":"<uuid>","suggestionId":1,"expectedBlockRevision":1,"decisionType":"accept","mergeMode":"accept_as_is","sourceSnapshot":{"sourceType":"ai_suggestion"},"auditReason":"smoke"}' \
  "http://localhost:48080/app-api/muse/works/1/blocks/1/suggestion-merges"  # → code:0,newRevision:2,sourceAttribution+lineage
# 实测(2026-06-14):reload GET .../blocks 块 content_text=AI候选正文、revision=2;陈旧 revision=99 → code 1041000002 乐观锁拒(反假绿验证)

启动配置要点(workflow 推导):mock 登录 token=test1;system_tenant(id=1) 必需(已在种子);MuseAiRuntimeJobDispatcher 每秒查 muse_ai_job 无开关→用超大 delay 熄火;app-api 租户来自 tenant-id 头(默认 1)。

七、本地验证 openapi-diff 破坏性变更门禁(2026-06-17 实证)

muse-cloud/.github/workflows/openapi-diff.ymldocker run tufin/oasdiff:v1.19.1 在 PR 上逐域比对 base/head 契约;本地无 Docker/GHA 无法整跑,但 oasdiff 是 Go 工具,可 go install 同款版本验证「检测逻辑 + 逐域循环聚合」。

# 一键复现(自举 oasdiff@v1.19.1 + 临时 venv 装 pyyaml,零全局污染;~首次 go install 稍慢)
bash muse-cloud/scripts/verify-openapi-diff.sh   # → 5 场景全绿即门禁逻辑正确
  • 坑1:CLI 须 subcommand 式 oasdiff breaking <base> <revision> --fail-on ERR(v1.7+ 由 flag 式 -base/-revision 改来);workflow 与脚本均用此式。
  • 坑2:版本须钉 tufin/oasdiff:v1.19.1(机械门禁不可用 :latest,否则上游 CLI/判定漂移会悄悄改门禁口径)。脚本 OASDIFF_VERSION 须与 workflow 标签一致。
  • 坑3:go install 自报 "version main" 属正常——release ldflags 仅官方构建注入,源码默认 version=main;二进制行为仍是所钉 tag 的。
  • 坑4:跨文件 $ref 靠目录结构:events 契约 $ref: '../openapi-base.yaml',故 base/head 必须各自保留 docs/api-contracts/ 完整目录(workflow 用 git worktree 检出整树、脚本用 cp -R 整目录),单独拷 openapi.yaml 会解析失败。
  • 实证(oasdiff v1.19.1,全部 7 域真实契约,5/5):无变更/新增可选参数/新增端点→放行(rc=0);参数变必填(request-parameter-became-required)/删端点(api-path-removed-without-deprecation)→拦截(rc=1)。
  • 残留(脚本覆盖不到):GHA 触发管道(pull_request paths 触发、base.sha worktree、docker 镜像拉取)须一次真实 PR(改 docs/api-contracts/**)首跑确认。未首跑前不得宣称"CI 已实拦破坏性变更",只能称"检测逻辑+循环逻辑已本地实证"。