31 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(1.0 历史知识运行时) | http://100.64.0.8(及 :9380) |
/api/v1/datasets、/api/v1/retrieval;健康检查 /v1/system/healthz;2.0 下线由用户手工执行,不阻塞 S1 |
| Dify 1.15.0(2.0 单人版目标外部服务) | http://100.64.0.8:18080 |
官方 compose 部署在 mini-infra:/home/qingse/dify-1.15.0/docker,compose project=muse-dify;API health 返回 version=1.15.0 |
| 开发 PG / Redis | 见下方凭据来源 | 远端 PG 15(用户确认可用,不强制 PG16);Redis 100.64.0.8:6379 |
Dify 状态(2026-07-07 S1):Dify 已作为 2.0 单人版目标外部服务完成基础设施部署与 live 契约验收,但生产运行时切换仍按 S2+ 后续步骤推进,不要把 S1 等同于 Muse runtime 已全面改走 Dify。当前 muse-dify 是一套独立基础设施栈,包含自己的 db/redis/weaviate/nginx/sandbox/plugin_daemon/worker/web 等容器;它不复用、不替换 Muse 既有 PostgreSQL/Redis。
凭据来源(明文不入库,只记位置):
- 外部验收:
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 连接 + 真实凭据)。 - Dify 控制台/模型/Datasets 凭据:
mini-infra:/home/qingse/.config/muse-repo/dify.env与本仓muse-cloud/scripts/dev/p1r-external-acceptance.env的MUSE_AI_DIFY_*、MUSE_KNOWLEDGE_DIFY_*块。仓内 env 按项目内网约定用于验收,日志与文档只输出长度/hash 前缀。 - ⚠️ 安全:上述令牌仅限当前内网验收上下文;本轮 agent 工具输出曾出现 New-API 与 Dify 明文 key,用户需手工轮换 New-API token 与 Dify app/dataset keys。
Live 验收 harness(opt-in,默认跳过,MUSE_P1R_EXTERNAL_ACCEPTANCE=true 才真跑):
muse-module-ai/.../application/muse/facade/P1rNewApiLiveAcceptanceIT.java(New-API)muse-module-ai/.../application/muse/P1rImportLlmNewApiLiveAcceptanceIT.java(New-API 完整导入 LLM 解析)muse-module-knowledge/.../application/muse/facade/P1rRagFlowLiveAcceptanceIT.java(RAGFlow)muse-module-ai/.../application/muse/facade/P1rDifyChatLiveAcceptanceIT.java(Dify chat app)muse-module-knowledge/.../application/muse/facade/P1rDifyDatasetsContractLiveIT.java(Dify Datasets create/upload/index/retrieve)- 另:
muse-server/.../framework/api/P1rAiRuntimeEndToEndLiveAcceptanceIT.java、P1rKnowledgeRuntimeEndToEndLiveAcceptanceIT.java - 输出脱敏(只打 endpoint/模型/id/状态/耗时/usage/key 长度 + sha256 前 12);RAGFlow smoke 会留存
p1r-live-<时间戳>dataset 不删,Dify Datasets smoke 会留存dify-contract-<时间戳>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。 - RAGFlow 无 copy/clone dataset API:复制一份 dataset 必须重走
createDataset+ 逐文档上传 +startParse重新索引,无法共享原 dataset 的索引(market KB 物化 D0-fork 据此设计,见market-install-downstream-materialization.md)。 - RAGFlow 检索 metadata 字段名:RAGFlow 官方契约是
metadata_condition。2026-06-28 已将 Muse HTTP 客户端从旧的metadata_filter修为metadata_condition,并用请求体单测防回退。即便字段已修,跨可见性安全隔离仍不得依赖运行时 metadata 过滤,market KB 继续使用公开副本物理隔离。 - GraphRAG attribution:默认 fail-closed(
ATTRIBUTION_NOT_CONFIGURED);真跑需MUSE_KNOWLEDGE_RAGFLOW_GRAPHRAG_ATTRIBUTION_READY=true+..._GRAPHRAG_DATASET_ID。 - New-API/RAGFlow Java 客户端代理污染:2026-06-28 盘点时,未清代理的 Java/Python 探针曾返回 502,但
curl --noproxy '*'与清代理 live IT 均证明 New-API/RAGFlow 可用。运行时代码已在 New-API 文本生成、导入解析、RAGFlow HTTP 客户端上显式使用 directProxySelector,避免 macOS/环境代理把 tailnet 内网请求转到本地代理导致假故障。 - Dify 官方 compose 钉版:S1 使用
langgenius/dify官方仓库 tag1.15.0的docker/目录,远端源码备份在/home/qingse/dify-1.15.0,compose project 固定为muse-dify。控制台初始化后/console/api/setup返回step=finished,API health 返回{"status":"ok","version":"1.15.0"}。 - Dify 存储类型坑:官方
.env默认STORAGE_TYPE=opendal+OPENDAL_FS_ROOT=storage在当前 compose 部署下会出现 API 上传成功、worker 索引时报File not found。已改为STORAGE_TYPE=local+STORAGE_LOCAL_PATH=storage并重启api/api_websocket/worker/worker_beat/nginx;回滚备份为mini-infra:/home/qingse/dify-1.15.0/docker/.env.s1-storage-before-local。 - Dify 模型 provider:通过 marketplace 插件
langgenius/openai_api_compatible/openai_api_compatible连接 New-API。MiniMax-M2.5作为 LLM,Qwen/Qwen3-Embedding-8B作为 text-embedding,Qwen/Qwen3-Reranker-8B作为 rerank;创建 high_quality dataset 前必须先配置 text-embedding,否则 dataset/create 会缺默认 embedding。 - Dify Datasets key 类型:工作区级 key 从
/console/api/datasets/api-keys创建,前缀dataset-,可调用/v1/datasets、/v1/datasets/{id}/document/create-by-file、/v1/datasets/{id}/retrieve;单 dataset key 从/console/api/datasets/{id}/api-keys创建,前缀通常为ds-,用于限定某个 dataset。S1 执行计划要求的是工作区级 Datasets API key。app key 前缀app-,只能打 app/workflow API,不能打 Datasets。 - Dify Datasets retrieve 契约:Dify 1.15 的
/v1/datasets/{id}/retrieve在retrieval_model中必填search_method与reranking_enable;只传top_k/score_threshold_enabled会 400。S1 live IT 固定search_method=semantic_search、reranking_enable=false。 - Dify 控制台 API 坑:
INIT_PASSWORD最大长度 30;控制台登录 password 字段实际传 base64(password),不是 RSA;控制台 API 写操作需 cookie +X-CSRF-Token,不要拿 app/dataset key 调控制台端点。
三、前端 / 构建坑速查
- 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 实证)
E0 分层入口(2026-06-27):默认 CI 只跑纯本地层,真实 PG / live 验收统一走脚本,避免把台账门禁、默认跳过或 mock 绿混成真验证。判据见 docs/mvp/1.0.0-真验证清单.md。
# 纯本地门禁:coverage/RealApiGate/ArchUnit/契约结构,不连 DB/外部服务
bash muse-cloud/scripts/run-p1r-verification.sh local
# 真实 PG 层:非 live P1r*IT,会 flyway.clean() 指定 _test 库
set -a && . "$HOME/.config/muse-repo/infra.env" && set +a
P1R_FLYWAY_URL='jdbc:postgresql://100.64.0.8:5433/<专属_test库>' \
P1R_FLYWAY_USER=root \
bash muse-cloud/scripts/run-p1r-verification.sh real-pg
# 外部 live 层:New-API/RAGFlow + runtime live,必须显式 opt-in
set -a
. "$HOME/.config/muse-repo/infra.env"
. "muse-cloud/scripts/dev/p1r-external-acceptance.env"
set +a
export MUSE_P1R_EXTERNAL_ACCEPTANCE=true
P1R_FLYWAY_URL='jdbc:postgresql://100.64.0.8:5433/<专属_test库>' \
P1R_FLYWAY_USER=root \
bash muse-cloud/scripts/run-p1r-verification.sh external-live
脚本硬闸:真实 database name 必须以 _test 结尾;目标 _test 库必须经人类 DDL 闸门预建,脚本只做存在性预检、不自动 CREATE DATABASE;JDBC URL 不得携带 password/token/secret/api-key query;密码只走 P1R_FLYWAY_PASSWORD 或 MUSE_POSTGRES_PASSWORD;输出只打印脱敏 URL。
结论先行:P1 后端 P1r*IT(completed-approval / Flyway 迁移 / events-publish outbox / live-acceptance)可在共享远端 PG 上真跑验证。2026-06-27 RC fresh real-PG 层:bash muse-cloud/scripts/run-p1r-verification.sh real-pg 真连 _test 库、显式排除 live 外部链路,194 用例 0F/0E/0S,BUILD SUCCESS;缺 URL、非 _test、URL 携带敏感 query、缺目标库、live 未 opt-in 均会在 Maven 前失败。2026-06-27 RC fresh external-live 层:bash muse-cloud/scripts/run-p1r-verification.sh external-live server live 8/0F/0E/0S, module live 4/0F/0E/1S,BUILD SUCCESS;New-API completion、AI runtime usage/quota/attribution、RAGFlow health/upload/parse/chunks/retrieval、knowledge runtime、market KB fork 物化与 after-commit 均真打通过,唯一 skip=GraphRAG attribution 未配置,不得计为 passed。本轮修 2 个测试上下文落后产品演进的预存红:① P1rContentPlanningCompletedApprovalIT 的 TARGET_VERSION=21 停在 V21,但 savePlanningItem→captureFieldSnapshot 已依赖 V25 新增的 muse_content_planning_field_snapshot 表(commit 94a2379)→ 写入 relation does not exist 500;修=推进 TARGET_VERSION 21→31 + migrationsExecuted 断言 21→31(与 P1rContentCoreCompletedApprovalIT V30、P1rAiRuntimeEndToEndLiveAcceptanceIT V31 同向,核心断言不放宽)。② live-acceptance HTTP 代理坑(见下条)。经验:*CompletedApprovalIT 各自硬编 TARGET_VERSION,只迁到其所测路径需要的版本(V14~V31 混杂);产品给某 service 新增依赖表后,对应 IT 的 target 须跟进,否则该 IT(且仅该 IT)红——其他停在低版本的 IT 不受影响是因其路径不碰新表。此前 P1rKnowledgeFlywayMigrationIT 硬编码版本断言随 schema 增长失效(V14→V21→V23 复发两次)已修为动态:读本次 Flyway MigrateResult.targetSchemaVersion/migrationsExecuted 自适应(commit 4d46d7a)。
**▲ stale jar / argLine 假红(2026-06-27 RC 实证):**手工绕过 run-p1r-verification.sh 直跑 muse-server IT 时,不要只在 Maven 顶层传 -Dp1r.flyway.url 或代理清理参数。surefire forked JVM 可能拿不到这些系统属性,导致 JDBC 仍走旧代理或缺 flyway 参数;应把 p1r.flyway.url/user/locations 和代理清理一起放入 -DargLine。另外,改过任一上游 module 后跑 muse-server IT 必须加 -am 或先 mvn install,否则 server 会链接本地仓库旧 jar,表现为“代码已改但 IT 仍跑旧行为”的 stale-jar 假红。本轮 P1rMarketGovernanceWriteCompletedApprovalIT 就因未 -am 先复现旧 recall 断言,加 -am 后 targeted 6/0、全量 real-PG 194/0。
▲ 最大坑(排查极久):本机 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)。
▲ 第二代理坑(2026-06-26 实证,live-acceptance IT 专属):上一条只清 socksProxyHost,但 macOS 系统网络代理 127.0.0.1:7897 还会被 JVM 自动注入 http.proxyHost/https.proxyHost(-XshowSettings:properties -version 可见,即便 shell 已 unset HTTP_PROXY 且 mise exec 环境无代理)。 java.net.http.HttpClient(RagFlow/NewApi runtime client 用它,HttpClient.newBuilder()...build() 无显式 .proxy(...))默认走 ProxySelector.getDefault() → 经死代理 7897 → RagFlow health 报 RAGFLOW_UNAVAILABLE、New-API completions 报 provider_5xx / 502(retryable=true)。关键诊断:http.nonProxyHosts 不含 tailnet 100.64.0.8,故这些请求不被 bypass;curl --noproxy '*' 直打同地址 health/completions 全 200(证明外部服务健康、是 JVM 客户端走代理),极易误判成"外部环境波动/限流"。纯 PG IT 不中此坑(PG 走原始 socket 受 socksProxyHost 管,已被上一条清掉;不碰 HTTP 客户端)。
- 修复:live-acceptance 的 argLine 须同时清 HTTP 代理:
-DargLine='-DsocksProxyHost= -DsocksProxyPort= -Dhttp.proxyHost= -Dhttp.proxyPort= -Dhttps.proxyHost= -Dhttps.proxyPort= -Djava.net.useSystemProxies=false'。本轮加此参后 4 个 live-acceptance(NewApi 2/2、RagFlow 2/2[1 GraphRAG skip]、AiRuntime 3/3、KnowledgeRuntime 3/3)全绿。 - 判定外部服务真可达(绕本机代理):
curl -s --noproxy '*' http://100.64.0.8/v1/system/healthz(RAGFlow 回{"status":"ok",...})、curl -s --noproxy '*' -X POST http://100.64.0.8:3000/v1/chat/completions -H "Authorization: Bearer <token>" -d '{"model":"MiniMax-M2.5","messages":[{"role":"user","content":"ping"}],"max_tokens":5}'(回 200 + completion)。两者 200 而 live IT 报 unavailable/5xx → 必是 JVM 代理坑,非外部波动。
▲ New-API 管理口 live 验收补充(2026-06-27 实证,E5 member): New-API 管理 API 与 OpenAI 兼容 completion 不是同一类调用,Account 侧 RealNewApiAccountFacade 会调用 /api/user/search、/api/user/、/api/user/{id} 创建/刷新用户与配额。管理口鉴权从 NEW_API_SYSTEM_MANAGEMENT_TOKEN 读取,New-Api-User 必须是数值管理员用户 id,两者都只走 env,不得写入仓库/日志。排查时发现即便清了 shell 代理,Java/Python 仍可能读取 macOS 系统代理;curl --noproxy '*' 直连成功但 Java HttpClient 失败时,优先检查是否显式设置 Proxy.NO_PROXY/ProxySelector。RealNewApiAccountFacadeLiveAcceptanceIT 必须显式 MUSE_ACCOUNT_NEW_API_LIVE_ACCEPTANCE=true 才计为 live passed;默认 skip 是诚实跳过,不能当绿证。
跑单个 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 实证)
.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 不会扫描触发。
# 一键复现(自举 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_requestpaths 触发、base.shaworktree、docker 镜像拉取)须一次真实 PR(改docs/api-contracts/**)首跑确认。未首跑前不得宣称"CI 已实拦破坏性变更",只能称"检测逻辑+循环逻辑已本地实证、root workflow 已接线"。