feat(muse): detach market assembly for solo mode
This commit is contained in:
parent
b9663f52d1
commit
c8a88e71b3
@ -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 调控制台端点。
|
||||
|
||||
|
||||
@ -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<next>__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<next>」配方,执行 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 的总验收门)。
|
||||
|
||||
@ -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 契约先行 |
|
||||
|
||||
@ -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。
|
||||
|
||||
@ -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"
|
||||
],
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -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。仍缺:版本恢复/高级回滚。
|
||||
|
||||
@ -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)。
|
||||
|
||||
@ -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<AppInstalledKnowledgeBaseVO.SummaryRespVO> 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,
|
||||
|
||||
@ -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 副本资源回收、下架/撤权更细补偿。
|
||||
|
||||
@ -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` 证据锚点门禁。前端仍有深页未全覆盖真实后端。
|
||||
|
||||
@ -69,11 +69,17 @@
|
||||
<artifactId>muse-module-knowledge-server</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<!-- 2.0.0 单人版保留 market 对外契约,供 knowledge/content 依赖 fail-closed facade;默认不装配 market-server。 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.cloud</groupId>
|
||||
<artifactId>muse-module-market-server</artifactId>
|
||||
<artifactId>muse-module-market-api</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>cn.iocoder.cloud</groupId>-->
|
||||
<!-- <artifactId>muse-module-market-server</artifactId>-->
|
||||
<!-- <version>${revision}</version>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
<!-- 会员中心。默认注释,保证编译速度 -->
|
||||
<dependency>
|
||||
@ -178,14 +184,11 @@
|
||||
<profile>
|
||||
<id>market-detached-test-tree</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>muse.market.detached.test-tree</name>
|
||||
<value>true</value>
|
||||
</property>
|
||||
<activeByDefault>true</activeByDefault>
|
||||
</activation>
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- 2.0.0 S2 dry-run:显式启用时剥离依赖 market-server 装配态的测试树;S3 才会拨默认开关。 -->
|
||||
<!-- 2.0.0 S3 默认剥离依赖 market-server 装配态的测试树;恢复时显式启用 market-assembled。 -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
@ -238,6 +241,13 @@
|
||||
|
||||
<profile>
|
||||
<id>market-assembled</id>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.cloud</groupId>
|
||||
<artifactId>muse-module-market-server</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- 恢复/全量装配口径:即使同时带上 dry-run profile,也以全测试树为准。 -->
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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 行为。
|
||||
|
||||
@ -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 行为。
|
||||
|
||||
@ -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 摘装配门禁。
|
||||
*
|
||||
* <p>本测试只证明默认单体形态已经不装配 market-server,market 跨域端口按 fail-closed 兜底,
|
||||
* 以及显式 {@code market-assembled} profile 仍保留恢复路径;不重新证明 market 业务 API。</p>
|
||||
*/
|
||||
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("<profiles>")));
|
||||
String marketAssembledProfile = pom.substring(pom.indexOf("<id>market-assembled</id>"));
|
||||
|
||||
assertTrue(defaultDependencies.contains("<artifactId>muse-module-market-api</artifactId>"),
|
||||
"muse-server 默认必须保留 market-api 契约");
|
||||
assertFalse(defaultDependencies.contains("<artifactId>muse-module-market-server</artifactId>"),
|
||||
"muse-server 默认不得装配 market-server");
|
||||
assertTrue(pom.contains("<id>market-detached-test-tree</id>")
|
||||
&& pom.contains("<activeByDefault>true</activeByDefault>"),
|
||||
"S3 后 market-detached-test-tree 必须默认生效");
|
||||
assertTrue(marketAssembledProfile.contains("<artifactId>muse-module-market-server</artifactId>"),
|
||||
"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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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}"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user