feat(deploy): S9 单人版 compose 从零部署+黄金旅程活体验收(1a 闭合)

- docker-compose.solo.yml:muse-server 单体+PG+Redis 一体化编排,固化 sql/muse Flyway 迁移只读挂载(mini-infra 从零真验暴露漏挂致 muse 表全缺、AI worker 每秒报 relation does not exist)
- 2.0.0-单人版部署手册.md:双栈拓扑/凭据清单/从零步骤/回滚恢复多用户/Dify 升级约定
- 进度总账:§9 判据 0–7 全留证(黄金旅程 workId98 七步 PG 核验+compose 从零 36 迁移 121 表)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
lili 2026-07-08 10:19:47 -07:00
parent 7a6e46b2fe
commit 1c2d51bfd4
3 changed files with 159 additions and 1 deletions

View File

@ -0,0 +1,50 @@
# Muse 2.0.0 单人版部署手册
> 版本 v0.1 · 2026-07-08 · 读者:部署与运维者 · 边界:仅覆盖单人版(1a)从零部署与回滚;设计意图见 [review v0.2](../agent-specs/2026-07-07-2.0.0单人版改造-review.md),任务拆解见 [execution](../agent-specs/2026-07-07-2.0.0单人版改造-execution.md)。
## 一、拓扑:双栈独立
单人版是两套 compose 各自独立、通过网络互访,不合并成一个编排:
- **本栈**(`muse-cloud/docker-compose.solo.yml`):muse-server 单体 + PostgreSQL + Redis。三者同网络,muse-server 经服务名 `postgres`/`redis` 直连;仅 muse-server 的 48080 对外暴露。
- **Dify 栈**:沿用 Dify 官方 compose 独立部署(见 Dify 官方文档),提供写作/解析 chat app 与知识 dataset 检索。本栈通过 env 指向 Dify 的 base-url 与两类 key,不反向依赖。
这样切分的理由是:Dify 有自己的版本节奏与官方编排,合并会把它的升级风险耦合进 muse 主栈;独立后各自可单独重启、单独升级。
## 二、凭据清单
部署前准备下列值,写入 `muse-cloud/.env.solo`(从 `scripts/dev/solo-compose.env.example` 复制骨架;该样例只带开关默认值、不带真实凭据):
| 变量 | 含义 | 来源 |
|------|------|------|
| `MUSE_POSTGRES_PASSWORD` | 本栈 PG 密码 | 部署者自定,生产必须显式设置 |
| `MUSE_REDIS_PASSWORD` | 本栈 Redis 密码 | 部署者自定,生产必须显式设置 |
| `MUSE_AI_DIFY_BASE_URL` | Dify chat app 入口(`.../v1`) | Dify 部署地址 |
| `MUSE_AI_DIFY_WRITING_APP_ID` / `_API_KEY` | 写作 chat app 与其 Service key | Dify 控制台该 app |
| `MUSE_AI_DIFY_PARSER_APP_ID` / `_API_KEY` | 全书解析 chat app 与其 Service key | Dify 控制台该 app |
| `MUSE_KNOWLEDGE_DIFY_BASE_URL` | Dify 知识 dataset 入口(`.../v1`) | Dify 部署地址 |
| `MUSE_KNOWLEDGE_DIFY_DATASET_API_KEY` | Dify **dataset** key(与 app key 不可混用) | Dify 控制台知识库 API |
`credentialRef`(如 `dify-writing-s1`/`dify-parser-s1`)只是引用名、非密钥,已在 `solo-compose.env.example` 固定,须与注入的 Dify 凭据 ref 一致。app key 与 dataset key 是两套体系,混用会 401。
## 三、从零部署步骤
1. **出可运行 jar**(Dockerfile 只 COPY jar、不在容器内编译):
`mvn -pl muse-server -am -Dmaven.test.skip=true clean package`
2. **备凭据**:`cp scripts/dev/solo-compose.env.example .env.solo`,按第二节填全。
3. **起栈**:`docker compose -f docker-compose.solo.yml --env-file .env.solo up -d --build`
4. **库 provision(自动)**:PostgreSQL 空卷首次初始化时,`docker-entrypoint-initdb.d` 自动执行 `sql/dev/yudao-base-*-postgres.sql` 建 yudao 基座;muse-server 启动时 Flyway 跑 `sql/muse/` 里 36 个脚本(V1..V37)建 muse 业务表。**关键**:`sql/muse/` 经 compose 只读挂载到容器 `/muse-server/sql/muse`(`flyway.locations``filesystem:sql/muse`),脚本未打进 jar——**部署目录必须含 `sql/muse/`,漏挂则 Flyway "No migrations found"、muse 表全缺、AI worker 每秒报 `relation does not exist`**(mini-infra 从零真验暴露并已在 compose 固化挂载)。二者顺序由 depends_on healthcheck 保证。
5. **菜单收口**:provision 后执行 `sql/solo/2026-07-08-disable-solo-governance-menus-postgres.sql`(隐藏市场治理/多用户相关菜单;幂等,回滚脚本见同目录 `restore-*`)。
6. **验活**:`curl --noproxy '*' http://<host>:48080/app-api/muse/...`(app-api 固定带 `tenant-id: 1`)。
无 Nacos 环境已在 compose 内以 `SPRING_CLOUD_NACOS_DISCOVERY_ENABLED=false` 关闭服务发现,启动日志不应有注册中心持续重连噪声。
## 四、回滚与恢复多用户
部署层回滚是独立的:`docker compose -f docker-compose.solo.yml down`(加 `-v` 清库卷则回到全新)。代码层不受影响。
若日后要恢复多用户形态(市场/成员),不是「取消一行 pom 注释」,按 [review v0.2 §10](../agent-specs/2026-07-07-2.0.0单人版改造-review.md) 清单执行:muse-server pom 取消 `market-server` 注释 → 激活 `market-assembled` maven profile(回编译被排除的市场测试树、修排除期漂移) → 覆盖门 market 域退出 dormant → 前端 revert 裁剪 → 部署库执行 `restore-solo-governance-menus` 恢复菜单 → 成员子域打开对应 `enabled` 开关。RAGFlow 运行时已在 S6d 删除(git 可回),恢复需按新集成重走契约与验收,不承诺兼容。
## 五、Dify 版本升级约定
Dify 是单人版唯一新增外部服务,且承载生成与知识两条主链。**升级 Dify 版本前必须先在隔离环境跑 live 验收**:`P1rDifyChatLiveAcceptanceIT` + `P1rDifyDatasetsContractLiveIT`(`MUSE_P1R_EXTERNAL_ACCEPTANCE=true`)+ 本手册第三节的黄金旅程 smoke,全绿后再升生产。Dify 的 console CSRF、per-tenant RSA 凭据加密、chat/dataset 两套 key 语义在小版本间都可能变,不跑验收直接升会静默打断生成或检索。

View File

@ -58,9 +58,19 @@
- **S6a 完成**commit 2ac42a06知识端口 RagFlow→Knowledge 重命名 + 裁 7 无用操作285 单测+双 profile 编译绿)。此前 worktree 隔离基线陈旧 320 commit 作废、主树重做(教训入个人记忆 [[worktree-isolation-stale-base]])。
- **S6bd 代码完成API 级验证2026-07-08**`DifyKnowledgeRuntimeClient` 5 操作 adaptercommit `09ff354e`)、检索改多 dataset 逐库扇出+按 score 合并 topK`a8f5e912`,适配 Dify `retrieve` 单库端点、响应 `records[].segment.content/score`)、摄入轮询键 documentId→batch`13bd910c`)均已提交并 API 级验证Muse→Dify 检索 wiring 已活体真跑(真生成的 `contextAssembly` 确实走了 Dify 检索)。**未闭合 = live grounding**:需要某作品 KB 挂上 Dify dataset + 已索引内容 + 授权,现有测试 KB 是 RAGFlow 取向、`chunks=0` 检不出东西,故 `P1rDifyKnowledgeRuntimeEndToEndLiveAcceptanceIT` 与检索质量 smoke 归 S9 fixture删 RAGFlow 实现/装配/live IT 收在 **S6d**,须待 S6 live grounding 证实后再动。
- **S8 代码完成**(见上「导入解析切 Dify」条按 provider 选 parser + `DifyMuseAiImportLlmParser` 已落、New-API 保留可回退live 全书解析(走对象存储上传流才触发的 LLM 路径)归 S9。
- **S9 待做**S6/S8 代码已就位,差 Dify grounding 与上传 live fixturedocker-compose.solo.yml + 无 Nacos 干净启动 + 全程真后端真库真 Dify 黄金旅程 + review v0.2 §9 七条判据留证。
- **S9 完成**2026-07-08详见下方「S9 部署收口与黄金旅程活体验收」段docker-compose.solo.yml 从零部署 + 无 Nacos 干净启动 + 真后端真库真 Dify 黄金旅程 + review v0.2 §9 判据 07 全留证;至此 1a 单人版交付闭合
- **主会话欠的 live 冒烟**M1New-API 真生成经护栏+chunk 携全文+决定后 purge+重连回取整链,避免造新 P1r 克隆、走现有 harness仍欠**M3Dify 生成 happy-path已于本轮打通**见上「M3 达成」条)。
**2.0.0 单人版改造 S9 部署收口与黄金旅程活体验收2026-07-08里程碑 M5 / 1a 单人版交付闭合)**:单人版从零部署与全程真后端真库真 Dify 黄金旅程双双活体证成review v0.2 §9 判据 07 全部留证。
判据6 是本步核心两支柱各自独立核验。功能流localhost:48080 活体后端(载 S6d/S8 代码)新建 workId=98七步端到端闭合、逐步 PG 直查——建作品work98 draft→写正文block60 revision1、唯一标记→AI 候选suggestion109 经 Dify 写作 app 出 80 字全文、accepted→采纳block60 revision 1→2、content 即采纳文→上传索引kb57 挂 Dify dataset adcd1b05、文档 indexing completed、1 个 chunk 含唯一 KBFACT→检索命中binding127 active、chunkCount=1、similarity 0.673→导出下载export12 completed、294 字节含采纳正文。从零部署mini-infra 上 docker compose 空卷起全栈Flyway 从零 applied 36 migrations 到 v37、建 121 张 muse 表、Started 27.1s、无 Nacos 噪声、冒烟 GET /agents 返 HTTP 200 且容器→Dify 通路可用(写作 app key 有效返参数 JSON。此步暴露并修复一处真实部署缺陷原 compose 只把 sql/dev 基座挂给 PG initdb、漏把 sql/muse 的 36 个 Flyway 脚本挂给 muse-server脚本未打进 jar、flyway.locations 用 filesystem:sql/muse漏挂则 muse 表全缺、AI worker 每秒报 relation does not exist——已在 docker-compose.solo.yml 固化只读挂载并写入部署手册。
其余判据判据0 基线(隔离线 S0 本地门绿判据1 本地门禁独立重跑 BUILD SUCCESS 65/0F/0EContractFirstGateTest V35 跳号白名单 + P1rApiCoverageReportTest 9/0 台账去 RAGFlow 引用 + SoloExternalAiProviderGate 3/0判据2 real-PG 层 P1rContentImportWizardCompletedApprovalIT 真 _test 库 2/0F/0E1 外部验收 live 跳过、Flyway 从零 applied 36 到 v37判据3 Dify liveS1 Datasets 契约 IT + S6 知识 grounding live + M3 生成 live + S8 解析器 code/smoke + X-API-Version判据4 fail-closed 反向SoloExternalAiProviderGate 无厂商 provider Bean + ai 授权未配 no-op判据5 前端studio tsc/vitest/build + MSW-off 创作主线 e2e 33/31 隔离S5 已验收判据7 隔离market 物理 404、多用户 worker isEnabled=false 空转、publish 仅产 Knowledge 快照、installed KB 仅 publisher=user
收口用整分支视角复跑门禁,暴露并修掉两处此前 per-task 视角漏掉的回归其一S6d 删 RAGFlow 运行时后覆盖台账p1r-api-coverage.json与生成器仍引用已删的 `HttpRagFlowKnowledgeRuntimeClient` / `P1rKnowledgeRuntimeEndToEndLiveAcceptanceIT``P1rApiCoverageReportTest` 红——外科手术式把知识域 serviceFiles/testFiles 1:1 换成 Dify 继任,保住 32 个 market dormant 人工口径不被重生成摧毁其二S8 给 `MuseAiImportParseService``@Resource MuseAiProperties`muse-server 跨模块 import 向导 IT 的极简上下文缺该 bean、ApplicationContext 加载失败——S8 当轮只跑了 ai 与 content 模块 IT、这个 muse-server IT 只编译修复未真跑,补 `MuseAiProperties` @Bean 后 2/0 绿。两处都印证"per-task 评审抓不到跨路径复现、收口须整分支再过一遍"。
诚实备注:黄金旅程 step3 的 AI 运行时授权经 `muse_tool_grant` 投影种入,当前 P1R-4 切片不接 Security 主流程、无用户端点创建 AI 授权(与 work1 grant id=1 同既有架构状态,非 Dify 迁移引入),故"全新作品跑 AI"需一步库内授权供给compose 冒烟未跑端到端真生成solo 空库无前置数据),降级以只读 200 + 到 Dify 通路证明V35 系并行线版本预留跳号V36/V37 已应用 live 库不可回填)已在 ContractFirstGateTest 与 contract-first.md 登记;覆盖台账 JSON 是生成器产物 + 手工 dormant 加工的混合体、已漂移,长期收敛(让生成器支持 dormant 或明确 JSON 为 SoT宜单列项本轮未触碰。
**E1 content 创作闭环切片(2026-06-27)**:已补 AI suggestion 采纳归档、前端“改后合并”入口、IndexedDB 草稿键对账、旧知识草稿失效、工作台知识/导入/导出/记录入口、Block 版本历史最小 API/UI。Content merge 写 Canonical 与来源归因后,必须由 AI owner 写 `accepted` 状态、accepted decision archive、AI command、business auditAI owner 不可用时整笔 merge 回滚,避免 Canonical 已写但候选仍 pending。Content 正文变更后通过 Knowledge owner API 将关联 pending draft 标为 `conflicted/needs_recheck` 并写 Knowledge draft decision archive通知失败不回滚 Canonical 主写Knowledge confirm 端仍按来源状态 fail-closed。Studio `CandidatePanel` 支持编辑最终正文并按 `accept_as_is`/`modify_then_merge` 提交IndexedDB 草稿键改为 `workId+blockId+revision` 防跨作品/版本污染。`saveBlock`/`mergeBlockSuggestion` 现在写 `muse_content_block_revision_snapshot`工作台“历史”Tab 只读展示 Canonical revision 快照;导入可创建真实任务,导出支持范围/格式选择、任务查询与下载凭证消费,知识/记录入口跳转对应工作台。fresh 证据:后端局部单测 `ContentSourceServiceTest` 25/0F/0E、`ContentAppServiceTest` 19/0F/0E、`MuseKnowledgeDraftInvalidationServiceTest` 3/0F/0E、`AiSuggestionMergeProjectionFacadeTest` 7/0F/0E契约/覆盖门 `ContractFirstGateTest` 4/0F/0E + `P1rApiCoverageReportTest` 8/0F/0Ereal-PG `P1rContentCoreCompletedApprovalIT` 13/0F/0E/0S + `P1rContentMergeSuggestionIT` 5/0F/0E/0S + `P1rContentMergeGeneratedSuggestionIT` 1/0F/0E/0Sstudio `tsc -b --force` 通过、lint 0 errors、Vitest 12/12追加 `AIPanel.contract.test.tsx` + `sse.test.ts` 22/0F/0E导出 UI 追加 `useWorks.test.tsx`+`ExportWorkModal.test.tsx` 11/0F/0E、目标 ESLint 0 errors。共享 PG 写入闸门批准后已补跑 MSW-off Playwright 真后端 `accept-suggestion.spec.ts` 2/0F/0E:正路真 New-API 生成 suggestionId=79、authz `rpe-local-*`、Block revision 160→161、AI owner accepted decision archive 非空;负路 stale revision 业务冲突。边界:`muse-studio/src/types/content.ts` 生成类型因 openapi-typescript 版本漂移未同步hook 内暂维护 `BlockRevision` 最小类型;完整导入向导已在 RC 后补,见下方记录;真后端导出下载 e2e 已在 2026-06-28 补跑通过FileApi 异常仍按后端合同 fail-closed。
**E2 ai 智能体生命周期与候选处置闭环(2026-06-27)**:已补用户自建 Agent update/archive、初始版本自动创建、版本列表/激活/归档非当前版本、作品槽位 unbind、Studio reject 调后端 AI owner 决策归档。Agent update 会创建下一 active version 并更新 `current_version_id`archive Agent 同步归档仍 active 的槽位绑定slot unbind 将绑定行 revision+1 且 `status=archived`运行时回到默认能力WorkspacePage “放弃修改”不再只清本地候选,而是真打 `POST /suggestions/{id}/reject``rejected` 与 decision archive。启动修红:最新单体启动时暴露 `muse.codegen.importEnable` 缺省,已在 `muse-server/src/main/resources/application.yaml``import-enable:false`,随后 48080 成功启动并 `GET /app-api/muse/agents` smoke 返回 `code=0`。fresh 证据:后端 AI targeted `MuseAgentServiceTest`+`MuseAgentSlotServiceTest`+Controller annotation 63/0F/0E契约门 `ContractFirstGateTest` 4/0F/0Estudio `tsc -b --force` 通过、目标 ESLint 0 errors、Vitest 29/0F/0EMSW-off Playwright 真后端 `agent-create.spec.ts`+`agent-slot-bind.spec.ts`+`accept-suggestion.spec.ts` 5/0F/0E:采纳真生成 suggestionId=80、Block revision 161→162、accepted decision archive 非空;拒绝真生成 suggestionId=81、`status=rejected`、decision archive 非空Agent 生命周期 DB 核验 v1/v2 current 切换与 v2 archivedSlot unbind DB 核验 revision 2→3、status archived、响应 `sourceStatus=unbound`。边界:本轮未跑 studio 全量 e2e。

View File

@ -0,0 +1,98 @@
# Muse 2.0.0 单人版一体化编排(S9):muse-server 单体 + PostgreSQL + Redis。
#
# 边界:Dify 沿用其官方 compose 独立部署,本栈通过 env 指向 Dify(base-url 与两类 key:
# 写作/解析 app key 走 muse.ai.dify、知识 dataset key 走 muse.knowledge.dify)。
# 租户机制保持开启,单人部署所有请求固定 tenant-id=1;多用户 worker 全关(见 env_file)。
#
# 用法:
# 1. mvn -pl muse-server -am -Dmaven.test.skip=true clean package # 先出可运行 jar(Dockerfile 只 COPY jar)
# 2. cp scripts/dev/solo-compose.env.example .env.solo # 填 Dify 真实凭据与 PG/Redis 密码
# 3. docker compose -f docker-compose.solo.yml --env-file .env.solo up -d --build
# 首次 up 时 PostgreSQL 空卷会执行 yudao 基座 SQL(initdb),muse-server 启动再跑 Flyway 建 muse 表;
# 菜单数据见部署手册的 system_menu solo SQL(provision 后单独执行)。
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: ${MUSE_POSTGRES_USERNAME:-muse}
POSTGRES_PASSWORD: ${MUSE_POSTGRES_PASSWORD:?solo 部署必须显式设置 PG 密码}
POSTGRES_DB: ${MUSE_POSTGRES_DATABASE:-muse}
volumes:
- solo-pgdata:/var/lib/postgresql/data
# 基座 schema+seed 仅在空卷首次初始化时执行(docker-entrypoint-initdb.d 约定);muse 表由 muse-server 的 Flyway 建。
- ./sql/dev/yudao-base-schema-postgres.sql:/docker-entrypoint-initdb.d/01-yudao-base-schema.sql:ro
- ./sql/dev/yudao-base-seed-postgres.sql:/docker-entrypoint-initdb.d/02-yudao-base-seed.sql:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${MUSE_POSTGRES_USERNAME:-muse} -d ${MUSE_POSTGRES_DATABASE:-muse}"]
interval: 5s
timeout: 5s
retries: 10
restart: unless-stopped
redis:
image: redis:8-alpine
# 生产稳定:solo 部署 Redis 必须设密码 + 开 AOF 持久化(草稿安全网/幂等键不可随重启丢失)。
command: ["redis-server", "--requirepass", "${MUSE_REDIS_PASSWORD:?solo 部署必须显式设置 Redis 密码}", "--appendonly", "yes"]
volumes:
- solo-redisdata:/data
healthcheck:
test: ["CMD-SHELL", "redis-cli -a \"$$MUSE_REDIS_PASSWORD\" ping | grep -q PONG"]
interval: 5s
timeout: 5s
retries: 10
environment:
MUSE_REDIS_PASSWORD: ${MUSE_REDIS_PASSWORD}
restart: unless-stopped
muse-server:
build:
context: ./muse-server
dockerfile: Dockerfile
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
# 单人版默认开关(tenant on / 多用户 worker 全关 / New-API off / Dify on / 超时对齐)。
env_file:
- scripts/dev/solo-compose.env.example
environment:
# --- 数据库/Redis 指向本栈服务(覆盖 infra profile 默认的远端 host)---
MUSE_POSTGRES_HOST: postgres
MUSE_POSTGRES_PORT: "5432"
MUSE_POSTGRES_DATABASE: ${MUSE_POSTGRES_DATABASE:-muse}
MUSE_POSTGRES_USERNAME: ${MUSE_POSTGRES_USERNAME:-muse}
MUSE_POSTGRES_PASSWORD: ${MUSE_POSTGRES_PASSWORD}
MUSE_REDIS_HOST: redis
MUSE_REDIS_PORT: "6379"
MUSE_REDIS_PASSWORD: ${MUSE_REDIS_PASSWORD}
# --- 知识运行时 = Dify(S6d 后 RAGFlow 运行时已删,provider 未设也缺省 Dify)---
MUSE_KNOWLEDGE_RUNTIME_PROVIDER: dify
# --- Dify 凭据(从 --env-file .env.solo 注入,勿在此硬编明文)---
MUSE_AI_DIFY_BASE_URL: ${MUSE_AI_DIFY_BASE_URL}
MUSE_AI_DIFY_WRITING_APP_ID: ${MUSE_AI_DIFY_WRITING_APP_ID}
MUSE_AI_DIFY_WRITING_API_KEY: ${MUSE_AI_DIFY_WRITING_API_KEY}
MUSE_AI_DIFY_PARSER_APP_ID: ${MUSE_AI_DIFY_PARSER_APP_ID}
MUSE_AI_DIFY_PARSER_API_KEY: ${MUSE_AI_DIFY_PARSER_API_KEY}
MUSE_KNOWLEDGE_DIFY_BASE_URL: ${MUSE_KNOWLEDGE_DIFY_BASE_URL}
MUSE_KNOWLEDGE_DIFY_DATASET_API_KEY: ${MUSE_KNOWLEDGE_DIFY_DATASET_API_KEY}
# --- 无 Nacos 干净启动(无注册中心环境,关服务发现避免持续重连噪声)---
SPRING_CLOUD_NACOS_DISCOVERY_ENABLED: "false"
# --- JVM 与代理(内网直连 Dify/PG/Redis 禁用系统代理,避免 fake-ip 劫持)---
JAVA_OPTS: "-Xms512m -Xmx1024m -Djava.security.egd=file:/dev/./urandom -Djava.net.useSystemProxies=false -Dhttp.proxyHost= -Dhttps.proxyHost="
ARGS: "--spring.profiles.active=local,infra"
volumes:
# muse 业务表的 36 个 Flyway 迁移脚本在 sql/muse/,application.yaml 的
# flyway.locations 含 filesystem:sql/muse(相对容器 WORKDIR /muse-server);
# 脚本未打进 jar(classpath:db/migration/muse 为空),必须在此挂载,否则 Flyway
# "No migrations found" → muse 表全缺 → AI worker 每秒报 relation does not exist。
# 经 mini-infra 从零起全栈真验:挂载后 applied 36 migrations、now at v37、121 表。
- ./sql/muse:/muse-server/sql/muse:ro
ports:
- "48080:48080"
restart: unless-stopped
volumes:
solo-pgdata:
solo-redisdata: