diff --git a/.agents/knowledge/external-deps-and-gotchas.md b/.agents/knowledge/external-deps-and-gotchas.md index 8cfd8a9e..7e1da045 100644 --- a/.agents/knowledge/external-deps-and-gotchas.md +++ b/.agents/knowledge/external-deps-and-gotchas.md @@ -40,6 +40,7 @@ - **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 Dataset 拓扑口径(2026-07-08)**:工作区级 Datasets API key 是管理钥匙,不是生产单 dataset 设计。生产至少分全局公共 dataset 与作品独立 dataset,Muse 本地保存 datasetId 映射,检索时按授权选择公共 + 当前作品 dataset 循环检索合并;禁止把公共和作品私有资料混在一个 dataset 后仅靠 metadata 当安全边界。S1 的 `dify-contract-*` dataset 只留作 contract evidence。 - **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 调控制台端点。 diff --git a/docs/agent-specs/2026-07-07-2.0.0单人版改造-execution.md b/docs/agent-specs/2026-07-07-2.0.0单人版改造-execution.md index 1a5c0910..0eca062a 100644 --- a/docs/agent-specs/2026-07-07-2.0.0单人版改造-execution.md +++ b/docs/agent-specs/2026-07-07-2.0.0单人版改造-execution.md @@ -68,7 +68,7 @@ flowchart LR 1. 按 Dify 官方 docker-compose 在内网宿主部署,**钉死版本号**(镜像 tag 记入凭据文档与 compose;禁 latest)。 2. Dify 控制台初始化:创建工作区;建两个 app——「muse-写作透传」chat app(prompt 极简透传、创作温度、绑定模型 provider,上游可配自有 New-API 网关)、「muse-全书解析」app/workflow(低温、要求严格 JSON 输出);生成 app 级 API key ×2 与工作区级**知识库 API key**(两类 key 不同,不可混用)。 3. 凭据落位:`muse-cloud/scripts/dev/p1r-external-acceptance.env` 新增 `MUSE_AI_DIFY_*`(base-url/credentials/console-base-urls)与 `MUSE_KNOWLEDGE_DIFY_*`(base-url/dataset-api-key)样例;地址与 key 按内网惯例记入项目凭据文档;**移除该文件中 RAGFlow 条目**(与 S6 同步生效亦可)。 -4. **Datasets contract test(写业务 adapter 前钉契约)**:新增 opt-in live IT(对齐既有 `P1r*LiveAcceptanceIT` 模式,`MUSE_P1R_EXTERNAL_ACCEPTANCE=true` 才真跑)`P1rDifyDatasetsContractLiveIT`,实测并断言:`POST /v1/datasets` 建库字段、`document/create-by-file` 响应含 document.id 与 batch、`documents/{batch}/indexing-status` 状态枚举、`POST /v1/datasets/{id}/retrieve` 响应结构(records[].segment.content/score)。留 `dify-contract-<日期>` dataset 不删(对齐 RAGFlow smoke 惯例)。 +4. **Datasets contract test(写业务 adapter 前钉契约)**:新增 opt-in live IT(对齐既有 `P1r*LiveAcceptanceIT` 模式,`MUSE_P1R_EXTERNAL_ACCEPTANCE=true` 才真跑)`P1rDifyDatasetsContractLiveIT`,实测并断言:`POST /v1/datasets` 建库字段、`document/create-by-file` 响应含 document.id 与 batch、`documents/{batch}/indexing-status` 状态枚举、`POST /v1/datasets/{id}/retrieve` 响应结构(records[].segment.content/score)。留 `dify-contract-<日期>` dataset 不删(对齐 RAGFlow smoke 惯例),仅作为验收证据,不是生产唯一 dataset;生产拓扑见 S6。 5. chat-messages smoke:用既有 `RealDifyMuseAiRuntimeClient` 配置真打「写作透传」app 一次(可借既有 Dify 单测/新增最小 live 用例),证明 app 级 key 与网络通。 **验收**:contract live IT 全绿输出留档;Dify 控制台两 app 可用;检索 smoke 返回结构与 §S6 映射表一致(不一致→先改 review v0.2 映射表再动工)。 @@ -105,7 +105,7 @@ flowchart LR **动作(同一提交)** 1. **兜底 Bean**:muse-server 侧新增 `MarketFacadeFallbackAutoConfiguration`(仿既有 `MonolithFacadeFallbackAutoConfiguration`,`@AutoConfiguration` + `@Bean @ConditionalOnMissingBean`),为三个抽象方法接口提供**真实 fail-closed 方法体**(不能用空匿名类):`MarketHandoffTokenApi`(verify/consume → token 无效/false)、`MarketAssetSourceApi`(→ not found 语义响应)、`MarketAssetForkApi`(recordPublicFork → false)。中文注释写明「market 未装配的单人形态兜底;装配后真实现优先」,首个调用打一次 warn 日志。 2. `muse-server/pom.xml` 注释 `muse-module-market-server` 依赖(保留 `muse-module-market-api`);S2 的 testExcludes 默认生效;台账 JSON market 32 条 dormant patch 生效。 -3. **市场语义端点核验(不改代码,补断言)**:新增/扩展 IT 断言 knowledge 侧市场语义端点在无 market 形态下的行为——`AppMuseKnowledgePublishController` 的 `POST /{kbId}/publish-prechecks|publish-snapshots|publish-readiness` 与 `AppMuseInstalledKnowledgeBaseController`:返回 fail-closed/空结果、无副作用(不产生任何 publish/installed 数据)。 +3. **市场语义端点核验(不改代码,补断言)**:新增/扩展 IT 断言 knowledge 侧 market 装配相关路径在无 market 形态下 fail-closed。`AppMuseInstalledKnowledgeBaseController` 返回空结果;market_kb binding/handoff/fork 依赖 S3 兜底返回 notFound/invalid/false。`AppMuseKnowledgePublishController` 的 `POST /{kbId}/publish-prechecks|publish-snapshots|publish-readiness` 保留 Knowledge 本域预检/快照语义,允许写 readiness/snapshot/command 记录,但不得产生 market 资产、安装、授权事实;前端发布入口由 S5 删除。 4. 全量回归:`local` + `real-pg`(专属 `_test` 库)。 **验收**:local + real-PG 全绿;MockMvc 或活体证明 `/app-api/market/**`、`/admin-api/market/**` 404;步骤 3 断言绿;单体启动日志无 UnsatisfiedDependency。 @@ -164,11 +164,12 @@ flowchart LR 1. **端口改造**(定性:重命名+操作裁剪+配置/审计/测试迁移):`RagFlowKnowledgeRuntimeClient` → `KnowledgeRuntimeClient`,裁掉 7 个无生产调用操作(`updateDatasetConfig/listDatasets/listChunks/runGraphRag/traceGraphRag/getKnowledgeGraph/health`);`MuseKnowledgeAuditService.recordRagflowCall` 更名 `recordRuntimeCall`(表 `muse_knowledge_ragflow_call` 不动,注释订正);同步全部实现/单测/调用方(机械 rename,IDE 级)。 2. **新 adapter**:`DifyKnowledgeRuntimeClient`(实现 5 操作,HTTP 范式对齐 `RealDifyMuseAiRuntimeClient`:JDK HttpClient + 直连 ProxySelector + Bearer + 脱敏 fail-closed);`UnavailableKnowledgeRuntimeClient` 桩;装配类读 `muse.knowledge.dify.*`(base-url/dataset-api-key/timeout-seconds/retry-budget),缺任一装 Unavailable。API 映射按 review §5.2 表,以 S1 contract test 实测为准。 3. **DDL**:`sql/muse/V__knowledge_runtime_batch.sql` 给 `muse_knowledge_processing_task` 加 `runtime_batch_id VARCHAR(160)`(注释:Dify 索引批次);上传链写入 batch,`MuseKnowledgeParseStatusPollWorker` 轮询主键 document_id→batch;绑定表 `ragflow_dataset_id` 列存 Dify dataset id(列注释订正,命名债登记)。 -4. **检索**:`MuseKnowledgeRetrievalApiImpl` 的 chunks 解析适配 Dify 响应;多授权 dataset **并行**检索 + 按 score 合并 topK;耗时与 score 分布打日志(观测跨库可比性)。 -5. **删除 RAGFlow**:`HttpRagFlowKnowledgeRuntimeClient`、其装配与配置类、`P1rRagFlowLiveAcceptanceIT`、`P1rKnowledgeRuntimeEndToEndLiveAcceptanceIT` 的 RAGFlow 形态、env 样例条目;全仓 grep `ragflow`(代码层)清零(表名/列名/迁移历史除外)。 -6. **worker 开启**:单人配置样例 `muse.knowledge.parse-poll-worker.enabled=true`。 -7. **live IT**:新 `P1rDifyKnowledgeRuntimeEndToEndLiveAcceptanceIT`:建库→上传→轮询至 completed→检索命中→授权门阻断路径;fail-closed 反向(无配置→CONFIG_MISSING)。 -8. **检索质量 smoke**:用真实创作素材(设定集/章节)建库检索,人工比对相关性并记录结论;不达标调 Dify 分块参数复测。 +4. **Dataset 拓扑**:同一工作区级 Datasets API key 管理多个 Dify dataset;生产至少支持全局公共 dataset + 作品独立 dataset 的 `datasetId` 映射。禁止把公共资料和作品私有资料混进单一 dataset 后仅靠 metadata 当安全边界;S1 的 `dify-contract-*` dataset 只是契约证据。 +5. **检索**:`MuseKnowledgeRetrievalApiImpl` 的 chunks 解析适配 Dify 响应;多授权 dataset **并行**检索 + 按 score 合并 topK;耗时与 score 分布打日志(观测跨库可比性)。 +6. **删除 RAGFlow**:`HttpRagFlowKnowledgeRuntimeClient`、其装配与配置类、`P1rRagFlowLiveAcceptanceIT`、`P1rKnowledgeRuntimeEndToEndLiveAcceptanceIT` 的 RAGFlow 形态、env 样例条目;全仓 grep `ragflow`(代码层)清零(表名/列名/迁移历史除外)。 +7. **worker 开启**:单人配置样例 `muse.knowledge.parse-poll-worker.enabled=true`。 +8. **live IT**:新 `P1rDifyKnowledgeRuntimeEndToEndLiveAcceptanceIT`:建库→上传→轮询至 completed→检索命中→授权门阻断路径;fail-closed 反向(无配置→CONFIG_MISSING)。 +9. **检索质量 smoke**:用真实创作素材(设定集/章节)建库检索,人工比对相关性并记录结论;不达标调 Dify 分块参数复测。 **验收**:knowledge 模块单测 + local + real-PG 全绿;live IT 全绿留档;质量 smoke 结论入总账;`grep -ri ragflow --include='*.java'` 仅剩 DO/表注释白名单。 **回滚**:revert;RAGFlow 代码经 git 历史可回(review §10 口径:退役不承诺兼容)。 @@ -240,7 +241,7 @@ flowchart LR 1. `docker-compose.solo.yml`(muse-server + PostgreSQL + Redis;env 指向 Dify 与两类 key;`tenant-id=1`;parse-poll worker 开、多用户 worker 全关、`muse.tenant.enable=true`);Dify 沿用 S1 官方 compose 独立部署。 2. 新库 provision:沿用「`sql/dev/yudao-base-*-postgres.sql` 基座 + Flyway V1-V」配方,执行 S4 的 system_menu solo SQL。 3. **无 Nacos 干净启动验证**:无 Nacos 环境起单体,日志无持续报错;有噪声则 `spring.cloud.nacos.discovery.enabled=false` 进 solo 配置。 -4. **黄金旅程 smoke(全程真后端真库真 Dify,`curl --noproxy` + 浏览器)**:登录→建作品→写正文→AI 候选(全文可见)→采纳(revision 递增+归因落库)→上传资料→索引 completed→检索命中→导出下载文件内容含真实正文;隔离核验:`/app-api/market/**` 404、knowledge 市场语义端点 fail-closed、studio 无市场入口、`SELECT` 确认无多用户 worker 活动痕迹。 +4. **黄金旅程 smoke(全程真后端真库真 Dify,`curl --noproxy` + 浏览器)**:登录→建作品→写正文→AI 候选(全文可见)→采纳(revision 递增+归因落库)→上传资料→索引 completed→检索命中→导出下载文件内容含真实正文;隔离核验:`/app-api/market/**` 404、knowledge market 装配相关路径 fail-closed/空结果、publish 三端点不产生 market 资产/安装/授权事实、studio 无市场入口、`SELECT` 确认无多用户 worker 活动痕迹。 5. 部署手册(docs 内,含:compose 双栈、凭据表指引、菜单 SQL、回滚步骤、Dify 版本升级前须跑 live 验收的约定)。 **验收**:review v0.2 §9 全部 7 条判据逐条留证(这是 1a 的总验收门)。 diff --git a/docs/agent-specs/2026-07-07-2.0.0单人版改造-review.md b/docs/agent-specs/2026-07-07-2.0.0单人版改造-review.md index 88e2c7be..cc67360a 100644 --- a/docs/agent-specs/2026-07-07-2.0.0单人版改造-review.md +++ b/docs/agent-specs/2026-07-07-2.0.0单人版改造-review.md @@ -158,6 +158,8 @@ flowchart TB | pollDocumentStatuses | `GET /v1/datasets/{id}/documents/{batch}/indexing-status` | **轮询主键从 document_id 变 batch**;状态映射 waiting/parsing/…/error→Muse 归一状态 | | retrieveChunks | `POST /v1/datasets/{id}/retrieve` | **单库端点→按授权 dataset 并行循环检索,合并按 score 取 topK**;响应结构在 adapter 内归一,调用方不变 | +**Dataset 拓扑口径(2026-07-08 裁决)**:工作区级 Datasets API key 是管理钥匙,不代表生产只使用一个 dataset。生产运行时至少区分全局公共 dataset 与作品独立 dataset;Muse 本地保存 `datasetId` 映射,检索时按授权选择公共 + 当前作品 dataset 循环检索并合并排序。禁止把公共资料和作品私有资料混进单一 dataset 后仅靠 metadata 当安全边界。S1 的 `dify-contract-*` dataset 只作为 live contract 验收证据,不是生产唯一知识库。 + **数据与配置落点**: - `muse_knowledge_processing_task` 新增列 `runtime_batch_id VARCHAR(160)`(取 `sql/muse` 当前最大版本+1 的迁移)承载 Dify batch;`muse_knowledge_ragflow_binding` 等三表**表名列名不动**,`ragflow_dataset_id` 列直接存 Dify dataset id,列注释订正——显式命名债,留待 P2-06 统一 Schema 收口。 @@ -174,7 +176,7 @@ flowchart TB 1. **补兜底再摘除**:在 muse-server 侧为 `MarketHandoffTokenApi`(verify→invalid)、`MarketAssetSourceApi`(→not found)、`MarketAssetForkApi`(→false)提供 `@ConditionalOnMissingBean` 的兜底 Bean(三接口均为抽象方法接口,**须写真实 fail-closed 方法体**);然后 muse-server pom 注释 `muse-module-market-server` 依赖(market-api 纯接口依赖保留)。market 装回时真实现自动优先,兜底让位。 2. **测试树编译处置(与 1 同步,见 §5.5)**:14 个 market 依赖测试文件不迁移、不删除,用 maven profile 化的 testExcludes 从默认编译中排除(恢复=激活 profile),其中 2 个 Account 侧混合测试一并排除并在台账登记。 -3. **市场语义端点收口(不随 market 模块消失的部分)**:knowledge 模块自带的市场语义端点——`AppMuseKnowledgePublishController` 的 publish-prechecks/publish-snapshots/publish-readiness 三个 POST 与 `AppMuseInstalledKnowledgeBaseController`(已安装库)——**代码不动**,行为依赖 market 兜底与外部校验器配置,自然 fail-closed/空结果;S3 验收对其行为做显式断言(不可产生副作用),前端入口已删,不新增 dormant 机制。 +3. **市场语义端点收口(不随 market 模块消失的部分)**:knowledge 模块自带的市场语义端点分两类处理。`AppMuseKnowledgePublishController` 的 publish-prechecks/publish-snapshots/publish-readiness 三个 POST 保留为 Knowledge 本域预检/快照能力,允许写本域 readiness/snapshot/command 记录,但不得产生 market 资产、安装、授权事实;前端发布入口在 S5 删除。`AppMuseInstalledKnowledgeBaseController` 与 market_kb binding/handoff/fork 路径依赖 market 兜底,必须 fail-closed/空结果。S3 验收断言 market API fallback、installed 空结果、market route 404;不要求 publish 三端点零本域副作用,也不新增 dormant 机制。 4. **member 子域收口(v0.2 订正口径:三类机制,不是「全走开关」)**:保留 登录/会话(member_user + system oauth2)、profile、命令幂等、审计、usage 只读观测。多用户子域的实际关闭机制分三类——① 真配置开关仅一个:`muse.account.new-api.enabled=false`(默认已关);② worker enabled 属性:quota/attribution/事件发布 worker(默认已关,配置文档固化);③ `@ConditionalOnMissingBean` 兜底:市场记录投影、账户导出的 FileService 依赖(market/真实 provider 缺席自动 Unavailable,无开关可设也无需设);entitlement 为本地常开纯读逻辑,无外部副作用,不动。yudao 会员商城子域(积分/等级/签到/标签)不动,仅菜单隐藏。 5. **租户/登录/worker 核对**:租户保持 enable、固定 tenant=1(**禁止关 `muse.tenant.enable`**,§2.7 红线);studio 仅保留单账号登录,不暴露注册/多用户入口;确认多用户向 worker 全部处于关闭,AI dispatcher 与质量评估 worker 保留。 @@ -236,7 +238,7 @@ flowchart TB ### 6.3 知识库(knowledge) -- **边界**:含 我创建的知识库 + 全局公共授权库、资料上传→索引→状态轮询、知识草稿(确认/忽略/重验)、实体关系 CRUD 与图谱可视化(Muse 自有 Canonical 数据)、作品级来源绑定与读回、授权过滤检索供 AI 上下文。不含:市场安装库、发布入口(后端市场语义端点保留但 fail-closed,见 §5.3.3)、GraphRAG、recheck 外部校验 worker。 +- **边界**:含 我创建的知识库 + 全局公共授权库、资料上传→索引→状态轮询、知识草稿(确认/忽略/重验)、实体关系 CRUD 与图谱可视化(Muse 自有 Canonical 数据)、作品级来源绑定与读回、授权过滤检索供 AI 上下文。不含:市场安装库、前端发布入口(后端 publish 预检/快照保留为 Knowledge 本域能力,market 装配相关路径 fail-closed/空结果,见 §5.3.3)、GraphRAG、recheck 外部校验 worker。 - **要求**:进 Local KB 唯一入口=用户确认草稿;检索前授权双门全在 Muse 侧;运行时故障检索降级为空、不阻断生成、不伪造结果;parse-poll worker 单人配置显式开启。 - **目标**:1a 运行时切 Dify Datasets(上传/轮询/检索三链真验证);1b 补处理状态与失败恢复 UI(P0-06:失败原因、重试/删除/重新上传入口,基于 Dify indexing-status 的失败态)。 @@ -313,7 +315,7 @@ flowchart LR 4. **fail-closed 反向证据**:Dify 未配置/凭据错误时,生成、检索、导入均拒绝服务且错误码正确;New-API parser 在无配置时 fail-closed;启动断言证明无 yudao 厂商 provider Bean。 5. **前端**:studio tsc/vitest/build 绿;MSW-off Playwright 创作主线 spec(候选采纳、导入向导、导出下载、知识草稿/图谱/绑定、agent 生命周期)全绿;market spec 已隔离不再计入。admin:muse.test.ts 绿,「市场治理/用户与权限」菜单不可见断言,newapi 页无写入口断言。 6. **单机活体**:单人版 compose 从零起全栈(基座 SQL + Flyway provision),黄金旅程 smoke:登录→建作品→写正文→AI 候选(全文可见)→采纳→上传资料→检索命中→导出下载,全程真后端真库真 Dify。 -7. **隔离核验**:`/app-api/market/**`、`/admin-api/market/**` 物理 404;knowledge 侧市场语义端点(publish-prechecks 等)行为 fail-closed 无副作用;studio 无市场入口;多用户 worker 无一运行。 +7. **隔离核验**:`/app-api/market/**`、`/admin-api/market/**` 物理 404;knowledge 侧 market_kb binding/handoff/fork/installed 路径 fail-closed/空结果;publish-prechecks/publish-snapshots/publish-readiness 只保留 Knowledge 本域预检/快照记录,不得产生 market 资产、安装、授权事实;studio 无市场入口;多用户 worker 无一运行。 --- @@ -368,7 +370,7 @@ member 子域:打开对应 enabled 开关即回(quota/归因/绑定 worker 与 | P0 | yudao 厂商脚手架 9 项 `enable: true` 且明文 key 在仓,违背「只直连 Dify」(Codex,已核实且比评审所述更重) | §2 表格新增第 4 行;§5.1.5 显式关停+启动断言+key 处置;§8 风险 | | P1 | 覆盖门硬编码 241/47 与台账 242/48 矛盾,local 门禁疑现红(Opus,静态坐实) | §2.11 新增;S0 立为首步 | | P1 | 门禁清单遗漏:P1rMarketRealApiGateTest、coverage testFiles/summary 连带、ContractFirstGateTest 要求 market 契约存在(Opus/Codex,已核实) | §5.5 重写补全 | -| P1 | 市场语义端点不随 market 模块消失(knowledge publish 三端点、installed KB),按路径验收会假绿(Codex,已核实) | §5.3.3 新增;§9.7 断言扩 | +| P1 | 市场语义端点不随 market 模块消失(knowledge publish 三端点、installed KB),按路径验收会假绿(Codex,已核实;2026-07-08 已裁决 publish 三端点保留 Knowledge 本域能力,market 装配路径 fail-closed) | §5.3.3 新增;§9.7 断言扩 | | P1 | admin newapi 页含写命令,与「只读保留」冲突(Codex,已核实) | §5.4/§6.6 明确禁写+断言 | | P1 | 导入解析是单实现单点注入,New-API 退出前须先具备 provider 选择与反向测试(Codex,已核实) | §5.1.3 完成门 | | P1 | Dify Datasets 映射是假设,须 contract test 前置钉版本(Codex) | §5.2 契约先行 | diff --git a/docs/mvp/进度总账.md b/docs/mvp/进度总账.md index 4cd7e74d..1a6e4c44 100644 --- a/docs/mvp/进度总账.md +++ b/docs/mvp/进度总账.md @@ -29,6 +29,8 @@ **2.0.0 单人版改造 S2 门禁与测试树适配(2026-07-07)**:已完成“机制就绪、不拨开关”。`muse-server` 新增 `market-detached-test-tree` profile,显式 `-Dmuse.market.detached.test-tree=true` 时用 compiler `testExcludes` + surefire excludes 剥离 14 个 market/account 混合装配测试及 `P1rMarketRealApiGateTest`;默认本地构建仍编译全量测试树,`market-assembled` 可在 dry-run profile 同启时恢复全量装配口径。`P1rApiCoverageReportTest` 引入仅 market 可用的 `dormant` 口径,dormant 保留总 operation 242、不计入 completed、跳过 completed 证据强制,但仍要求 dedicated 且属于批准域;覆盖 JSON 本步未改。AI/Knowledge/Events 三个 RealApiGate 的汇总断言同步改为 `totalOperations=242`、`completedOperations=242-marketDormant`,避免 S3 dry-run 在非 market gate 假红。Admin e2e 增加 `MUSE_ADMIN_E2E_MARKET=false` 开关,显式关闭时跳过 market/account 治理切片和 market fixture,默认不跳过。验收证据:Admin typecheck 通过;`P1rApiCoverageReportTest` 默认 9/0F/0E/0S;AI/Knowledge/Events RealApiGate 22/0F/0E/0S;`-Pmarket-assembled -DskipTests clean test` 回编译全量 91 个 muse-server 测试源;主工作树 `run-p1r-verification.sh local` 62/0F/0E/0S;临时 worktree 注释 market-server 依赖并把 market 32 条置 dormant 后,带 `MAVEN_ARGS='-Pmarket-detached-test-tree -Dmuse.market.detached.test-tree=true'` 的 local dry-run 56/0F/0E/0S。未改 OpenAPI、DDL、业务实现、覆盖 JSON 或外部凭据;S3 仍需原子拨开关。 +**2.0.0 单人版改造 S3 market 摘装配(2026-07-08)**:已原子拨开关并完成验收。`muse-server` 默认保留 `muse-module-market-api`、停用 `muse-module-market-server`,`market-detached-test-tree` 默认生效;`market-assembled` profile 恢复 market-server 依赖与全测试树回编译。`MonolithFacadeFallbackAutoConfiguration` 为 `MarketHandoffTokenApi`、`MarketAssetSourceApi`、`MarketAssetForkApi` 增加 `@Bean @ConditionalOnMissingBean` fail-closed 兜底:verify 返回 `valid=false/MARKET_DETACHED`,consume 返回 `consumed=false`,asset source 返回 `notFound()`,fork 写回返回 `false`,首次调用 warn 日志;真实 market Bean 存在时不覆盖。覆盖 JSON 将 market 32 条 operation 改为 `dormant`,`summary.completedOperations=210`,未删 operation/testFiles/sourceFiles,未改 OpenAPI/DDL/业务实现/外部凭据。新增 `P1rMarketDetachedAssemblyGateTest` 证明 fallback、真实 Bean 优先、默认摘装配、`/app-api/market/**`/`/admin-api/market/**` 404 形态;Knowledge installed 列表补空结果断言,既有 market_kb binding notFound/fork-not-ready 测试继续覆盖 fail-closed。验收证据:server targeted gate `P1rApiCoverageReportTest`+`P1rMarketDetachedAssemblyGateTest` 13/0F/0E/0S;knowledge targeted `MuseKnowledgeBindingServiceTest`+`MuseInstalledKnowledgeBaseServiceTest` 25/0F/0E/0S;`-Pmarket-assembled -DskipTests test` 47 模块 reactor BUILD SUCCESS;`run-p1r-verification.sh local` 60/0F/0E/0S;`run-p1r-verification.sh real-pg` 真实 `_test` 库 143/0F/0E/0S,外部验收 opt-in 后 `P1rContentImportWizardCompletedApprovalIT` 无 skip 且 provider 200;coverage 计数核验 `total=242/completed=210/marketDormant=32/nonMarketDormant=0`。2026-07-08 裁决:publish-prechecks/snapshots/readiness 保留 Knowledge 本域预检/快照能力,允许写 readiness/snapshot/command 记录,但不得产生 market 资产、安装、授权事实;前端发布入口由 S5 删除。 + **E1 content 创作闭环切片(2026-06-27)**:已补 AI suggestion 采纳归档、前端“改后合并”入口、IndexedDB 草稿键对账、旧知识草稿失效、工作台知识/导入/导出/记录入口、Block 版本历史最小 API/UI。Content merge 写 Canonical 与来源归因后,必须由 AI owner 写 `accepted` 状态、accepted decision archive、AI command、business audit;AI 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/0E;real-PG `P1rContentCoreCompletedApprovalIT` 13/0F/0E/0S + `P1rContentMergeSuggestionIT` 5/0F/0E/0S + `P1rContentMergeGeneratedSuggestionIT` 1/0F/0E/0S;studio `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/0E;studio `tsc -b --force` 通过、目标 ESLint 0 errors、Vitest 29/0F/0E;MSW-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 archived;Slot unbind DB 核验 revision 2→3、status archived、响应 `sourceStatus=unbound`。边界:本轮未跑 studio 全量 e2e。 diff --git a/docs/superpowers/reports/p1r-api-coverage.json b/docs/superpowers/reports/p1r-api-coverage.json index 48ef0228..78be490e 100644 --- a/docs/superpowers/reports/p1r-api-coverage.json +++ b/docs/superpowers/reports/p1r-api-coverage.json @@ -11,7 +11,7 @@ ], "summary": { "totalOperations": 242, - "completedOperations": 242, + "completedOperations": 210, "incompleteOperations": 0, "needsVerificationOperations": 0, "catchAllOperations": 0, @@ -16529,7 +16529,7 @@ "externalDependencies": [], "targetStage": "P1R-6 Market Real API", "implementationStatus": "dedicated", - "completionStatus": "completed", + "completionStatus": "dormant", "controllerFiles": [ "muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/controller/admin/muse/AdminMuseMarketAppealController.java" ], @@ -16583,7 +16583,7 @@ "externalDependencies": [], "targetStage": "P1R-6 Market Real API", "implementationStatus": "dedicated", - "completionStatus": "completed", + "completionStatus": "dormant", "controllerFiles": [ "muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/controller/admin/muse/AdminMuseMarketAppealController.java" ], @@ -16637,7 +16637,7 @@ "externalDependencies": [], "targetStage": "P1R-6 Market Real API", "implementationStatus": "dedicated", - "completionStatus": "completed", + "completionStatus": "dormant", "controllerFiles": [ "muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/controller/admin/muse/AdminMuseMarketAppealController.java" ], @@ -16691,7 +16691,7 @@ "externalDependencies": [], "targetStage": "P1R-6 Market Real API", "implementationStatus": "dedicated", - "completionStatus": "completed", + "completionStatus": "dormant", "controllerFiles": [ "muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/controller/admin/muse/AdminMuseMarketAssetController.java" ], @@ -16745,7 +16745,7 @@ "externalDependencies": [], "targetStage": "P1R-6 Market Real API", "implementationStatus": "dedicated", - "completionStatus": "completed", + "completionStatus": "dormant", "controllerFiles": [ "muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/controller/admin/muse/AdminMuseMarketAssetController.java" ], @@ -16799,7 +16799,7 @@ "externalDependencies": [], "targetStage": "P1R-6 Market Real API", "implementationStatus": "dedicated", - "completionStatus": "completed", + "completionStatus": "dormant", "controllerFiles": [ "muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/controller/admin/muse/AdminMuseMarketAssetController.java" ], @@ -16853,7 +16853,7 @@ "externalDependencies": [], "targetStage": "P1R-6 Market Real API", "implementationStatus": "dedicated", - "completionStatus": "completed", + "completionStatus": "dormant", "controllerFiles": [ "muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/controller/admin/muse/AdminMuseMarketAssetController.java" ], @@ -16907,7 +16907,7 @@ "externalDependencies": [], "targetStage": "P1R-6 Market Real API", "implementationStatus": "dedicated", - "completionStatus": "completed", + "completionStatus": "dormant", "controllerFiles": [ "muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/controller/admin/muse/AdminMuseMarketAssetController.java" ], @@ -16961,7 +16961,7 @@ "externalDependencies": [], "targetStage": "P1R-6 Market Real API", "implementationStatus": "dedicated", - "completionStatus": "completed", + "completionStatus": "dormant", "controllerFiles": [ "muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/controller/admin/muse/AdminMuseMarketReviewController.java" ], @@ -17015,7 +17015,7 @@ "externalDependencies": [], "targetStage": "P1R-6 Market Real API", "implementationStatus": "dedicated", - "completionStatus": "completed", + "completionStatus": "dormant", "controllerFiles": [ "muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/controller/admin/muse/AdminMuseMarketReviewController.java" ], @@ -17069,7 +17069,7 @@ "externalDependencies": [], "targetStage": "P1R-6 Market Real API", "implementationStatus": "dedicated", - "completionStatus": "completed", + "completionStatus": "dormant", "controllerFiles": [ "muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/controller/admin/muse/AdminMuseMarketReviewController.java" ], @@ -17126,7 +17126,7 @@ ], "targetStage": "P1R-6 Market Real API", "implementationStatus": "dedicated", - "completionStatus": "completed", + "completionStatus": "dormant", "controllerFiles": [ "muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/controller/app/muse/AppMuseMarketAppealController.java" ], @@ -17183,7 +17183,7 @@ ], "targetStage": "P1R-6 Market Real API", "implementationStatus": "dedicated", - "completionStatus": "completed", + "completionStatus": "dormant", "controllerFiles": [ "muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/controller/app/muse/AppMuseMarketAppealController.java" ], @@ -17240,7 +17240,7 @@ ], "targetStage": "P1R-6 Market Real API", "implementationStatus": "dedicated", - "completionStatus": "completed", + "completionStatus": "dormant", "controllerFiles": [ "muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/controller/app/muse/AppMuseMarketAppealController.java" ], @@ -17297,7 +17297,7 @@ ], "targetStage": "P1R-6 Market Real API", "implementationStatus": "dedicated", - "completionStatus": "completed", + "completionStatus": "dormant", "controllerFiles": [ "muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/controller/app/muse/AppMuseMarketplaceAssetController.java" ], @@ -17354,7 +17354,7 @@ ], "targetStage": "P1R-6 Market Real API", "implementationStatus": "dedicated", - "completionStatus": "completed", + "completionStatus": "dormant", "controllerFiles": [ "muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/controller/app/muse/AppMuseMarketplaceAssetController.java" ], @@ -17411,7 +17411,7 @@ ], "targetStage": "P1R-6 Market Real API", "implementationStatus": "dedicated", - "completionStatus": "completed", + "completionStatus": "dormant", "controllerFiles": [ "muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/controller/app/muse/AppMuseMarketHandoffController.java" ], @@ -17468,7 +17468,7 @@ ], "targetStage": "P1R-6 Market Real API", "implementationStatus": "dedicated", - "completionStatus": "completed", + "completionStatus": "dormant", "controllerFiles": [ "muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/controller/app/muse/AppMuseMarketplaceAssetController.java" ], @@ -17525,7 +17525,7 @@ ], "targetStage": "P1R-6 Market Real API", "implementationStatus": "dedicated", - "completionStatus": "completed", + "completionStatus": "dormant", "controllerFiles": [ "muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/controller/app/muse/AppMuseMarketplaceAssetController.java" ], @@ -17582,7 +17582,7 @@ ], "targetStage": "P1R-6 Market Real API", "implementationStatus": "dedicated", - "completionStatus": "completed", + "completionStatus": "dormant", "controllerFiles": [ "muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/controller/app/muse/AppMuseMarketplaceAssetController.java" ], @@ -17639,7 +17639,7 @@ ], "targetStage": "P1R-6 Market Real API", "implementationStatus": "dedicated", - "completionStatus": "completed", + "completionStatus": "dormant", "controllerFiles": [ "muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/controller/app/muse/AppMuseMarketLicenseController.java" ], @@ -17696,7 +17696,7 @@ ], "targetStage": "P1R-6 Market Real API", "implementationStatus": "dedicated", - "completionStatus": "completed", + "completionStatus": "dormant", "controllerFiles": [ "muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/controller/app/muse/AppMuseMarketLicenseController.java" ], @@ -17753,7 +17753,7 @@ ], "targetStage": "P1R-6 Market Real API", "implementationStatus": "dedicated", - "completionStatus": "completed", + "completionStatus": "dormant", "controllerFiles": [ "muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/controller/app/muse/AppMuseMarketplaceAssetController.java" ], @@ -17810,7 +17810,7 @@ ], "targetStage": "P1R-6 Market Real API", "implementationStatus": "dedicated", - "completionStatus": "completed", + "completionStatus": "dormant", "controllerFiles": [ "muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/controller/app/muse/AppMuseMarketHandoffController.java" ], @@ -17867,7 +17867,7 @@ ], "targetStage": "P1R-6 Market Real API", "implementationStatus": "dedicated", - "completionStatus": "completed", + "completionStatus": "dormant", "controllerFiles": [ "muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/controller/app/muse/AppMuseMarketHandoffController.java" ], @@ -17924,7 +17924,7 @@ ], "targetStage": "P1R-6 Market Real API", "implementationStatus": "dedicated", - "completionStatus": "completed", + "completionStatus": "dormant", "controllerFiles": [ "muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/controller/app/muse/AppMuseMarketHandoffController.java" ], @@ -17981,7 +17981,7 @@ ], "targetStage": "P1R-6 Market Real API", "implementationStatus": "dedicated", - "completionStatus": "completed", + "completionStatus": "dormant", "controllerFiles": [ "muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/controller/app/muse/AppMuseMarketPublishController.java" ], @@ -18038,7 +18038,7 @@ ], "targetStage": "P1R-6 Market Real API", "implementationStatus": "dedicated", - "completionStatus": "completed", + "completionStatus": "dormant", "controllerFiles": [ "muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/controller/app/muse/AppMuseMarketPublishController.java" ], @@ -18095,7 +18095,7 @@ ], "targetStage": "P1R-6 Market Real API", "implementationStatus": "dedicated", - "completionStatus": "completed", + "completionStatus": "dormant", "controllerFiles": [ "muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/controller/app/muse/AppMuseMarketPublishController.java" ], @@ -18152,7 +18152,7 @@ ], "targetStage": "P1R-6 Market Real API", "implementationStatus": "dedicated", - "completionStatus": "completed", + "completionStatus": "dormant", "controllerFiles": [ "muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/controller/app/muse/AppMuseMarketPublishController.java" ], @@ -18209,7 +18209,7 @@ ], "targetStage": "P1R-6 Market Real API", "implementationStatus": "dedicated", - "completionStatus": "completed", + "completionStatus": "dormant", "controllerFiles": [ "muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/controller/app/muse/AppMuseMarketPublishController.java" ], @@ -18266,7 +18266,7 @@ ], "targetStage": "P1R-6 Market Real API", "implementationStatus": "dedicated", - "completionStatus": "completed", + "completionStatus": "dormant", "controllerFiles": [ "muse-cloud/muse-module-market/muse-module-market-server/src/main/java/cn/iocoder/muse/module/market/controller/app/muse/AppMuseMarketplaceAssetController.java" ], diff --git a/muse-cloud/muse-module-ai/.agent b/muse-cloud/muse-module-ai/.agent index 6054f4f5..b06cff6d 100644 --- a/muse-cloud/muse-module-ai/.agent +++ b/muse-cloud/muse-module-ai/.agent @@ -12,5 +12,6 @@ - **2.0.0 S0 覆盖门基线(2026-07-07)**:`adminArchiveAgent` 已作为 AI completed operation 纳入覆盖 JSON,AI 域计数为 48,总 completed 为 242。已同步修正 Market/Knowledge/Events legacy gate 中仍停留在 `AI 47 / completedOperations 241` 的陈旧断言;`run-p1r-verification.sh local` 复跑 61/0F/0E/0S、BUILD SUCCESS。后续不要把 AI 覆盖口径回退到 47/241。 - **Dify Adapter 最小闭环(2026-06-29)**:系统 Agent 版本配置新增 `runtimeProvider` / `providerRef.dify` / `outputContract`;新发布版本必须显式 provider,旧版本缺省 runtime 仅兼容为 `new-api`。`RoutingMuseAiRuntimeClient` 按 provider 失败关闭路由,`RealDifyMuseAiRuntimeClient` 支持 Dify blocking chat/workflow 调用、错误分类、timeout、脱敏、无隐式 fallback;Dify 内部知识上下文标记 `untraceable_provider_context`,当输出合同要求 Muse source refs 时直接失败关闭。Dify 密钥不入库,只通过 `credentialRef` 从后端配置取;Admin 发布页只保存 Dify app/workflow/credentialRef 引用,`consoleUrl` 由后端按 allowlist 拼装。验证:`MuseAgentServiceTest`+`MuseAiRuntimeClientTest` 51/0F/0E;Admin AI 目标 Vitest 2 files / 9 tests passed。后续仍需真实 Dify live 验证与用量进入 New-API/Member 归属链路后再宣称生产级可计费。 - **2.0.0 S1 Dify live 验证(2026-07-07)**:Dify 1.15.0 已在 `mini-infra` 独立部署,写作 chat app `75f105e8-dbca-4aa6-a296-2113a65ad005` 通过 `P1rDifyChatLiveAcceptanceIT` 真跑;默认不设置 `MUSE_P1R_EXTERNAL_ACCEPTANCE=true` 时 honest skipped,显式 opt-in 后真实 `POST /v1/chat-messages` 返回 `status=success`、`messageIdPresent=true`、`summarySha256Prefix=565339bc4d33`。后端配置新增 `muse.ai.dify.*` env 占位,密钥仍只走 `credentialRef`/env,不进 DB;S1 只证明 Dify app 可调用,New-API attribution 仍显示 `requires_new_api_attribution`,后续接 Dify runtime 归属/计费前不得宣称 Member 用量已闭环。 +- **2.0.0 S3 market 摘装配影响(2026-07-07)**:默认单体无 market-server,历史 E4 market agent handoff 物化链路进入 dormant;AI owner 自建 Agent、槽位、生成、SSE、Dify/New-API runtime 不依赖 market-server,仍按自身门禁验证。恢复 market agent 安装/物化验收时用 `muse-server -Pmarket-assembled`。 - **RC 后完整导入解析 owner(2026-06-28)**:新增 `MuseAiImportParseApi` 与 `MuseAiImportParseService`,由 AI owner 持久化 `muse_ai_parse_job` / `muse_ai_chapter_parse_result`,读取 Content 传入的稳定 storageRef 后支持 txt/markdown/docx/epub 文本抽取;默认按 Markdown/CN/EN 标题做确定性章节拆分,`parseConfig.mode=full_book` 或 `strategy=llm_full_book` 时调用 New-API LLM 全书解析,并对 timeout/429/5xx/连接失败做可重试处理。解析只输出 pending-review Shadow 章节并支持确认/驳回/replay,不写 Content Canonical,不写 Knowledge Local KB。验证:`MuseAiImportParseServiceTest`、`NewApiMuseAiImportLlmParserTest`、`P1rImportLlmNewApiLiveAcceptanceIT` live 与完整导入链路 real-PG/live 测试通过。 - **关键风险 / TODO**:**AI runtime 生成 e2e 已端到端真验(2026-06-24)**——方案1(d647f0c 剥离 sourceSnapshotId 解授权死结)+ D(4dadc7b `DefaultDBFieldHandler` 无登录兜底 `system`,根治 scheduled worker 写 creator/updater null 违约 NOT NULL;BaseDO 带 jdbcType 强制入列致 insert/update 同病)+ agent_version config 补 modelKey=MiniMax-M2.5 + 启动 source `scripts/dev/p1r-external-acceptance.env`(New-API/RAGFlow 配置)→ task5 completed+suggestion+MiniMax-M2.5 真 LLM 生成(P-B envelope/approved、P-C contextAssembly 固化 ✓)。**P-A/P-B/P-C 完整 e2e 已端到端真验打通(2026-06-24,task7)**:P-A 检索命中根因=knowledge 模块 bug——`HttpRagFlowKnowledgeRuntimeClient.retrieveChunks` 把 `document_ids` 以 null 入 retrieval body,RAGFlow 要求其为 list 故拒(code 102 "documents should be a list")→检索 fail-closed→chunkCount=0(task3/4/5/6 全 0 即此 bug,非 RAGFlow 数据/凭据/timeout)。已修(commit 86c6dba:null 时不入 body + 回归测试)。task7 contextAssembly `chunkCount=1/retrievedKbIds=[2]/authorizationSnapshotIds=[authsnap-e2e-1]`,LLM(MiniMax-M2.5)基于 grounding 续写。**02D 槽位首次创建死锁已修+真验(2026-06-24,commit 9115d4f)**:根因=precheck/bind 都 `requireSlotBinding`(缺行抛 forbidden),而"替换"是设计中唯一创建入口("替换"=首次写入、无独立创建端点是设计有意,见后端-04 line748/产品-02D line507)→死锁,首次绑定永不可达(work4 seed binding 实为 E2E 绕此 bug 手插)。修:precheck 缺行放行 + bind 抽 `persistBinding` upsert(无行 insert revision=1/有行 updateById 沿用乐观锁)+ V29 `uk_muse_agent_slot_binding_active` partial unique index(同 work+slot 仅一条 active 并发兜底)+ 前端 mock protected 口径对齐 `protected:` 前缀。真验(muse_slice_live):work1 `writing.continuation` precheck(不再 forbidden)→bind 首次建 binding(slotRevision=1 active)→GET 槽位显示 agent1→AI task 用 `agentSlotKey`(非 agentOverrideRef)→agent1 解析 + runtime 授权过门 → completed + suggestion7(MiniMax-M2.5 真 LLM"星环大陆魔法体系"续写、finishReason stop)。**前端 MVP(df063cb)agentSlotKey 路径真后端端到端走通**;单测 `MuseAgentSlotServiceTest` 26/26 + Controller 4/4 + 前端 4/4;V29 由 Flyway 启动日志证 applied。**E2 智能体生命周期与候选拒绝已补并真验(2026-06-27)**:用户自建 Agent create 会同步建 v1 active version;update 创建下一 active version并更新 `current_version_id`;版本面板支持 list/activate/archive 非当前版本;archive Agent 同步 archive active 槽位绑定;slot unbind 将 binding revision+1、status=`archived`、响应 `sourceStatus=unbound`;WorkspacePage reject 调 AI owner `POST /suggestions/{id}/reject` 写 `rejected` 与 decision archive。验证:AI targeted 单测+Controller 63/0F/0E,Studio `tsc`/目标 ESLint/targeted Vitest 29/0F/0E,MSW-off Playwright 真后端 5/0F/0E(采纳真生成 suggestionId=80、拒绝真生成 suggestionId=81、Agent 生命周期 DB 核验、Slot unbind DB 核验)。**仍缺/线 B**:Override Slot Contract 主数据(方案 B:合法 slot 校验/系统默认 agent/listWorkAgentSlots 预设骨架未做,slotKey 暂自由字符串、listSlots 缺行仅返回已绑定);前端 agent "运行试用"等深链;Quality eval 真实评估集管理/外发授权/LLM judge 仍后置;grant/runtime 完整物理包拆分仍未做。SSE `event:` 解析漂移已修复并有 targeted Vitest 证据(2026-06-19:`sse.test.ts` + `AIPanel.contract.test.tsx` 17/17);`muse-module-ai-contract-server` 空壳孤儿 submodule 已清理(2026-06-19:从 AI reactor 移除并删除唯一 pom,`P1rAiRouteOwnershipTest` 加回潮门禁)。**2026-06-25:AI 生成候选采纳断层已修(ADR-020 方案 A,commit a16c596)**——`MuseAiRuntimeProjectionService` 此前用 `numericEnvelopeId(Long.parseLong)` 落 authz 快照,但真 runtime envelope 是字符串 `rpe-local-uuid`→解析失败落 null→suggestion 恒不可采纳(merge 1041001001);改直落字符串 envelope(删 numericEnvelopeId)、`MuseAiSuggestionDO.authorizationSnapshotId` 改 String、`AiSuggestionMergeProjectionFacade.getSuggestion` 校验改 hasText、V30 列 BIGINT→VARCHAR(128)。揭 `P1rContentMergeGeneratedSuggestionIT` 假绿(注入数值 9001、真 runtime 不产数值),改真字符串 envelope 转真绿。活体真验:真生成 suggestion authz=rpe-local 字符串→采纳 merge code=0+block revision 自增,生成→采纳→Canonical 全链通。详见总账 2026-06-25 / ADR-020 / memory muse-ai-accept-authz-snapshot-fix。**2026-06-27:E1 accepted decision owner 归档已补**——`AiSuggestionMergeProjectionFacade.markSuggestionAccepted` 作为 AI owner 写 `muse_ai_suggestion_decision`、更新 suggestion `status=accepted/decision_archive_id`、记录 AI command 与 business audit;幂等 replay 不重复写,非 pending 候选返回 conflict 且不预占 command。注意边界:AI 仍只归档 Shadow 决策与审计,不写 Canonical 正文,不保存 `finalContent` 或 provider 原文。P1r 真 PG 切片 6/0F/0E/0S。**2026-06-25(同日续):槽位绑定授权快照孪生收口(C1,commit 854f18c)**——三路盘点发现 `muse_agent_slot_binding.authorization_snapshot_id` 是采纳断层唯一漏网 binding 表(列 BIGINT、DO Long,bind 时 parseLong 对字符串 envelope `rpe-local-` 返 null→授权快照静默丢失、绑定溯源链断);V31 两列(authz+source)BIGINT→VARCHAR(128)、DO Long→String、bind 两处(insert/updateById)直透传删 parseLong(`sameAuthorizationSnapshot` 的 parseLong 保留,字符串 envelope 走 fallback 字符串比较正确)。活体真验(muse_slice_live):真 envelope 经 precheck→bind,binding authz 列存非空 rpe-local(insert id=51+updateById id=50 两路径),列类型 varchar(128)、flyway V31 success;修前恒 null。**此前模块"仍缺"只列方案 B/深链/物理拆分,从未记此类型错配——盘点补上的盲区**。至此 ADR-020 全部 binding 表(knowledge V14 + suggestion/attribution V30 + slot V31)收口完成。 **2026-06-25(同日续):AI 流 SSE 长任务候选丢失已修(commit c77b006、A1+候选③、design-docs/临时-01 review 批准后执行)**——studio e2e ai-generation 真红根因=后端 `MuseAiTaskStreamServiceImpl` SSE 死线 30s 硬编码 < LLM 真时延(MiniMax 11-60s)、更 < 上游自己 `MUSE_AI_NEW_API_TOTAL_TIMEOUT_SECONDS=180`,poll 30s 后 `emitter.complete()` 静默关连接不补 done;前端 `connectAIStream` 一次性连接无重连(重连只在 connectEventStream)→LLM>30s 候选永丢(ai-gen 真红/accept flaky 是同 bug 时延两侧)。修:① 后端删 `DEFAULT_TIMEOUT_MILLIS` 常量→`@Value("${muse.ai.sse.task-timeout-millis:240000}")`(连接死线:70+poll deadline:198 同引用、application.yaml 配置项);② 前端退避重连续 poll + 按 SSE id 去重(后端不读 lastEventId、每次 seq0 全量回放→`lastSeenSequenceNo` 丢弃 id≤已见,避免重复渲染候选)+总超时 300s `onError(SSE_TIMEOUT)` 不静默卡死(返回仍 AbortController、AIPanel 零改动);③ A1 `scripts/dev/start-muse-server-infra.sh` 固化追加 source acceptance env(防漏 `MUSE_AI_NEW_API_*`→`UnavailableMuseAiRuntimeClient` 兜底秒失败 `AI_NEW_API_UNAVAILABLE`)。验证:后端 SSE 单测 14/14+前端 107/107(含重连去重用例)、活体 e2e ai-generation+accept 连跑 3 轮 6/6、重连幂等方法 A 活体证;残留=本机 LLM 6.8-19s 未自然>30s、慢路径靠「240s 数学覆盖上游 180s+单测+方法 A 重连活体证」三重保证。**通则(避免重踩"后端比上游先关门")**:SSE 长任务死线须 ≥ 上游 TOTAL_TIMEOUT;AI 流重连必须以 SSE id 幂等去重(后端 seq0 全量回放、无 lastEventId)。详见 memory muse-ai-generation-sse-timeout-bug。 diff --git a/muse-cloud/muse-module-content/.agent b/muse-cloud/muse-module-content/.agent index 316e313c..4f0d844f 100644 --- a/muse-cloud/muse-module-content/.agent +++ b/muse-cloud/muse-module-content/.agent @@ -7,4 +7,5 @@ - **out-of-scope**:知识入库(归 knowledge BC)、AI 生成(归 ai BC)。 - **现状**:只读评估 82%;后端齐全,契约高度一致。 - **2026-06-17 验收债**:批1 已补 work/chapter/block 生命周期 11 op 真实 PG 证据;批20-28 已补 planning candidate 5 + meta-projection 3 + style-check 2 + chapter-parse-result 决策 2 + parse-job 生命周期/创建 5 + import-task 2 + export-task 4 + 管理端任务列表 2 + mergeBlockSuggestion 1,均真实 PG 绿且证据已人工批准并翻 completed。批26 `P1rContentExportTaskCompletedApprovalIT` 补 export-task 簇 4 op(getExportTask/exportWork/createExportTask/downloadExportPackage)并修正 `ContentExportServiceImpl` public `exportWork/createExportTask` 事务边界,IT 已用 command_log 0 残留断言验证预占命令回滚。批27 扩展 `P1rContentAdminReadCompletedApprovalIT` 补 adminListImportTasks/adminListExportTasks:真实 `/admin-api` + method security + 租户行拦截器,读 `muse_content_import_task`/`muse_content_export_task` 真实行,覆盖分页/筛选/workTitle 回填/跨租户不泄露/RBAC/API version/no-write,并补齐老 harness 专属 `_test` 库自动创建。批28 扩展 `P1rContentParseJobLifecycleCompletedApprovalIT` 补 createParseJob 守卫/fail-closed/事务回滚证据,并修正 `P1rContentMergeSuggestionIT` 老 harness 专属 `_test` 库自动创建后复验 mergeBlockSuggestion 真实写 Canonical 正文链路。content/account/market needs_verification 验收债均已清零;coverage JSON completed 已按人工批准翻转,且 2026-06-19 已补 `testFiles` 证据锚点门禁。 +- **2.0.0 S3 market 摘装配影响(2026-07-08)**:Content 继续保留 `market-api` 契约依赖;默认单体无 market-server 时,`MarketHandoffTokenApi` fallback 让 market 来源资产使用/交接 verify fail-closed、consume 不核销,避免客户端伪造 handoff。Content 自身作品/正文/导入导出/采纳链路不因 market dormant 变化;恢复市场来源使用验收时走 `muse-server -Pmarket-assembled`。本轮 real-PG 修正 `P1rContentChapterParseResultDecisionCompletedApprovalIT` 与 `P1rContentParseJobLifecycleCompletedApprovalIT` 测试上下文:导入解析/章节确认链路需要同时装配 `ContentImportParseServiceImpl`、`ContentEventPublishOutboxServiceImpl`、`ContentBlockRevisionServiceImpl` 与 `MuseContentEventsProperties`,避免测试上下文落后于内容事件发布实现。 - **关键风险 / TODO**:Content 验收债已补齐真实 PG 证据;外部 owner(FileService/解析投影等)未接入的路径仍按合同 fail-closed,需要配置真实外部依赖或 owner 服务后才能从 fail-closed 升级为完整成功路径。**2026-06-25:AI suggestion 采纳已从 fail-closed 升级为真成功(ADR-020 方案 A,commit a16c596)**——此前真生成候选 `authorization_snapshot_id=null` 恒被 `mergeBlockSuggestion` 拒(1041001001),根因 authz 列 BIGINT 存不下字符串 runtime envelope `rpe-local-uuid`;V30 把 `muse_content_block_source_attribution`+`muse_ai_suggestion` authz 列 BIGINT→VARCHAR(128)、放开 `ContentSourceServiceImpl` 两道数值门(删 requireNumericAuthorizationSnapshot/parseAuthorizationSnapshotId/parseLongQuietly,字符串直落、外部 owner 非数值编号不再丢)、`BlockSourceAttributionDO.authorizationSnapshotId` 改 String。活体 merge code=0+revision 自增、IT `P1rContentMergeGeneratedSuggestion` 假绿(注入数值 9001)转真绿。详见总账 2026-06-25 / ADR-020。**2026-06-27:E1 采纳归档 + 旧知识草稿失效切片已补**——`mergeBlockSuggestion` 写 Canonical/来源归因后必须让 AI owner 归档 accepted decision;AI owner 不可用则整笔回滚,避免正文已写但 suggestion 仍 pending。Content 正文变更后经 Knowledge owner API 将关联 pending draft 标为 `conflicted/needs_recheck`,通知失败不回滚 Canonical 主写但 confirm 端 fail-closed。P1r 真 PG `P1rContentMergeSuggestionIT` 5/0F/0E/0S 已校验 suggestion `accepted`、decision archive、AI command/audit、Knowledge draft decision 与幂等 replay;`P1rContentMergeGeneratedSuggestionIT` 1/0F/0E/0S 回归生成候选采纳。**2026-06-27:E1 工作台入口 + 版本历史已补**——`saveBlock`/`mergeBlockSuggestion` 写 `muse_content_block_revision_snapshot`,`GET /app-api/muse/works/{workId}/blocks/{blockId}/revisions` 只读返回 Canonical revision 快照,Studio 工作台“历史”Tab 展示;工作台知识/导入/导出/记录最小入口已接。P1r 真 PG `P1rContentCoreCompletedApprovalIT` 13/0F/0E/0S 校验版本历史、owner/tenant 守卫和纯查询不写;前端 `tsc`/lint/Vitest 12/12 绿;MSW-off Playwright `accept-suggestion.spec.ts` 2/0F/0E 验证真 New-API suggestionId=79 采纳后 revision 160→161、AI owner accepted decision archive 非空。**2026-06-27:E7 Meta 动态字段校验已从 fail-closed 升级为真成功**——`RealContentMetaFacade.validateDynamicFields` 读取 Meta active projection 校验 required/type/enum/range/regex/deprecated/unknown field 并返回 routeSuggestions,`P1rContentMetaProjectionClusterCompletedApprovalIT` 4/0F/0E/0S 覆盖 no-schema fail-closed 与 active projection 成功规则命中且 0 Content 写。**2026-06-27:RC 后本地文本导入初始化正文已补**——`CreateImportTaskReqVO.contentText` 支持 txt/markdown inline 模式,后端保守拆章并同事务写 import task/chapter/block/source attribution/outbox/block revision snapshot/work 统计;Studio 导入弹窗可选 `.txt/.md` 或粘贴正文。验证:Content import/controller 单测 22/0F/0E、契约/覆盖门 12/0F/0E、Studio hook 9/0F/0E、tsc/目标 lint 绿;真实 PG `_test` 库 `P1rContentImportTaskCompletedApprovalIT` 4/0F/0E/0S 已绿(需在 surefire forked JVM 清 `socksProxyHost/socksProxyPort` 并透传 `p1r.flyway.*`,此前 JDBC EOF 是代理残留假红)。**2026-06-27:RC 后作品导出范围选择与凭证下载 UI 已补**——Studio 导出弹窗支持 TXT/DOCX/EPUB、全部/指定章节、元数据/规划开关;hook 接 `createExportTask/getExportTask/downloads/{credentialId}`,`api.download` 解析 octet-stream 与 JSON 业务错误。验证:Studio `tsc -b --force`、目标 Vitest 12/0F/0E、目标 ESLint 0 errors;2026-06-28 已补 `export-download.spec.ts` MSW-off 真后端 FileService 下载 e2e 1/0F/0E,FileApi 不可用仍按后端合同 fail-closed。**2026-06-27:RC 后 Block 版本历史只读 diff 已补**——Studio 历史面板支持任意两版基准/目标选择,按段落/句子做 LCS diff 并展示新增/删除/不变统计;只读消费版本历史 API,不新增恢复/回滚写入口。验证:目标 Vitest 2/0F/0E、`tsc`、目标 ESLint 绿。**2026-06-28:RC 后完整导入向导已补**——Infra 上传返回稳定 path,Content 外部导入任务只接受 infra path 并用 FileApi 真字节扫描;AI owner 持久化 parse job 与章节 Shadow;Knowledge owner 在章节确认时只产 pending Draft,不写 Local KB Canonical;Studio 向导串起上传扫描→解析→章节确认。验证见总账 2026-06-28,含浏览器 MSW-off 导入全链 e2e。2026-06-28 已补 `export-download.spec.ts` MSW-off 真后端 FileService 下载 e2e 1/0F/0E。仍缺:版本恢复/高级回滚。 diff --git a/muse-cloud/muse-module-knowledge/.agent b/muse-cloud/muse-module-knowledge/.agent index eb2f9166..44526459 100644 --- a/muse-cloud/muse-module-knowledge/.agent +++ b/muse-cloud/muse-module-knowledge/.agent @@ -6,5 +6,6 @@ - **边界(不可违反)**:**进 Local KB 唯一入口=用户显式确认草稿或手动修正**;全书解析章节确认只产/推 Draft,不写正式知识;来源优先级 Local KB > 用户绑定 > 市场/全局;1.0 运行时仍有 RAGFlow GraphRAG 历史链路,2.0 单人版迁移目标为 Dify Datasets(不可用时 Unavailable 优雅降级);守 BC 边界不碰他域 `.dal`([bc-boundaries](../../.agents/rules/bc-boundaries.md))。 - **out-of-scope**:正文写入(归 content)。 - **现状**:只读评估 78%;后端最完整模块之一(67 端点对齐契约),RAGFlow 真 HTTP + SSRF 防护;installed-KB 删除记录回显 bug 已修(2026-06-19:`MuseKnowledgeBindingMapperTest` + `MuseInstalledKnowledgeBaseServiceTest` 10/10,删除时写 `deleted=true` 且列表排除 `binding_status=deleted`);impact preview 已按 `muse_knowledge_source_binding_projection.owner_user_id` 输出 installed/source-binding owner 审计计数(2026-06-19:`MuseKnowledgeBaseServiceTest` + `MuseKnowledgeSourceBindingProjectionMapperTest` 30/30);source binding projection 回填闭环已修(2026-06-19:绑定确认为权威回填点写读模型行、unbind 按 bindingId 作用域撤销不误伤同源跨作品投影、唯一键补 work_id 修跨作品复用误判冲突〔V24〕;`MuseKnowledgeBindingServiceTest` + `MuseKnowledgeSourceBindingProjectionMapperTest` 11/11、整套件 214/214 绿);**读回端到端坐实 + unbind 真软删修复(2026-06-19)**:嵌入式 DB(H2)往返证 bind 写投影→`selectActiveByWorkId` 读回该来源、unbind 后读回消失、同源他作品投影存活;并修 unbind 软删真 bug——`deleted` 是 `@TableLogic`,实体 `setDeleted(true)` 被 MP 普通 update 剥离成空操作,改 `setSql("deleted = true")` 才真软删(`MuseKnowledgeSourceBindingProjectionRoundTripTest` 2/2、整套件 216/216 绿;新增 knowledge 模块首套嵌入式 DB 测试基建)。**GET bindings 读端点已补(2026-06-19)**:`AppMuseKnowledgeBindingController` 加 `GET /muse/works/{workId}/knowledge-bindings`→`listKnowledgeBindings`(requireWorkOwner 防 IDOR + selectActiveByWorkId 读回投影,id→String;happy-path + 越权 fail-closed 单测,整套件 218/218 绿);**bindings 读回后端腿(写→读→端点)已闭环,仅余 FE hook**。**FE 读回面板已加(2026-06-19)**:studio `useKnowledgeBindings` hook + `KnowledgeBindingsPanel`(镜像 DraftPanel)接入 `KnowledgePage`,契约+组件测试经 vitest 验证(全 studio 50/50、tsc 干净);live playwright e2e 需全栈 app(env 受限)未跑。bindings 写→读→端点→FE 读回展示链(除 live e2e)全通。**live e2e 已写 ready 未跑(2026-06-19)**:`muse-studio/e2e/knowledge-bindings.spec.ts`(镜像 graph spec,`playwright --list` 编译有效);活体受阻=远端 PG 宿主 100.64.0.8 离线(不在 tailnet)、无本地 PG 兜底,PG 宿主恢复后按 [.agents/knowledge §六] 起栈可跑。 **文本内容安全扫描已实现 + 摄入 scanning 关卡打通(2026-06-23,commit ec7637d)**:`KnowledgeContentScanService`(同步纯文本字符扫描——L1 非法 UTF-8/null/危险 C0-C1 控制符/Bidi 覆盖/体量硬阻断,L2 NFC+去零宽+换行规整,L3 prompt 注入软标记不阻断;特殊字符判定全用十六进制码点避免源码字面不可见字符),`KnowledgeFileFacade.materialize` 接入(passed→materialized+规整内容入 RAG、blocked→隔离带 reasonCode、扫描异常→fail-closed),替换原恒 `scanBlocked` 占位;`MaterializedFile.scanBlocked` 加 reasonCode/message(连带改 P1r live IT 构造签名)。单测 ScanService 9 + Facade 接线 3 + DocumentService 17 + P-B RetrievalApiImpl 11 全绿。**端到端真验(48080 + muse_slice_live + RAGFlow/New-API env 配齐)**:上传纯文本 document 4 → `scan_status=passed` → 处理链推进 → RAGFlow dataset(5950e9ee)+document(595cb63e)创建成功,坐实 scan 关卡 + muse→RAGFlow 集成贯通(对比 document 2 旧 jar 卡 SCAN_SERVICE_UNAVAILABLE、document 3 RAGFlow 未配 CONFIG_MISSING)。**E3 studio 与检索闭环已完成(2026-06-27)**:新增主动检索 App 端点与 `KnowledgeRetrievalPanel`,no_dataset 显式治理提示,自建 KB 上传真实后端读回,market KB handoff 正负路真 e2e;AI 生成 e2e 反查 `source_summary.contextAssembly` 证实 Knowledge Source 被真实消费。fresh 证据:knowledge targeted 25/0F/0E/0S,契约/迁移门 21/0F/0E/0S,studio Vitest 19/0F/0E、ESLint/tsc 通过,MSW-off Playwright knowledge 6/0F/0E + ai-generation 1/0F/0E。 -- **2.0.0 S1 Dify Datasets live 契约(2026-07-07)**:Dify 1.15.0 已创建工作区级 Datasets API key(`dataset-` 类型,非 app key、非单 dataset `ds-` key),并通过 `P1rDifyDatasetsContractLiveIT` 真跑 create dataset / upload file / indexing-status poll / retrieve。验收 dataset `cce33d41-c738-42d2-97b6-2d08a69965f2` 保留在 Dify 作为证据,`indexingStatus=completed`、retrieval `score=0.7442479133605957`;默认未 opt-in 时 honest skipped。Dify 1.15 `/v1/datasets/{id}/retrieve` 的 `retrieval_model` 必须显式包含 `search_method=semantic_search` 与 `reranking_enable=false`,只传 `top_k/score_threshold_enabled` 会 400。Dify 官方 compose 在本机部署必须用 `STORAGE_TYPE=local` + `STORAGE_LOCAL_PATH=storage`,否则 upload 成功后 worker 取文件会报 `File not found`。 +- **2.0.0 S1 Dify Datasets live 契约(2026-07-07)**:Dify 1.15.0 已创建工作区级 Datasets API key(`dataset-` 类型,非 app key、非单 dataset `ds-` key),并通过 `P1rDifyDatasetsContractLiveIT` 真跑 create dataset / upload file / indexing-status poll / retrieve。验收 dataset `cce33d41-c738-42d2-97b6-2d08a69965f2` 保留在 Dify 作为证据,`indexingStatus=completed`、retrieval `score=0.7442479133605957`;默认未 opt-in 时 honest skipped。Dify 1.15 `/v1/datasets/{id}/retrieve` 的 `retrieval_model` 必须显式包含 `search_method=semantic_search` 与 `reranking_enable=false`,只传 `top_k/score_threshold_enabled` 会 400。Dify 官方 compose 在本机部署必须用 `STORAGE_TYPE=local` + `STORAGE_LOCAL_PATH=storage`,否则 upload 成功后 worker 取文件会报 `File not found`。工作区级 key 是管理钥匙,不是生产单 dataset 设计;S6 生产拓扑至少分全局公共 dataset 与作品独立 dataset,验收 dataset 只作证据。 +- **2.0.0 S3 market 摘装配影响(2026-07-08)**:默认单体无 `market-server`,knowledge 通过 `MarketHandoffTokenApi` / `MarketAssetSourceApi` / `MarketAssetForkApi` 只拿到 muse-server fallback:handoff 不可用、asset source `notFound()`、fork 写回 `false`。market_kb binding 既有 fail-closed 测试继续覆盖 notFound / fork-not-ready;installed knowledge bases 在无 projection 时返回空页,本轮补 `MuseInstalledKnowledgeBaseServiceTest` 空结果断言。2026-07-08 裁决:publish-prechecks / publish-snapshots / publish-readiness 保留 Knowledge 本域预检/快照能力,允许写本域 readiness/snapshot/command 记录,但不得产生 market 资产、安装、授权事实;前端发布入口由 S5 删除。 - **关键风险 / TODO**:前端图谱/发布深面仍薄;Task4 剩余 owner 缺口已收窄为 **document owner count / export_task_owner** 仍不能伪造,需后续 owner 事实或外部服务接入;已物化 KB 的召回触达/检索停用已在 E4 闭环。**2026-06-27:E4 market KB 召回触达已补并真验**:Knowledge 新增 `KnowledgeMarketSourceStatusChangedConsumer` 消费 Market `MarketAssetSourceStatusChangedEvent`,将本地 installed_ref KB 的 source binding projection 更新为 `recalled/blocked`,检索来源状态门返回 `omittedSources` 而不再送入 RAGFlow;MSW-off Playwright `market-kb-recall.spec.ts` 1/0F/0E,后端单测 `MuseKnowledgeSourceEventServiceTest`+`KnowledgeMarketSourceStatusChangedConsumerTest` 9/0F/0E。副本资源回收/更细补偿仍后置。**2026-06-27:E1 旧知识草稿失效 owner API 已补**:`MuseKnowledgeDraftInvalidationApi` 由 knowledge-api 定义、knowledge-server owner 实现;Content 正文变更只传 work/chapter/block/revision/commandId,不跨 BC 传正文。Knowledge 将匹配的 pending draft 更新为 `conflicted` + `source_action_policy=needs_recheck`,并写 `muse_knowledge_draft_decision(decision_type=recheck)` 作为审计归档;invalid command 跳过并 warn,Content 通知失败不回滚 Canonical 主写,confirm 端仍按 `needs_recheck` fail-closed。验证:`MuseKnowledgeDraftInvalidationServiceTest` 3/0F/0E;`P1rContentMergeSuggestionIT` real-PG 5/0F/0E/0S 穿透验证采纳后旧 draft 失效与 replay 不重复。**2026-06-28:导入章节确认 Draft owner API 已补**:`MuseKnowledgeDraftCreationApi` 由 knowledge-api 定义、knowledge-server owner 实现;Content/AI 只传章节 Shadow 结果与 sourceRef,Knowledge 负责创建 pending draft 与幂等 command,不写 Local KB Canonical,仍需用户后续显式确认草稿才进入正式知识。验证:`MuseKnowledgeDraftCreationApiImplTest` 与完整导入链路 targeted 后端测试通过。**parse 轮询 worker 已补齐并端到端真验(2026-06-23,commit 635045c)**:`MuseKnowledgeParseStatusPollWorker`(@Scheduled + 开关 `muse.knowledge.parse-poll-worker.enabled`,跨租户捞 parsing 任务轮询 RAGFlow 文档状态→run=done 推 version processingStatus=searchable、failed 标败、processing 保持、RAGFlow 不可达/异常不误判)+ `ProcessingTaskService.markRagflowParseCompleted/markRagflowParsePolling` + `Mapper.selectParsingForPoll`;worker 接线 7 测试 + 回归 40 全绿;启用开关后 document 4 自动 parsing→searchable(isSearchable=true),摄入链 **scan→RAGFlow→worker→searchable** 全程贯通(此前"RAGFlow backend 故障"系本机 HTTP 代理误诊,RAGFlow 实始终正常,真因=muse 缺 parse 轮询 worker)。 diff --git a/muse-cloud/muse-module-knowledge/muse-module-knowledge-server/src/test/java/cn/iocoder/muse/module/knowledge/application/muse/MuseInstalledKnowledgeBaseServiceTest.java b/muse-cloud/muse-module-knowledge/muse-module-knowledge-server/src/test/java/cn/iocoder/muse/module/knowledge/application/muse/MuseInstalledKnowledgeBaseServiceTest.java index e69d1aae..38490244 100644 --- a/muse-cloud/muse-module-knowledge/muse-module-knowledge-server/src/test/java/cn/iocoder/muse/module/knowledge/application/muse/MuseInstalledKnowledgeBaseServiceTest.java +++ b/muse-cloud/muse-module-knowledge/muse-module-knowledge-server/src/test/java/cn/iocoder/muse/module/knowledge/application/muse/MuseInstalledKnowledgeBaseServiceTest.java @@ -100,6 +100,21 @@ class MuseInstalledKnowledgeBaseServiceTest extends BaseMockitoUnitTest { assertEquals("6001", page.getList().getFirst().getInstallId()); } + @Test + void should_returnEmptyInstalledKnowledgeBasesWhenMarketIsDetachedAndNoProjectionExists() { + when(bindingMapper.selectInstalledPage(any(), org.mockito.ArgumentMatchers.eq(2001L), any())) + .thenReturn(new PageResult<>(List.of(), 0L)); + + AppInstalledKnowledgeBaseVO.PageResultVO page = + installedService.listInstalledKnowledgeBases(2001L, 1, 20, null); + + assertEquals(1, page.getPageNo()); + assertEquals(20, page.getPageSize()); + assertEquals(0L, page.getTotal()); + assertEquals(0, page.getList().size()); + verify(workOwnerFacade, never()).requireWorkOwner(any(), any()); + } + @Test void should_rejectInstalledListIllegalStatusOrPageSize() { assertEquals(KNOWLEDGE_INVALID_PAGE_PARAM.getCode(), assertThrows(ServiceException.class, diff --git a/muse-cloud/muse-module-market/.agent b/muse-cloud/muse-module-market/.agent index 1bdc1353..4acc2054 100644 --- a/muse-cloud/muse-module-market/.agent +++ b/muse-cloud/muse-module-market/.agent @@ -7,4 +7,5 @@ - **out-of-scope**:完整电商支付结算 DRM(项目非目标);模板化/参考写入/AI 上下文受 Feature Gate 默认关闭。 - **现状**:只读评估 82%;核心不变式已被代码强制。**生产者飞轮已端到端打通并活体证(2026-06-15)**:发布草稿→检查→提交→上架状态可视化→申诉(提交/补充材料/撤回)→申诉态回显(`MarketPublish`+`market-publish.spec.ts` 8/8,见[总账](../../docs/mvp/进度总账.md) §五C)。本会话新增 3 处 app-api 契约:`PublishRecordItem.marketAssetId`(物化资产 id,解申诉 assetId 映射 gap)、`GET /marketplace/appeals`(我的申诉列表)、记录 `appealStatus` 回填(`selectLatestByAssetIdAndUser`)。**2026-06-16 补真实 PG 验收证据**:`P1rMarketPublishProducerCompletedApprovalIT` 覆盖发布生产侧 5 op,真实 PG 3/3 绿;`P1rMarketAdminAppealCompletedApprovalIT` 覆盖管理端申诉 3 op(adminListAppeals/adminGetAppeal/adminResolveAppeal),真实 PG 6/6 绿;`P1rMarketAdminPublishReviewCompletedApprovalIT` 覆盖管理端发布审核 3 op(adminListPublishRequests/adminApprovePublishRequest/adminRejectPublishRequest),真实 PG 6/6 绿;`P1rMarketProducerAppealCompletedApprovalIT` 覆盖生产者申诉写路 3 op(submitAppeal/supplementAppeal/withdrawAppeal,app 入口 + owner 隔离 + 脱敏 + 状态 CAS + 冲突回滚),真实 PG 7/7 绿(独立复跑)。**2026-06-17 续补**:`P1rMarketAdminAssetReadsCompletedApprovalIT` 覆盖管理端资产治理读 2 op(adminListMarketAssets/adminGetMarketAsset:tenant 隔离 + assetType/listingStatus(not_listed↔draft 归一)/publisherId 过滤、详情资产快照 + 授权/安装/绑定/受影响任务计数全 0(关联表空)、缺资产/跨租户→MARKET_ASSET_NOT_EXISTS、API version/RBAC(muse:market:asset:query)fail-closed、纯读 no-write),真实 PG 2/2 绿(独立复跑)。`P1rMarketGovernanceWriteCompletedApprovalIT` 覆盖管理端资产治理写 3 op(adminPreviewGovernanceImpact/adminDelistAsset/adminRecallAsset:影响预览→凭预览下架 listed→delisted 的 expectedStatus CAS→凭预览召回 listed→recalled + 目标 owner 传播 fail-closed 如实记录 recalled/recorded/TARGET_OWNER_UNAVAILABLE;命令幂等、合同枚举 @Pattern→BAD_REQUEST、缺预览/CAS 阻断/缺资产/API version/RBAC fail-closed 0 写),真实 PG 4/4 绿(独立复跑)。`P1rMarketplaceDiscoveryReadsCompletedApprovalIT` 覆盖 marketplace 发现读 3 op(listMarketplaceAssets/listMarketplaceRecommendations/getGovernanceImpact:App 可见性隔离 listed 公开/非公开仅 publisher 自见 + assetType 过滤、fallback 推荐 + 理由、治理影响读 publisher/受影响者门禁 非属主无关联→MARKET_RESOURCE_FORBIDDEN、缺资产/API version fail-closed、纯读 no-write),真实 PG 4/4 绿(独立复跑)。`P1rMarketLicenseInstallCompletedApprovalIT` 覆盖采纳链 2 op(purchaseAsset/installMarketplaceAsset:购买写授权快照+购买事实+命令并跨 BC 同步 purchase/license 两条 Account 投影、安装要求已购授权+仅 agent/kb 可安装+写 installed 记录、命令幂等、未上架/缺资产/授权类型不符/无授权/作品不可安装/API version fail-closed 0 写),真实 PG 3/3 绿(独立复跑);证据已人工批准并翻 completed。`P1rMarketHandoffClusterCompletedApprovalIT` 覆盖 handoff 簇 4 op(createBindPrecheck/createMarketplaceHandoff/getHandoffStatus/cancelHandoff:已购 active 授权快照→来源授权摘要(handoffReady)→一次性 handoff token(明文不入库、只存 sha256 hash,事件快照不含明文)→只读回显 pending→expectedStatus CAS 取消(pending→cancelled)的端到端链;bind/handoff 回放幂等、cancel 终态后再取消被取消性守卫拒绝且不双写;非法 version/缺资产/无授权(授权≠可达)/目标 owner 白名单外(LocalMarketTargetOwnerFacade 本地目标页 fail-closed)/跨属主 token 读取与取消(属主隔离 RESOURCE_FORBIDDEN)/取消 CAS 期望态不匹配(reserveCommand MANDATORY 预占随事务回滚)失败路径 0 写),真实 PG 3/3 绿(独立复跑);至此 market needs_verification 28 op 全补 dedicated 真实 PG 证据,证据已人工批准并翻 completed。 - **2.0.0 S2 摘装配门禁机制(2026-07-07)**:`market-detached-test-tree` 只在显式 `-Dmuse.market.detached.test-tree=true` 时剥离 14 个 market/account 混合装配测试与 `P1rMarketRealApiGateTest`,默认本地构建仍保留全量测试树;`market-assembled` 可恢复全量回编译。覆盖门新增 market-only `dormant` 口径但本步不改 coverage JSON,S3 拨开关时 market 32 条可临时 dormant,summary completed 应为 `242-marketDormant`。dry-run 已在临时 worktree 验证:注释 market-server 依赖并将 market 32 条置 dormant 后,local 门禁 56/0F/0E/0S;主工作树 local 仍为 62/0F/0E/0S。 +- **2.0.0 S3 默认摘装配(2026-07-07)**:`muse-server` 默认不再装配 `muse-module-market-server`,只保留 `muse-module-market-api` 契约;market 32 条 coverage operation 全部置 `dormant`,`summary.completedOperations=210`,不删 operation/testFiles/sourceFiles。`market-assembled` profile 恢复 market-server 依赖与全测试树回编译,作为多用户/市场恢复路径。`MonolithFacadeFallbackAutoConfiguration` 为 handoff/source/fork 三个 market API 提供 fail-closed fallback,真实 Bean 存在时优先。验证:`P1rMarketDetachedAssemblyGateTest`+`P1rApiCoverageReportTest` 13/0F/0E/0S,`-Pmarket-assembled -DskipTests test` 47 模块 reactor BUILD SUCCESS。S3 后不要把 dormant 误解为删除契约;OpenAPI 与 market 代码仍保留。 - **关键风险 / TODO**:① **资产物化只在 admin 审核通过(`markListed`)发生**——submit 阶段 `publish_request.asset_id=draft.id` 占位,故未上架资产无 `muse_market_asset` 行,申诉(`requireAsset`)仅对已物化(曾上架)资产可达;② 申诉证据材料附件上传(`attachmentIds` 当前前端置空);③ 上架后下架/召回的生产者自助入口;④ **market 验收债已清零(2026-06-17)**:批15-19 补 14 op(资产治理读 2 + 治理写 3 + marketplace 发现读 3 + 采纳链 purchase/install 2 + handoff 簇 4)叠加本战役前 14 op(发布生产 5 + 管理端申诉 3 + 发布审核 3 + 生产者申诉 3)= market needs_verification 28 op 全有 dedicated 真实 PG 证据;**台账 completed flip 与覆盖门禁批准集合已按人工批准同步,2026-06-19 已补 `testFiles` 证据锚点门禁**。⑤ **2026-06-25:install 用户可见假绿修复(isAcquired/isInstalled enrich,commit 37e7a8d)**——`MarketAssetQueryServiceImpl` `toCard`/`userActions` 此前把 isAcquired/isInstalled/canInstall 裸硬编 false(install 后端真实已验、但读模型不回填用户真实态),致 studio 安装按钮(`assetType!=='work' && isAcquired && !isInstalled`)真后端永不渲染、批17 IT 把 false 断言为正确(假绿固化)。修=仿 `isFavorite` enrich isAcquired(active 授权)/isInstalled(installation)/isInstallable,**与 install 写端口 `MarketInstallServiceImpl` 严格同源**(`selectActiveByOwnerAndAsset` + `INSTALLABLE_ASSET_TYPES`,避免"显示可装但点了 MARKET_LICENSE_NOT_EXISTS"新假绿);批量加载避放大 isFavorite 既有 N+1;批17 IT 改三态;前端补 onError;UI e2e `market-install.spec` 连跑 2 次。活体真验 purchase→isAcquired=T→install→isInstalled=T(详总账 2026-06-25 / memory muse-market-install-isacquired-enrich)。**教训:读模型 enrich 必须与写端口校验同源,否则显示态与可操作态背离=新假绿。** ⑥ **2026-06-27:E4 agent 物化 + KB 召回触达已补并真验**:Market 召回 `recallAsset` 写 `source_status_event` 后发布 `MarketAssetSourceStatusChangedEvent`,目标 owner 自治消费;agent handoff 只签 market token/assetId,不把发布者 agent id 交给客户端。Studio 真 e2e 覆盖 handoff-agent、market-kb-recall、market-asset-detail、governance-impact、handoff-precheck 6/0F/0E。⑦ **2026-06-27:E6 restore 分支真 PG IT 已补**:`P1rMarketAdminAppealCompletedApprovalIT` 扩到 7/0F/0E/0S,覆盖申诉 `restored` 分支凭治理预览恢复资产、preview consumed、asset relisted、restore governance action、command/event 幂等与快照 `impactPreviewId`。⑧ **2026-06-27 RC 后补治理/撤权申诉独立入口**:Studio 新增 `/market/appeals`,侧边栏“治理”和市场页可直达,消费 `GET /marketplace/appeals`、补充材料、撤回申诉真实 hook;该入口只处理发布者/使用者面对下架/召回/撤权结果后的申诉,不暴露管理员执行撤权/召回命令。验证:`useMarketPublish.test.tsx` 2/0F/0E、`MarketAppealCenter.test.tsx` 2/0F/0E、组合目标 Vitest 4 files / 11 tests passed、`tsc`/目标 ESLint 通过。仍缺:更完整生产者发布深面、申诉附件上传、KB 副本资源回收、下架/撤权更细补偿。 diff --git a/muse-cloud/muse-module-member/.agent b/muse-cloud/muse-module-member/.agent index c6f4c062..647b1f34 100644 --- a/muse-cloud/muse-module-member/.agent +++ b/muse-cloud/muse-module-member/.agent @@ -9,4 +9,5 @@ - **E5 member 写侧闭环(2026-06-27)**:AI 生成入口通过 `MuseAccountUsageApi` 预占配额,成功后写 usage record 并累加 quota used,失败终态释放预占;`AccountQuotaRequestWorker` 消费 queued quota request,短事务 claim/terminal、New-API 管理口调用在事务外执行,成功写 completed、失败写 failed + integration unavailable;`AccountAttributionWorker` 消费 queued attribution job,按 usage correlation 写 attribution item 并终态化。`RealNewApiAccountFacade` 已接 `/api/user/search`、`/api/user/`、`/api/user/{id}` 管理口,默认配置 fail-closed,显式 live 验证通过。fresh 证据:targeted 单测 120/0F/0E;account real-PG 16/0F/0E/0S;New-API 管理口 live 1/0F/0E/0S;AI runtime live 3/0F/0E/0S 反查 usage/quota/attribution 全落库。边界:workers 默认 disabled,生产启用需显式配置。 - **2026-06-27 RC 后补安全事件处理入口**:Studio 个人中心安全事件从单一“确认”扩为契约内 `acknowledged/password_changed/session_revoked/false_positive` 四类处理动作,写 `POST /account/security-events/{eventId}/acknowledge` 时带 commandId 和 note,成功后展示后端 `riskSummary/nextSteps` 并刷新列表。验证:Studio `useAccount.test.tsx` 5/0F/0E,组合目标 Vitest 4 files / 11 tests passed,`tsc -b --force` 与目标 ESLint 通过。边界:这不是完整改密/2FA/session service,会话吊销真实联动仍后置;偏好通知仍无 app 写契约,不伪造。 - **2.0.0 S2 market detached 测试树影响(2026-07-07)**:`P1rAccountAdminPurchaseRecordsCompletedApprovalIT` 与 `P1rAccountMarketRecordsCompletedApprovalIT` 依赖 market 单体装配投影,已随 `market-detached-test-tree` 显式 profile 一并剥离;默认构建不跳过,`market-assembled` 可恢复全量回编译。本步未改 Account 业务实现、coverage JSON 或 Account completed 口径,纯 Account gate 仍随 local 门禁运行。 +- **2.0.0 S3 market 默认摘装配影响(2026-07-07)**:`market-detached-test-tree` 已默认生效,Account 侧两组 market/account 混合 IT 默认不再编译/运行;Account owner 自身的 profile、usage、quota、security、New-API binding 能力不因 market dormant 变化。恢复市场相关账户记录验收时显式使用 `muse-server -Pmarket-assembled`。 - **关键风险 / TODO**:account needs_verification 验收债**已清零**:末位 adminListPurchaseRecords 经 `P1rAccountAdminPurchaseRecordsCompletedApprovalIT`(跨模块 base-package=cn.iocoder.muse.module + import MarketAccountProjectionProvider)真实 PG 2/2 绿补证;批3(市场记录读)+ 批7-14 account needs_verification op 全部补真实 PG 证据、证据已人工批准并翻 completed。截至 2026-06-17,content/market/account 三域 needs_verification 验收债均已补齐真实 PG 证据,coverage JSON completed flip 与覆盖门禁批准集合已按人工批准同步,2026-06-19 已补 `testFiles` 证据锚点门禁。前端仍有深页未全覆盖真实后端。 diff --git a/muse-cloud/muse-server/pom.xml b/muse-cloud/muse-server/pom.xml index 3b087591..028e0121 100644 --- a/muse-cloud/muse-server/pom.xml +++ b/muse-cloud/muse-server/pom.xml @@ -69,11 +69,17 @@ muse-module-knowledge-server ${revision} + cn.iocoder.cloud - muse-module-market-server + muse-module-market-api ${revision} + + + + + @@ -178,14 +184,11 @@ market-detached-test-tree - - muse.market.detached.test-tree - true - + true - + org.apache.maven.plugins maven-compiler-plugin @@ -238,6 +241,13 @@ market-assembled + + + cn.iocoder.cloud + muse-module-market-server + ${revision} + + diff --git a/muse-cloud/muse-server/src/main/java/cn/iocoder/muse/server/framework/MonolithFacadeFallbackAutoConfiguration.java b/muse-cloud/muse-server/src/main/java/cn/iocoder/muse/server/framework/MonolithFacadeFallbackAutoConfiguration.java index a6a7e3c7..83ab6ca3 100644 --- a/muse-cloud/muse-server/src/main/java/cn/iocoder/muse/server/framework/MonolithFacadeFallbackAutoConfiguration.java +++ b/muse-cloud/muse-server/src/main/java/cn/iocoder/muse/server/framework/MonolithFacadeFallbackAutoConfiguration.java @@ -6,10 +6,22 @@ import cn.iocoder.muse.module.content.application.facade.ContentMetaFacade; import cn.iocoder.muse.module.content.application.facade.ContentParseJobFacade; import cn.iocoder.muse.module.content.application.facade.ContentPlanningCandidateFacade; import cn.iocoder.muse.module.content.application.facade.ContentStyleCheckFacade; +import cn.iocoder.muse.module.market.api.asset.MarketAssetForkApi; +import cn.iocoder.muse.module.market.api.asset.MarketAssetSourceApi; +import cn.iocoder.muse.module.market.api.asset.dto.MarketAssetSourceRespDTO; +import cn.iocoder.muse.module.market.api.handoff.MarketHandoffTokenApi; +import cn.iocoder.muse.module.market.api.handoff.dto.HandoffConsumeReqDTO; +import cn.iocoder.muse.module.market.api.handoff.dto.HandoffConsumeRespDTO; +import cn.iocoder.muse.module.market.api.handoff.dto.HandoffVerifyReqDTO; +import cn.iocoder.muse.module.market.api.handoff.dto.HandoffVerifyRespDTO; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; +import java.util.concurrent.atomic.AtomicBoolean; + /** * 单体装配 facade 兜底自动配置。 * @@ -27,6 +39,12 @@ import org.springframework.context.annotation.Bean; @AutoConfiguration public class MonolithFacadeFallbackAutoConfiguration { + private static final Logger log = LoggerFactory.getLogger(MonolithFacadeFallbackAutoConfiguration.class); + + private static final AtomicBoolean MARKET_HANDOFF_FALLBACK_WARNED = new AtomicBoolean(false); + private static final AtomicBoolean MARKET_ASSET_SOURCE_FALLBACK_WARNED = new AtomicBoolean(false); + private static final AtomicBoolean MARKET_ASSET_FORK_FALLBACK_WARNED = new AtomicBoolean(false); + @Bean @ConditionalOnMissingBean public ContentFileFacade contentFileFacadeFallback() { @@ -69,4 +87,56 @@ public class MonolithFacadeFallbackAutoConfiguration { }; } + @Bean + @ConditionalOnMissingBean + public MarketHandoffTokenApi marketHandoffTokenApiFallback() { + return new MarketHandoffTokenApi() { + @Override + public HandoffVerifyRespDTO verify(HandoffVerifyReqDTO request) { + warnMarketHandoffFallbackOnce("verify"); + // market 未装配的单人形态下,handoff token 一律视为不可兑现,避免跨域来源被误放行。 + return new HandoffVerifyRespDTO(false, "market_detached", null, null, null, null, null, null, + "MARKET_DETACHED"); + } + + @Override + public HandoffConsumeRespDTO consume(HandoffConsumeReqDTO request) { + warnMarketHandoffFallbackOnce("consume"); + // 未装配 market 时不能核销 token;返回 false 让 owner 侧保持 fail-closed。 + return new HandoffConsumeRespDTO(false, "market_detached"); + } + }; + } + + @Bean + @ConditionalOnMissingBean + public MarketAssetSourceApi marketAssetSourceApiFallback() { + return assetId -> { + if (MARKET_ASSET_SOURCE_FALLBACK_WARNED.compareAndSet(false, true)) { + log.warn("[marketAssetSourceApiFallback][market-server 未装配,资产来源读取按 notFound 兜底,assetId={}]", assetId); + } + // 单人版摘装配后没有 market 资产来源表可读,安装侧必须按来源不存在阻断绑定。 + return MarketAssetSourceRespDTO.notFound(); + }; + } + + @Bean + @ConditionalOnMissingBean + public MarketAssetForkApi marketAssetForkApiFallback() { + return request -> { + if (MARKET_ASSET_FORK_FALLBACK_WARNED.compareAndSet(false, true)) { + log.warn("[marketAssetForkApiFallback][market-server 未装配,公开副本状态写回按失败兜底,assetId={}]", + request == null ? null : request.assetId()); + } + // market 未装配时没有资产 tags 可写,返回 false 让 knowledge fork 流等待后续恢复路径补偿。 + return false; + }; + } + + private void warnMarketHandoffFallbackOnce(String operation) { + if (MARKET_HANDOFF_FALLBACK_WARNED.compareAndSet(false, true)) { + log.warn("[marketHandoffTokenApiFallback][market-server 未装配,handoff {} 按不可用兜底]", operation); + } + } + } diff --git a/muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rContentChapterParseResultDecisionCompletedApprovalIT.java b/muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rContentChapterParseResultDecisionCompletedApprovalIT.java index e0efbc55..2f322b04 100644 --- a/muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rContentChapterParseResultDecisionCompletedApprovalIT.java +++ b/muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rContentChapterParseResultDecisionCompletedApprovalIT.java @@ -15,12 +15,15 @@ import cn.iocoder.muse.framework.tenant.core.context.TenantContextHolder; import cn.iocoder.muse.framework.tenant.core.db.TenantDatabaseInterceptor; import cn.iocoder.muse.framework.web.config.MuseWebAutoConfiguration; import cn.iocoder.muse.module.content.application.ContentAuditServiceImpl; +import cn.iocoder.muse.module.content.application.ContentBlockRevisionServiceImpl; import cn.iocoder.muse.module.content.application.ContentCommandServiceImpl; +import cn.iocoder.muse.module.content.application.ContentEventPublishOutboxServiceImpl; import cn.iocoder.muse.module.content.application.ContentImportParseServiceImpl; import cn.iocoder.muse.module.content.application.facade.ContentFileFacade; import cn.iocoder.muse.module.content.application.facade.ContentKnowledgeDraftFacade; import cn.iocoder.muse.module.content.application.facade.ContentParseJobFacade; import cn.iocoder.muse.module.content.controller.app.AppContentImportParseController; +import cn.iocoder.muse.module.content.framework.config.MuseContentEventsProperties; import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor; @@ -635,12 +638,19 @@ class P1rContentChapterParseResultDecisionCompletedApprovalIT { @Import({ AppContentImportParseController.class, ContentImportParseServiceImpl.class, + ContentBlockRevisionServiceImpl.class, ContentCommandServiceImpl.class, ContentAuditServiceImpl.class, + ContentEventPublishOutboxServiceImpl.class, SpringUtil.class }) static class ChapterParseResultDecisionConfiguration { + @Bean + MuseContentEventsProperties museContentEventsProperties() { + return new MuseContentEventsProperties(); + } + /** * 三个外部 owner facade 均提供全 default(unavailable)兜底,等价生产 `@ConditionalOnMissingBean` * 兜底实现(AI 解析/知识草稿/FileService 未接入),用于复现真实 fail-closed 行为。 diff --git a/muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rContentParseJobLifecycleCompletedApprovalIT.java b/muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rContentParseJobLifecycleCompletedApprovalIT.java index 44551713..d6657791 100644 --- a/muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rContentParseJobLifecycleCompletedApprovalIT.java +++ b/muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rContentParseJobLifecycleCompletedApprovalIT.java @@ -15,12 +15,15 @@ import cn.iocoder.muse.framework.tenant.core.context.TenantContextHolder; import cn.iocoder.muse.framework.tenant.core.db.TenantDatabaseInterceptor; import cn.iocoder.muse.framework.web.config.MuseWebAutoConfiguration; import cn.iocoder.muse.module.content.application.ContentAuditServiceImpl; +import cn.iocoder.muse.module.content.application.ContentBlockRevisionServiceImpl; import cn.iocoder.muse.module.content.application.ContentCommandServiceImpl; +import cn.iocoder.muse.module.content.application.ContentEventPublishOutboxServiceImpl; import cn.iocoder.muse.module.content.application.ContentImportParseServiceImpl; import cn.iocoder.muse.module.content.application.facade.ContentFileFacade; import cn.iocoder.muse.module.content.application.facade.ContentKnowledgeDraftFacade; import cn.iocoder.muse.module.content.application.facade.ContentParseJobFacade; import cn.iocoder.muse.module.content.controller.app.AppContentImportParseController; +import cn.iocoder.muse.module.content.framework.config.MuseContentEventsProperties; import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor; @@ -780,12 +783,19 @@ class P1rContentParseJobLifecycleCompletedApprovalIT { @Import({ AppContentImportParseController.class, ContentImportParseServiceImpl.class, + ContentBlockRevisionServiceImpl.class, ContentCommandServiceImpl.class, ContentAuditServiceImpl.class, + ContentEventPublishOutboxServiceImpl.class, SpringUtil.class }) static class ParseJobLifecycleConfiguration { + @Bean + MuseContentEventsProperties museContentEventsProperties() { + return new MuseContentEventsProperties(); + } + /** * 三个外部 owner facade 均提供全 default(unavailable)兜底,等价生产 `@ConditionalOnMissingBean` * 兜底实现(AI 解析/知识草稿/FileService 未接入),用于复现真实 fail-closed 行为。 diff --git a/muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rMarketDetachedAssemblyGateTest.java b/muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rMarketDetachedAssemblyGateTest.java new file mode 100644 index 00000000..b6c5b8a5 --- /dev/null +++ b/muse-cloud/muse-server/src/test/java/cn/iocoder/muse/server/framework/api/P1rMarketDetachedAssemblyGateTest.java @@ -0,0 +1,162 @@ +package cn.iocoder.muse.server.framework.api; + +import cn.iocoder.muse.module.market.api.asset.MarketAssetForkApi; +import cn.iocoder.muse.module.market.api.asset.MarketAssetSourceApi; +import cn.iocoder.muse.module.market.api.asset.dto.MarketAssetForkRecordReqDTO; +import cn.iocoder.muse.module.market.api.asset.dto.MarketAssetSourceRespDTO; +import cn.iocoder.muse.module.market.api.handoff.MarketHandoffTokenApi; +import cn.iocoder.muse.module.market.api.handoff.dto.HandoffConsumeReqDTO; +import cn.iocoder.muse.module.market.api.handoff.dto.HandoffConsumeRespDTO; +import cn.iocoder.muse.module.market.api.handoff.dto.HandoffVerifyReqDTO; +import cn.iocoder.muse.module.market.api.handoff.dto.HandoffVerifyRespDTO; +import cn.iocoder.muse.server.framework.MonolithFacadeFallbackAutoConfiguration; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.regex.Pattern; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * 2.0.0 S3 market 摘装配门禁。 + * + *

本测试只证明默认单体形态已经不装配 market-server,market 跨域端口按 fail-closed 兜底, + * 以及显式 {@code market-assembled} profile 仍保留恢复路径;不重新证明 market 业务 API。

+ */ +class P1rMarketDetachedAssemblyGateTest { + + private static final String MUSE_SERVER_POM = "muse-cloud/muse-server/pom.xml"; + + private final ApplicationContextRunner fallbackContextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(MonolithFacadeFallbackAutoConfiguration.class)); + + private final WebApplicationContextRunner webContextRunner = new WebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(WebMvcAutoConfiguration.class)); + + @Test + void should_provide_fail_closed_market_facades_when_market_server_is_detached() { + fallbackContextRunner.run(context -> { + MarketHandoffTokenApi handoffTokenApi = context.getBean(MarketHandoffTokenApi.class); + HandoffVerifyRespDTO verify = handoffTokenApi.verify( + new HandoffVerifyReqDTO("token", "knowledge", "bind")); + HandoffConsumeRespDTO consume = handoffTokenApi.consume( + new HandoffConsumeReqDTO("token", "cmd", "1", "pending", 1001L, "binding-1")); + + assertFalse(verify.valid(), "market 未装配时 token verify 必须 fail-closed"); + assertEquals("MARKET_DETACHED", verify.blockedReason(), + "market 未装配时 verify 必须给出可追溯阻断原因"); + assertFalse(consume.consumed(), "market 未装配时 token consume 必须返回未核销"); + + MarketAssetSourceRespDTO source = context.getBean(MarketAssetSourceApi.class).getAssetSource(9001L); + assertFalse(source.exists(), "market 未装配时资产来源读取必须返回 notFound"); + assertFalse(context.getBean(MarketAssetForkApi.class).recordPublicFork( + new MarketAssetForkRecordReqDTO(9001L, "dataset-public", "ready", "v1")), + "market 未装配时公开副本写回必须返回 false"); + }); + } + + @Test + void should_keep_real_market_facades_preferred_when_market_is_assembled() { + fallbackContextRunner.withUserConfiguration(RealMarketFacadeConfiguration.class).run(context -> { + assertSame(context.getBean("realMarketHandoffTokenApi"), context.getBean(MarketHandoffTokenApi.class), + "真实 MarketHandoffTokenApi 存在时不能被 fallback 覆盖"); + assertSame(context.getBean("realMarketAssetSourceApi"), context.getBean(MarketAssetSourceApi.class), + "真实 MarketAssetSourceApi 存在时不能被 fallback 覆盖"); + assertSame(context.getBean("realMarketAssetForkApi"), context.getBean(MarketAssetForkApi.class), + "真实 MarketAssetForkApi 存在时不能被 fallback 覆盖"); + }); + } + + @Test + void should_keep_market_server_dependency_detached_by_default_and_restorable_by_profile() throws IOException { + String pom = Files.readString(findRepositoryRoot().resolve(MUSE_SERVER_POM)); + String defaultDependencies = stripXmlComments(pom.substring(0, pom.indexOf(""))); + String marketAssembledProfile = pom.substring(pom.indexOf("market-assembled")); + + assertTrue(defaultDependencies.contains("muse-module-market-api"), + "muse-server 默认必须保留 market-api 契约"); + assertFalse(defaultDependencies.contains("muse-module-market-server"), + "muse-server 默认不得装配 market-server"); + assertTrue(pom.contains("market-detached-test-tree") + && pom.contains("true"), + "S3 后 market-detached-test-tree 必须默认生效"); + assertTrue(marketAssembledProfile.contains("muse-module-market-server"), + "market-assembled profile 必须保留恢复 market-server 装配的路径"); + assertTrue(marketAssembledProfile.contains("combine.self=\"override\""), + "market-assembled profile 必须恢复被剥离的 market 测试树"); + } + + @Test + void should_return_404_for_market_route_surface_without_market_controllers() { + webContextRunner.run(context -> { + WebApplicationContext webApplicationContext = + (WebApplicationContext) context.getSourceApplicationContext(); + MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); + + mockMvc.perform(get("/app-api/market/probe")).andExpect(status().isNotFound()); + mockMvc.perform(get("/app-api/marketplace/assets")).andExpect(status().isNotFound()); + mockMvc.perform(get("/admin-api/muse/market/assets")).andExpect(status().isNotFound()); + }); + } + + private static String stripXmlComments(String value) { + return Pattern.compile("", Pattern.DOTALL).matcher(value).replaceAll(""); + } + + private static Path findRepositoryRoot() { + Path current = Path.of("").toAbsolutePath(); + for (Path candidate = current; candidate != null; candidate = candidate.getParent()) { + if (Files.exists(candidate.resolve(MUSE_SERVER_POM))) { + return candidate; + } + } + return current; + } + + @Configuration + static class RealMarketFacadeConfiguration { + + @Bean + MarketHandoffTokenApi realMarketHandoffTokenApi() { + return new MarketHandoffTokenApi() { + @Override + public HandoffVerifyRespDTO verify(HandoffVerifyReqDTO request) { + return new HandoffVerifyRespDTO(true, "pending", "knowledge", "bind", + "asset-1", 1001L, 2001L, null, null); + } + + @Override + public HandoffConsumeRespDTO consume(HandoffConsumeReqDTO request) { + return new HandoffConsumeRespDTO(true, "completed"); + } + }; + } + + @Bean + MarketAssetSourceApi realMarketAssetSourceApi() { + return assetId -> new MarketAssetSourceRespDTO(true, "knowledge_base", 4001L, 2001L, + "Real KB", "dataset-public", "ready"); + } + + @Bean + MarketAssetForkApi realMarketAssetForkApi() { + return request -> true; + } + } +} diff --git a/muse-cloud/scripts/run-p1r-verification.sh b/muse-cloud/scripts/run-p1r-verification.sh index d98b1ea3..e2e82a33 100644 --- a/muse-cloud/scripts/run-p1r-verification.sh +++ b/muse-cloud/scripts/run-p1r-verification.sh @@ -10,7 +10,7 @@ SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" REPO_ROOT="$(cd -- "${SCRIPT_DIR}/../.." >/dev/null 2>&1 && pwd)" CLOUD_DIR="${REPO_ROOT}/muse-cloud" -LOCAL_TESTS="P1rApiCoverageReportTest,P1r*RealApiGateTest,AgentsInfraIntegrityTest,ContractFirstGateTest,BcBoundaryArchTest,AiGrantRuntimeBoundaryArchTest" +LOCAL_TESTS="P1rApiCoverageReportTest,P1r*RealApiGateTest,P1rMarketDetachedAssemblyGateTest,AgentsInfraIntegrityTest,ContractFirstGateTest,BcBoundaryArchTest,AiGrantRuntimeBoundaryArchTest" REAL_PG_TESTS="${P1R_REAL_PG_TESTS:-P1r*IT,!P1rAiRuntimeEndToEndLiveAcceptanceIT,!P1rKnowledgeRuntimeEndToEndLiveAcceptanceIT,!P1rMarketKbForkMaterializationIT,!P1rImportLlmNewApiLiveAcceptanceIT,!P1rNewApiLiveAcceptanceIT,!P1rRagFlowLiveAcceptanceIT,!P1rDifyChatLiveAcceptanceIT,!P1rDifyDatasetsContractLiveIT}" EXTERNAL_LIVE_TESTS="${P1R_EXTERNAL_LIVE_TESTS:-P1rAiRuntimeEndToEndLiveAcceptanceIT,P1rKnowledgeRuntimeEndToEndLiveAcceptanceIT,P1rMarketKbForkMaterializationIT}" MODULE_LIVE_TESTS="${P1R_MODULE_LIVE_TESTS:-P1rNewApiLiveAcceptanceIT,P1rImportLlmNewApiLiveAcceptanceIT,P1rRagFlowLiveAcceptanceIT,P1rDifyChatLiveAcceptanceIT,P1rDifyDatasetsContractLiveIT}"