129 lines
21 KiB
Markdown
129 lines
21 KiB
Markdown
# 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.java`、`P1rKnowledgeRuntimeEndToEndLiveAcceptanceIT.java`
|
||
- 输出脱敏(只打 endpoint/模型/id/状态/耗时/usage/key 长度 + sha256 前 12);RAGFlow smoke 会留存 `p1r-live-<时间戳>` dataset 不删。
|
||
- **runtime client live ≠ coverage completed**:仍需 Muse API 触发 + 落库 + 审计 + 失败路径证据(见 [golden-journey](../skills/golden-journey-vertical-slice.md))。
|
||
|
||
## 二、外部集成兼容坑(实证)
|
||
- **New-API 系统管理令牌 ≠ RAGFlow key**:用前者打 RAGFlow `/api/v1/datasets` 返回 `code=109 Authentication error`,必须用独立 `ragflow-*` key。
|
||
- **RAGFlow 建数据集**:`POST /api/v1/datasets` 带 `config:{}` 返回 `code=101 Extra inputs are not permitted`;**只发 `name`** 才 `code=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 | xxd` 回 `N` 即 PG 在说话(纯 nc 直连,不经代理)。
|
||
- **修复:给 forked 测试 JVM 加 `-DargLine='-DsocksProxyHost= -DsocksProxyPort='`**(清空 SOCKS,JVM 直连 tailnet);代码内可用 `new Socket(java.net.Proxy.NO_PROXY)`。
|
||
|
||
**跑单个 IT 的配方(已验证)**:
|
||
```bash
|
||
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`**:`P1rMarketFlywayMigrationIT` 会 `System.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**。
|
||
|
||
**起全栈配方(已实测打通,可直接复用)**:
|
||
```bash
|
||
# 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 代理)**:
|
||
```bash
|
||
# 健康: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 实证)
|
||
|
||
`.github/workflows/openapi-diff.yml` 用 `docker run tufin/oasdiff:v1.19.1` 在 PR 上逐域比对 base/head 契约;本地无 Docker/GHA 无法整跑,但 oasdiff 是 Go 工具,可 `go install` 同款版本验证「检测逻辑 + 逐域循环聚合」。2026-06-19 已从 `muse-cloud/.github/workflows/` 移到仓库根,否则自建 Gitea Actions 不会扫描触发。
|
||
|
||
```bash
|
||
# 一键复现(自举 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)。
|
||
- **残留(脚本覆盖不到)**:远端 Gitea Actions 触发管道(`pull_request` paths 触发、`base.sha` worktree、docker 镜像拉取)须一次真实 PR(改 `docs/api-contracts/**`)首跑确认。未首跑前不得宣称"CI 已实拦破坏性变更",只能称"检测逻辑+循环逻辑已本地实证、root workflow 已接线"。
|