本会话验证 .application 边界门时实证:仅改 ArchUnit 谓词逻辑后重跑,.class 时间戳更新但方法体未真正重编译→反向"应红"测试假绿(跑的旧字节码)。 沉淀可靠复跑配方(rm maven-status / -Dmaven.compiler.useIncrementalCompilation=false) 入 knowledge §三,供后续门禁自检避免"反假绿本身假绿"。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
21 KiB
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)。
二、外部集成兼容坑(实证)
- 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 的配方(已验证):
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 里真能用」):
- 远端 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 证明。 - studio AI 流 parser 漂移(已修复 2026-06-14):
connectAIStream(src/lib/sse.ts)原按 JSONdata.type单行分发,真后端(MuseAiTaskStreamServiceImpl用 SpringSseEmitter.name(event).data(json))把事件名放 SSEevent:行(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 校验失败,勿用)。 - 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 类硬阻塞实测复盘(可复用):
已拆(可复用):
- build:member-server repackage 无 classifier → fat jar → 下游 market 编译期
package does not exist,致mvn package全项目破损(仅test阶段不触发,故 CI test 假绿)。修:member-server repackage 配<classifier>exec</classifier>(commit1f89007)。 - 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.yml 用 docker 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_requestpaths 触发、base.shaworktree、docker 镜像拉取)须一次真实 PR(改docs/api-contracts/**)首跑确认。未首跑前不得宣称"CI 已实拦破坏性变更",只能称"检测逻辑+循环逻辑已本地实证"。