feat: expand EasyCode software factory workflows

This commit is contained in:
王鹏
2026-07-15 12:48:50 +08:00
parent fcfa4374d7
commit 79dea897bc
1108 changed files with 163774 additions and 21593 deletions

View File

@@ -0,0 +1,92 @@
# EasyCode Profile Center Design
## Goal
Add a lightweight personal center to the EasyCode front-end app. The page gives logged-in front users one place to view and edit account data, change password, review recent generated projects, review source purchases, and check AI quota.
## Scope
This work targets `easycode-web`, the Vue 3 + Element Plus front app. The existing `ruoyi-ui` admin app already has the standard RuoYi profile center and is not changed.
The first version is a single protected `/profile` route. It does not introduce nested profile routes, membership management, payment flows, invoices, or avatar upload.
## User Experience
The header shows a profile entry when the user is logged in. Clicking the displayed user name opens `/profile`.
The profile page uses one account summary area and tabbed detail sections:
- Basic profile: username, nickname, email, phone, created time, last login time.
- Edit profile: update nickname, email, and phone.
- Change password: old password, new password, confirm password.
- My projects: recent generated projects from the existing front project list API.
- Source purchases: existing source purchase records.
- AI quota: today's task usage, running task usage, and monthly cost usage.
Each tab has its own loading and empty state so one failing data source does not block the rest of the page.
## Backend Design
Existing APIs are reused:
- `GET /front/auth/profile`
- `GET /front/project/list`
- `GET /front/project/ai-quota`
- `GET /front/source/purchases`
Two authenticated front-account APIs are added:
- `PUT /front/auth/profile`
- Reads the current user id from `SecurityUtils.getUserId()`.
- Accepts nickname, email, and phone.
- Validates basic length and format.
- Updates only the current `front_user` row.
- Returns the refreshed profile without password.
- `PUT /front/auth/password`
- Reads the current user id from `SecurityUtils.getUserId()`.
- Verifies the old password against the stored encoded password.
- Requires the new password and confirmation to match.
- Reuses the existing 5-50 character password rule.
- Stores the encrypted new password.
## Frontend Design
New and changed files:
- `easycode-web/src/api/auth.js`: add `updateProfile` and `updatePassword`.
- `easycode-web/src/router/index.js`: add protected `/profile`.
- `easycode-web/src/components/AppHeader.vue`: make the logged-in user name a profile entry.
- `easycode-web/src/views/ProfileView.vue`: implement the single-page profile center.
- `easycode-web/src/views/profileView.test.mjs`: protect the expected route, API usage, and page sections.
The page updates local `easycode_web_user` after a successful profile save so the header reflects the new nickname immediately.
## Error Handling
Backend service exceptions return standard RuoYi `AjaxResult` error responses through existing exception handling. Frontend API errors use the existing axios interceptor and show local tab empty/error states where appropriate.
Password update success prompts the user to log in again. The page clears the local token after successful password change and routes to `/login`.
## Tests
Backend tests cover:
- Profile update trims and persists editable fields for the current front user.
- Profile update returns a password-free refreshed profile.
- Password update rejects an incorrect old password.
- Password update encrypts the accepted new password.
Frontend tests cover:
- `/profile` is a protected route.
- The header exposes a profile navigation entry when logged in.
- `ProfileView.vue` uses the profile, update profile, update password, projects, purchases, and quota APIs.
## Acceptance Criteria
- Logged-in front users can open `/profile` from the header.
- Users can edit nickname, email, and phone.
- Users can change password only with the correct old password.
- Projects, source purchases, and AI quota are visible in the profile center.
- Existing admin profile behavior is unchanged.

View File

@@ -0,0 +1,111 @@
# 在线运行预览设计
## 目标
为 EasyCode 生成出来的项目提供本机在线运行预览能力,让用户不需要手动下载、解压、配置和启动生成项目,也能快速确认真实运行效果。
## 第一期范围
第一期运行在当前开发机器上。系统会为每个生成项目创建独立的预览工作目录,初始化专用 MySQL 数据库,把生成出来的 Spring Boot 后端和 Vue 前端作为子进程启动,并把本地预览地址返回给前端页面。
这一期主要用于开发阶段快速验证,不作为公开的多租户沙箱。
## 用户流程
1. 用户完成项目预览生成。
2. 用户打开项目预览页。
3. 用户点击 `运行预览`
4. 后端把生成源码 zip 导出到本地预览工作目录。
5. 后端创建专用预览数据库,并导入生成 SQL。
6. 后端在空闲本地端口启动生成出来的 Spring Boot 服务。
7. 后端在空闲本地端口启动生成出来的 Vue 前端。
8. 页面展示后端状态、前端状态、日志和外部预览链接。
9. 用户可以在页面上停止当前预览会话。
## 架构
该能力在现有源码预览/下载服务旁边新增一个运行预览服务。
- 现有 `IFrontProjectPreviewService` 继续负责生成源码 zip。
- 新增 `IFrontProjectRunPreviewService` 负责运行预览会话。
- 第一期会话状态保存在内存中,并按 `userId + projectId` 隔离。
- 工作目录创建在 `preview-workspaces/project-{projectId}` 下。
- 生成项目会被解压到该工作目录。
- 预览数据库命名形如 `preview_{projectId}_{timestamp}`
- 子进程通过环境变量注入端口和数据库配置,不直接改写生成文件。
## 生成模板要求
生成后端模板必须从环境变量读取运行配置:
```yaml
server:
port: ${SERVER_PORT:8080}
spring:
datasource:
url: ${DB_URL:jdbc:mysql://localhost:3306/vip?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8}
username: ${DB_USERNAME:root}
password: ${DB_PASSWORD:123456}
```
生成 Vue 模板必须从环境变量读取前端端口和后端代理地址:
```js
const port = process.env.PORT || 8081
const apiBaseUrl = process.env.VUE_APP_API_BASE_URL || 'http://localhost:8080'
```
## 后端接口
```text
POST /front/project/{projectId}/run-preview
GET /front/project/{projectId}/run-preview
POST /front/project/{projectId}/run-preview/stop
```
所有接口都使用当前前台用户 ID并且必须调用项目服务校验项目归属。
## 会话状态
状态枚举:
- `NOT_STARTED`
- `PREPARING`
- `STARTING`
- `RUNNING`
- `FAILED`
- `STOPPED`
返回字段:
- `projectId`
- `status`
- `message`
- `backendUrl`
- `frontendUrl`
- `backendPort`
- `frontendPort`
- `workspacePath`
- `databaseName`
- `logs`
- `startedAt`
- `updatedAt`
## 安全约束
第一期不能把生成项目直接运行在宿主 RuoYi 进程里。生成项目必须作为独立子进程启动,并且可以被单独停止。
解压工作目录时必须拒绝逃逸工作目录的 zip 条目。每次启动新会话前可以清理项目工作目录,但清理范围必须限制在配置的预览工作根目录内。
数据库初始化会创建新数据库并导入生成 SQL。第一期停止预览时暂不自动删除数据库等预览生命周期稳定后可以再补清理策略。
## 后续阶段
第二期建议迁移到 Docker Compose
- 一个生成后端容器
- 一个生成前端容器
- 一个 MySQL 容器
- 反向代理路径,例如 `/preview/{projectId}/`
- 基于 TTL 的自动清理

View File

@@ -0,0 +1,108 @@
# Frontend Action Slots Design
## Goal
Generate user-facing frontend pages from the system module blueprint, then attach business workflow actions to explicit page slots such as detail primary buttons and list row actions.
## Problem
The current blueprint flow already separates static application modules from executable business actions:
- `AppBlueprintDesign` stores roles plus `frontendMenus` and `adminMenus`.
- `BusinessActionDesign` stores executable action details such as `ownerTable`, `requestFields`, `ruleChecks`, and `effects`.
Generated frontend templates still attach actions by table only. A table-owned action appears in both the generated list page and detail page because templates iterate `tableBusinessActions`. This cannot express cases such as:
- Book catalog is the only frontend menu.
- Book detail is a child page, not a top-level menu.
- Borrow and reserve appear on book detail.
- Return appears in My Borrows row actions.
- Cancel reservation appears in My Reservations row actions.
## Architecture
Add a small frontend page composition layer between menus and business actions.
System module blueprint gains `frontendPages`. A menu can point to a page with `pageCode`, while child pages such as details can use `parentPageCode`. Business actions gain `uiBindings`, each of which names a target page and a supported slot.
The generator keeps existing table-based action rendering as a compatibility fallback. When `uiBindings` are present, frontend templates render by slot instead of blindly rendering every action for the table.
## DSL
`frontendPages` item:
```json
{
"code": "book_detail_page",
"name": "Book Detail",
"menuCode": "book_catalog",
"parentPageCode": "book_catalog_page",
"path": "/books/:id",
"pageType": "detail",
"tableName": "book_info"
}
```
`uiBindings` item:
```json
{
"target": "frontend",
"pageCode": "book_detail_page",
"slot": "detail.primaryActions",
"component": "button",
"inputMapping": {
"book_id": "detail.book_id"
},
"refresh": "detail"
}
```
Supported first-phase slots:
- `list.toolbarActions`
- `list.rowActions`
- `detail.primaryActions`
- `detail.secondaryActions`
- `form.footerActions`
First implementation renders `list.rowActions` and `detail.primaryActions`. Other slot names are validated and preserved for later template expansion.
## Data Flow
1. AI generates system module blueprint with menus and pages.
2. Frontend sends confirmed app blueprint when generating business blueprint.
3. Business blueprint prompt includes saved schema and confirmed app blueprint.
4. AI returns actions with `uiBindings`.
5. Backend normalizes and validates table, field, page, slot, component, and refresh values.
6. `GenProjectServiceImpl` builds `slotBusinessActions` and exposes it to Velocity.
7. Vue3 frontend templates render row and detail buttons from slot-specific action lists.
## Compatibility
Existing saved projects without `frontendPages` or `uiBindings` still work:
- Menus render as they do today.
- Backend action code still uses `ownerTable`.
- Frontend action buttons fall back to table-owned actions when no slot bindings exist.
## Validation
Validation rejects:
- Unknown page codes in `uiBindings`.
- Unknown slot names.
- Unknown target values other than `frontend` and `admin_frontend`.
- Unknown component values other than `button`, `link`, and `dropdown-item`.
- Unsafe input mapping expressions.
For first phase, page validation is strict when the app blueprint is present and lenient when legacy actions have no bindings.
## Testing
Tests cover:
- DTO serialization and task worker propagation of app blueprint into business blueprint requests.
- Validator acceptance of valid frontend page bindings and rejection of unknown page codes.
- Velocity context grouping of actions into `slotBusinessActions`.
- Vue3 template seed rendering of `list.rowActions` and `detail.primaryActions`.

View File

@@ -0,0 +1,50 @@
# Business Blueprint Schema Repair Design
## Problem
Business blueprint generation currently includes a fixed example that uses
`book_info.book_id`. When the saved `book_info` table instead uses `id`, the
model may copy the fixed example even though later prompt instructions list
the real schema. Strict target-table validation then correctly rejects the
generated condition field.
## Design
Keep strict table-scoped validation unchanged. The generator must never infer
that `book_id` means `id`, because those columns can have different meanings
on different tables.
Build the JSON shape example from the saved schema. Select the first table
that has columns and use its primary key, or its first column when primary-key
metadata is unavailable, as both the request field and an `EXISTS` rule
condition. This keeps every identifier in the example valid for the current
project while still demonstrating the expected DSL shape.
If the first AI response fails business-blueprint validation, make one
correction request. The correction prompt includes the validation error, the
original response, and the saved table/column schema. Parse, normalize, and
validate the corrected response through the same strict pipeline. A second
invalid response is returned as the final error; there is no third attempt.
## Error Handling
Only `ServiceException` raised while parsing, normalizing, or validating the
AI business blueprint triggers correction. Authentication, project lookup,
database loading, persistence, and network failures are not retried by this
local correction path.
Successful generation stores the final corrected AI response. Failed
generation keeps the final validation error. Existing asynchronous retry
behavior remains responsible for transient network failures.
## Testing
Regression tests use a saved `book_info` table whose primary key is `id` while
another table contains `book_id`.
- The initial prompt example must use `book_info.id`, not
`book_info.book_id`.
- An invalid first response followed by a valid corrected response must result
in two AI calls and persist the corrected blueprint.
- Two invalid responses must still fail strict validation after exactly two AI
calls.

View File

@@ -0,0 +1,37 @@
# Legacy Menu Code Navigation Design
## Problem
Legacy frontend page designs may store a business menu code such as
`announcements` in `menu_code` while the page code is
`announcement_list_page`. These records do not declare a navigation level or
parent menu.
The current generator treats every differing `menu_code` as a parent menu
code. It therefore renders an artificial directory named `announcements`
instead of the page's display name.
## Design
Menu hierarchy must be driven only by explicit navigation metadata:
- A page is secondary when `navigation.menuLevel` is `secondary`.
- A page is secondary when `navigation.parentMenuCode` is present.
- Otherwise the page is a primary menu, even when `menu_code` differs from
`page_code`.
The existing `menu_code` remains the primary group's stable key. Its display
label continues to come from `navigation.menuName`, then `page_name`, then
`page_code`.
## Compatibility
Explicit directory and secondary-menu configurations are unchanged. No
database migration is required.
## Verification
Add a generator regression test using a page whose `menu_code` differs from
its `page_code` and whose layout has no navigation hierarchy metadata. The
rendered application must contain one clickable primary menu using the page
name and must not contain an artificial directory using the menu code.

View File

@@ -0,0 +1,48 @@
# Qing Business Action Runtime Design
## Goal
Make business-action buttons generated by the Qing runnable templates call real backend endpoints instead of returning HTTP 404.
## Root Cause
The portal frontend serializes `BusinessActionDesign.path` and sends requests such as:
- `POST /library/borrow/borrow`
- `POST /library/reservation/reserve`
The Qing backend templates currently generate only CRUD controllers and empty MyBatis-Plus services. No endpoint or executable DSL implementation is generated for `tableBusinessActions`.
## Architecture
Each table remains the owner of its business actions. Its generated controller exposes CRUD routes with complete `/module/business` mappings and exposes business actions using the absolute path stored in the blueprint. Its generated service interface and implementation render deterministic methods from the validated business-action DSL.
The service implementation uses `JdbcTemplate` inside `@Transactional` methods for rule checks and effects. It supports the existing validated rule/effect set and resolves `${param.field}`, `${now}`, and `${current_user.id}` without executing arbitrary AI-generated SQL.
The controller reads the authenticated portal user from `PortalAuthTokenStore` and puts the user ID into an internal parameter used by `${current_user.id}`. The frontend resolves fields available on the selected/detail record and prompts for any remaining required fields before sending the action.
## Template Synchronization
The source-of-truth bundled templates are:
- `ruoyi-generator/src/main/resources/qing/controller.java.vm`
- `ruoyi-generator/src/main/resources/qing/service.java.vm`
- `ruoyi-generator/src/main/resources/qing/serviceImpl.java.vm`
- `ruoyi-generator/src/main/resources/qing/index.vue.vm`
The runnable database seed copies in `sql/qing_templates.sql` and `sql/db.sql` must contain the same behavior. `GenProjectServiceImpl` falls back to bundled backend service/controller templates when an installed template record is stale, so existing local databases gain the fix without requiring an immediate SQL migration.
## Error Handling
- Missing action parameters return a business error rather than a 404.
- Missing authenticated portal users return “请先登录”.
- Invalid rules/effects throw a runtime business error and roll back the transaction.
- The frontend displays backend business messages and does not report success unless the response code is `200`.
## Testing
- Template rendering tests prove controller mappings, current-user injection, transactional service methods, DSL helpers, and frontend parameter collection are emitted.
- Generator service tests prove stale runnable backend templates are replaced by bundled controller/service templates.
- The generated project must compile.
- Authenticated HTTP requests to borrow and reserve must no longer return 404.

View File

@@ -0,0 +1,43 @@
# Strict Business Action Condition Fields Design
## Goal
Prevent generated business-action SQL from using a column that does not exist on the configured target table.
## Field Semantics
`requestFields` are request payload keys. `ruleChecks.conditionFields` and
`effects.conditionFields` are SQL condition columns and must exist on the
corresponding `targetTable`.
No alias mapping or legacy inference is supported. If a book table uses `id`,
the business blueprint must use `id` as its condition field. A related record
may still write that value into its own `book_id` column through
`${param.id}`.
## Validation
Business blueprint normalization and validation use a table-to-column map.
Every rule target field, effect target field, effect value key, and condition
field is checked against its own target table instead of the union of all
project columns.
Condition-based effects require explicit condition fields. The generator must
not fall back to all action request fields because request payloads can contain
fields that are not columns of the effect target table.
Invalid business blueprints fail before persistence and code generation with
an error naming the target table and invalid condition field.
## Generated Runtime
The Qing service implementation builds SQL directly from validated
`conditionFields`. It does not read `conditionColumns` and does not substitute
request fields when effect conditions are absent.
## Testing
Regression tests cover a schema where `book_info` has primary key `id` while
another table has `book_id`. A rule targeting `book_info` with
`conditionFields: ["book_id"]` must be rejected. A valid rule using `id` must
remain renderable as `where id = ?`.

View File

@@ -24,7 +24,7 @@
- 第一版目标选 B生成后能在线运行预览。
- 输入边界:项目名称必填,需求描述可选。只填“图书借阅系统”也应能生成默认可运行项目。
- 默认生成范围:后端、用户前台、后台管理端和 SQL。高级设置允许关闭用户前台。
- 流程策略:默认一键到底,不在中间强制用户确认;专家模式允许逐步查看和修正。
- 流程策略:默认一键到底,不在中间强制用户确认;高级调整允许逐步查看和修正。
- 项目类型范围:带常见业务闭环的项目,不止 CRUD。第一版支持状态流转、提交、审核、取消、借还、归还、完成、库存或数量变化等安全 DSL 可覆盖的动作。
- 实现路线:以“后端新增一键生成编排任务”为主;模板项目库作为后续增强。
@@ -62,7 +62,7 @@
- 下载完整源码。
- 查看生成报告。
现有设计器不删除,改为专家模式入口。用户生成后不满意时,可以进入“调整项目 / 专家模式”修改系统蓝图、数据库结构、业务流程或页面设计,然后重新生成完整项目或重建预览。
现有设计器不删除,改为高级调整入口。用户生成后不满意时,可以进入“调整项目 / 高级调整”修改系统蓝图、数据库结构、业务流程或页面设计,然后重新生成完整项目或重建预览。
## 后端架构
@@ -188,8 +188,8 @@
一键生成失败不等于所有成果作废。系统应保留已完成阶段的产物,并在报告里明确可用内容。
- AI 调用失败:沿用现有任务重试策略。
- 蓝图或数据库校验失败:展示校验原因,引导用户补充需求或进入专家模式
- 页面初始化失败:允许进入专家模式修正系统蓝图或数据库。
- 蓝图或数据库校验失败:展示校验原因,引导用户补充需求或进入高级调整
- 页面初始化失败:允许进入高级调整修正系统蓝图或数据库。
- 源码结构失败:展示模板和文件类型相关错误。
- 运行预览失败:保留源码下载、结构预览和日志入口。
@@ -204,9 +204,9 @@
- 轮询任务状态。
- 展示阶段进度。
- 展示成功结果或失败报告。
- 提供专家模式入口。
- 提供高级调整入口。
现有面板迁移到专家模式
现有面板迁移到高级调整
- `AppBlueprintPanel`:系统蓝图调整。
- `DatabaseDesigner` 和 ER 图:数据库结构调整。
@@ -214,7 +214,7 @@
- 页面设计入口:继续跳转到独立 `PageDesignerView`
- `PreviewView`:继续负责完整源码结构、运行 iframe、日志和下载。
路由可以先保持 `/generate?projectId=...`,通过页面内模式切换实现普通模式和专家模式。后续如果需要更清晰的信息架构,再补 `/project/:projectId/workspace`
路由可以先保持 `/generate?projectId=...`,通过页面内模式切换实现普通模式和高级调整。后续如果需要更清晰的信息架构,再补 `/project/:projectId/workspace`
## 测试策略
@@ -237,8 +237,8 @@
- `GenerateView` 默认展示一键入口,而不是默认展开数据库和业务流程设计器。
- 点击“生成完整项目”后创建 `one_click_project` 任务并轮询。
- 成功后展示用户前台、后台管理端、下载和预览入口。
- 失败后展示失败阶段、错误信息、日志或专家模式入口。
- 专家模式能展示现有蓝图、数据库、业务动作和页面设计入口。
- 失败后展示失败阶段、错误信息、日志或高级调整入口。
- 高级调整能展示现有蓝图、数据库、业务动作和页面设计入口。
人工验收:
@@ -261,7 +261,7 @@
- 复用现有任务轮询接口。
- 简化 `GenerateView` 默认界面。
- 生成成功后展示运行预览和下载入口。
- 保留专家模式入口。
- 保留高级调整入口。
第二阶段:
@@ -275,5 +275,5 @@
- 范围聚焦在一键生成完整项目,不扩展到模板市场或生产部署。
- 后端采用编排现有能力的方式,不重写生成器。
- 成功标准明确为在线运行预览可访问。
- 设计器能力保留为专家模式,不再作为普通用户主路径。
- 设计器能力保留为高级调整,不再作为普通用户主路径。
- 失败处理保留阶段成果,避免运行预览失败导致全部成果不可用。

View File

@@ -2,7 +2,7 @@
## 目标
把一键模式从“生成完整 CRUD 项目”升级为“生成可运行的完整业务闭环项目”。用户只输入项目名称例如“图书借阅系统”系统也应自动推断核心业务闭环并生成后端、用户前台、后台管理端、SQL、业务动作按钮和在线运行预览。
把一键生成从“生成完整 CRUD 项目”升级为“生成可运行的完整业务闭环项目”。用户只输入项目名称例如“图书借阅系统”系统也应自动推断核心业务闭环并生成后端、用户前台、后台管理端、SQL、业务动作按钮和在线运行预览。
第一版聚焦通用闭环生成引擎,不依赖行业模板包。成功标准不是项目能打开,也不是表结构能维护,而是生成项目至少包含一条从入口动作、状态流转、数量变化或关联记录写入,到后续完成动作的可执行闭环。
@@ -16,7 +16,7 @@
## 背景
当前 `one_click_project` 已经能串联应用蓝图、数据库、业务动作、页面初始化、源码结构和运行预览。问题在于编排链路本身不表达“业务闭环必须完整”。如果 AI 在数据库或业务动作阶段输出偏保守,一键模式仍会生成一个看起来完整、实则以表维护为主的项目。
当前 `one_click_project` 已经能串联应用蓝图、数据库、业务动作、页面初始化、源码结构和运行预览。问题在于编排链路本身不表达“业务闭环必须完整”。如果 AI 在数据库或业务动作阶段输出偏保守,一键生成仍会生成一个看起来完整、实则以表维护为主的项目。
已有能力可以复用:
@@ -182,7 +182,7 @@ BusinessLoopAuditResult
- 业务记录页放状态流转按钮和详情入口。
- 基础资料表保留 CRUD但只作为维护入口。
实现上复用现有页面设计和模板能力,把计划动作自动转成 `pageDesignToolbarBusinessActions``pageDesignRowBusinessActions` 或详情页主按钮配置。第一版不要求用户在一键模式中手动确认按钮位置。
实现上复用现有页面设计和模板能力,把计划动作自动转成 `pageDesignToolbarBusinessActions``pageDesignRowBusinessActions` 或详情页主按钮配置。第一版不要求用户在一键生成中手动确认按钮位置。
## 任务阶段与结果
@@ -205,11 +205,11 @@ BusinessLoopAuditResult
- `DATABASE_LOOP_AUDIT`:数据库覆盖审计。
- `BUSINESS_LOOP_AUDIT`:业务动作覆盖审计和页面动作落位检查。
失败时保留已完成产物。若数据库已生成但动作审计失败,用户仍可进入专家模式查看数据库和计划,但一键任务不标记为成功。
失败时保留已完成产物。若数据库已生成但动作审计失败,用户仍可进入高级调整查看数据库和计划,但一键任务不标记为成功。
## 专家模式
## 高级调整
专家模式继续保留现有蓝图、数据库、业务流程和页面设计能力。新增闭环计划摘要面板,用于展示:
高级调整继续保留现有蓝图、数据库、业务流程和页面设计能力。新增闭环计划摘要面板,用于展示:
- 核心对象。
- 必需动作。
@@ -217,7 +217,7 @@ BusinessLoopAuditResult
- 数量和记录规则。
- 覆盖审计结果。
第一版不要求支持可视化编辑闭环计划。若用户修改数据库或业务动作,专家模式可以重新运行闭环审计,指出缺失项。
第一版不要求支持可视化编辑闭环计划。若用户修改数据库或业务动作,高级调整可以重新运行闭环审计,指出缺失项。
## 测试策略
@@ -237,7 +237,7 @@ BusinessLoopAuditResult
前端测试:
- 一键结果报告展示闭环完成状态和缺失项。
- 专家模式能显示闭环计划摘要和审计错误。
- 高级调整能显示闭环计划摘要和审计错误。
- 页面设计初始化后,闭环动作进入合适页面按钮集合。
人工验收:
@@ -262,19 +262,19 @@ BusinessLoopAuditResult
第二阶段:
- 页面初始化自动落位闭环按钮。
- 专家模式展示闭环计划摘要和审计结果。
- 高级调整展示闭环计划摘要和审计结果。
- 完成图书借阅、商城订单、请假审批三个验收场景。
第三阶段:
- 支持从审计失败阶段继续生成。
- 引入可选行业模板包,提高常见场景稳定性。
- 支持用户在专家模式调整闭环计划。
- 支持用户在高级调整调整闭环计划。
## 自检
- 本设计聚焦通用闭环生成,不转向行业模板市场。
- 执行层继续使用安全 DSL不开放任意代码生成。
- 只填项目名称也能触发闭环推断。
- 一键模式不会把 CRUD 项目伪装成闭环成功。
- 一键生成不会把 CRUD 项目伪装成闭环成功。
- 页面按钮、数据库结构和业务动作都围绕同一份闭环计划生成。

View File

@@ -0,0 +1,96 @@
# 系统架构图 DSL 编辑器设计
## 目标
将现有 `图表中心 > 系统架构图` 从简单横向节点图升级为截图风格的架构图编辑器。用户进入页面时,系统根据当前项目数据自动生成一份架构图 DSL 初稿;用户可编辑 DSL、生成预览、保存草稿并导出 SVG/PNG。
## 当前基础
- 前端已有 `easycode-web/src/views/DiagramCenterView.vue`,提供 ER 图、功能模块图、架构图、项目总览图和 AI 图表入口。
- 架构图当前由 `buildArchitectureGraph()` 生成通用节点图,缺少 DSL 编辑、分层架构布局和横切关注点侧栏。
- 图表保存已经通过 `saveProjectDiagram(projectId, payload)` 写入 `front_project_diagram``diagramType='architecture'` 可复用。
- 导出能力已有 SVG/PNG 浏览器端实现,可继续复用。
## DSL 语法
采用缩进文本 DSL贴近截图中的编辑体验
```text
用户层
学生
选课 / 查成绩
教师
课程管理 / 成绩录入
表现层 - Vue 3 前端
Vue 3
Composition API
Vue Router 路由
横切关注点
JWT 认证
登录签发 Token
```
规则:
- 顶格行为层名,可用 ` - ``—` 附加副标题。
- 缩进 2 个空格为组件标题。
- 缩进 4 个空格为组件说明,可多行。
- 层名为 `横切关注点` 时,不进入主纵向架构流,而是渲染到右侧关注点栏。
- 空行忽略;无法归类的行尽量按最近层/组件吸收,避免用户轻微格式错误导致整图不可用。
## 默认 DSL 生成
进入系统架构图时优先加载已保存的 `architecture` 草稿;若没有保存草稿,则根据当前项目数据生成默认 DSL
- 用户层:从 `appBlueprint.roles` 或项目菜单推断角色,缺省为“普通用户/管理员”。
- 表现层:使用 Vue 3、Element Plus、Axios补充前台/后台菜单数量。
- 接口层Spring MVC Controller、统一接口规范、统一响应封装。
- 业务逻辑层:从业务模块、菜单或数据库表提取服务组件,缺省提供用户服务、业务服务、文件服务。
- 持久层MyBatis、Mapper、分页和连接池。
- 数据层MySQL、本地文件系统展示表数量。
- 运行环境Windows/Mac、JDK 8+、Node.js 16+、Spring Boot、MySQL。
- 横切关注点JWT 认证、全局异常、接口规范、跨域配置。
## 交互设计
`activeDiagram === 'architecture'` 时显示专用编辑器:
- 顶部工具栏显示“系统架构图”、层数/组件数统计、黑白模式、保存、导出 SVG、导出 PNG。
- 左侧为 DSL 编辑区,提供“清空”“加载示例”“换一换”“生成预览”。
- 右侧为可滚动 SVG 预览,主架构层纵向排列,层之间用箭头连接;横切关注点在右侧独立粉色侧栏显示。
- 预览失败时保留编辑区,右侧显示可诊断的空态或错误提示。
- 保存时写入 `diagramJson`,结构包含 `dsl``graph``blackWhite`,后续重新进入页面可恢复。
## 渲染设计
新增前端工具模块负责纯函数处理:
- `buildDefaultArchitectureDsl(project, database, blueprint)`:生成默认 DSL。
- `parseArchitectureDsl(dsl)`:解析为 `{ layers, concerns, stats, errors }`
- `buildArchitectureDiagram(parsed, options)`:计算 SVG 坐标、画布尺寸、层/组件/箭头数据。
- `buildArchitectureExportSvg(diagram, options)`:输出可下载 SVG 字符串。
布局采用固定宽度、响应式外层滚动:
- 主层宽约 620px组件按每行 3 个排列。
- 横切关注点侧栏宽约 190px高度跟随主图。
- 根据组件数量自动增加每层高度。
- 黑白模式仅改变色彩,不改变 DSL、坐标或保存结构。
## 测试
新增 `easycode-web/src/utils/architectureDiagram.test.mjs`
- 默认 DSL 生成包含项目名、前端/后端/数据库核心层。
- DSL 解析能识别层、副标题、组件、说明和横切关注点。
- 布局统计能正确返回层数、组件数,画布尺寸随内容增加。
- 导出 SVG 包含层标题、组件标题和关注点内容。
- 保存恢复结构保持 DSL 和图数据字段稳定。
## 非目标
- 不接 AI 生成 DSL。
- 不做拖拽移动组件。
- 不做历史撤销/重做。
- 不新增后端接口或数据库字段。
- 不替换 ER 图、功能模块图、项目总览图的现有实现。

View File

@@ -0,0 +1,226 @@
# EasyCode ER 图中心设计方案
## 背景
当前项目已经在 `easycode-web` 前台项目工作台中提供图表中心页面,并具备基础 ER 图能力:
- `easycode-web/src/views/DiagramCenterView.vue` 负责图表中心入口、图表切换、草稿保存和导出。
- `easycode-web/src/components/ErDiagramView.vue` 已能基于数据库设计渲染表结构卡片、拖动节点、手工添加关系、导出 PNG。
- `front_project_diagram` 已能保存 `diagram_type = er` 的图表草稿。
- `easycode-web/src/api/project.js` 已有项目、数据库、图表草稿相关接口封装。
本次目标是把现有 ER 图升级为截图所示的 ER 图工作台SQL 输入、实体筛选、Chen 风格实体属性图、默认布局、一键美化、样式控制、导出和草稿恢复。
## 范围
本功能入口只放在 `easycode-web` 前台项目工作台,不在 `ruoyi-ui` 后台管理端新增菜单或页面。
允许补充必要后端能力,主要是 SQL DDL 解析接口和图表草稿持久化复用。后端能力只服务 `easycode-web` 前台项目工作台,不作为后台工具菜单交付。
## 用户体验
页面采用截图中的三栏结构:
- 左侧为 SQL 输入区,支持清空、加载示例、换一换、上传 `.sql` 文件、重新生成。
- 中间为实体列表,显示已选实体数和关系数,支持全选、单表勾选、实体搜索。
- 右侧为 ER 画布,顶部提供基础 ER 图和系统 ER 图标签。首期实现基础 ER 图,系统 ER 图预留入口。
画布工具栏包含:
- 重新生成:基于当前 SQL 或当前数据库设计重新构建 ER 图。
- 默认布局:恢复稳定网格布局。
- 一键美化:按关系密度和实体数量重新布局。
- 主键下划线:开关主键属性文字下划线。
- 线条粗细:控制关系线和属性线粗细。
- 字体:提供默认、紧凑、论文三种字体方案。
- 导出:支持 PNG、SVG、JSON。
画布交互包含:
- 鼠标滚轮缩放。
- 拖动画布平移。
- 拖动实体节点,属性节点跟随实体移动。
- 双击实体或属性改显示名。
- 选中实体列表后实时隐藏未选实体。
## ER 图模型
前端统一使用标准化后的 ER 图模型:
```json
{
"version": 2,
"sql": "CREATE TABLE ...",
"selectedTableNames": ["user", "order"],
"tables": [
{
"tableName": "user",
"tableComment": "用户表",
"columns": [
{
"columnName": "id",
"columnComment": "用户ID",
"columnType": "bigint",
"isPk": "1",
"isIncrement": "1",
"isRequired": "1"
}
]
}
],
"relations": [
{
"id": "order.user_id->user.id",
"source": "order",
"sourceField": "user_id",
"target": "user",
"targetField": "id",
"inferred": true
}
],
"layout": {
"entities": {
"user": { "x": 120, "y": 180 }
}
},
"style": {
"primaryKeyUnderline": true,
"lineWidth": 1,
"fontPreset": "default"
}
}
```
## 图形呈现
基础 ER 图采用 Chen 风格:
- 实体使用矩形节点,显示中文表名优先,英文表名作为辅助信息。
- 属性使用椭圆节点,围绕实体排布,显示中文字段注释优先,字段名作为辅助信息。
- 主键属性文字带下划线。
- 属性与实体之间使用细线连接。
- 实体关系线根据外键或 `xxx_id` 字段推断生成。
布局策略:
- 每个实体内部采用椭圆环绕布局,字段数量较少时均匀环绕,字段数量较多时分左右两列。
- 多实体默认按网格布局排列,保证截图中类似的疏朗效果。
- 一键美化时优先把存在关系的实体放近,孤立实体放到外侧。
- 保存草稿后优先恢复用户拖动后的实体坐标。
## SQL 解析
SQL 解析放在后端实现,复用项目中已有的 Druid SQL 解析能力,避免前端用正则拆 SQL。
新增接口:
```text
POST /front/project/{projectId}/er/parse-sql
```
入参:
```json
{
"sql": "CREATE TABLE `user` (...)"
}
```
出参:
```json
{
"tables": [],
"relations": [],
"warnings": []
}
```
解析范围:
- MySQL `CREATE TABLE`
- 表名、表注释。
- 字段名、字段类型、字段注释。
- 主键、自增、非空。
- 唯一键和索引可作为后续展示信息保留。
- 显式外键优先生成关系。
- 没有显式外键时,根据 `xxx_id` 字段推断关系。
异常处理:
-`CREATE TABLE` 语句忽略并返回 warning。
- 无法解析的片段返回 warning不影响其他表生成。
- SQL 为空时前端提示用户输入 SQL 或先生成数据库设计。
## 持久化
继续复用 `front_project_diagram`
- `diagram_type = er`
- `title = ER 图`
- `diagram_json` 保存完整 ER 草稿 JSON
数据库设计本身仍通过现有数据库保存接口维护。ER 图草稿只保存图形视图状态、SQL 输入、筛选状态、关系草稿和布局样式,不替代项目数据库设计。
## 前端改造
建议拆分如下文件:
- `easycode-web/src/views/DiagramCenterView.vue`:保留图表中心容器,负责数据加载、保存、导出和图表切换。
- `easycode-web/src/components/er/ErSqlPanel.vue`SQL 输入、示例、上传和重新生成。
- `easycode-web/src/components/er/ErEntityList.vue`:实体筛选、全选、数量统计。
- `easycode-web/src/components/er/ErToolbar.vue`:默认布局、一键美化、样式控制、导出按钮。
- `easycode-web/src/components/er/ErChenCanvas.vue`SVG 画布、缩放、平移、节点拖拽、双击改名。
- `easycode-web/src/utils/erChenGraph.js`:把数据库表模型转换为实体、属性、关系图模型。
- `easycode-web/src/utils/erLayout.js`:默认布局和一键美化布局。
- `easycode-web/src/utils/erDraft.js`:草稿合并、版本升级、默认值补齐。
- `easycode-web/src/utils/erExport.js`:扩展现有导出,支持 Chen 图 SVG/PNG。
## 后端改造
建议新增:
- `ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/ParseErSqlRequest.java`
- `ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/ParseErSqlResponse.java`
- `ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/SqlDdlParseService.java`
建议修改:
- `ruoyi-admin/src/main/java/com/ruoyi/web/controller/front/FrontProjectController.java`:新增 SQL 解析接口。
- `easycode-web/src/api/project.js`:新增 `parseErSql(projectId, data)`
## 分阶段交付
### 阶段一ER 图工作台 UI 和 Chen 图
用当前项目数据库设计生成 Chen 风格 ER 图,补齐实体筛选、默认布局、一键美化、样式控制、导出和草稿保存。此阶段不依赖 SQL 解析,能先替换现有卡片式 ER 图体验。
### 阶段二SQL 输入和解析
新增后端 SQL 解析接口,前端左侧 SQL 输入接入解析结果。支持粘贴 SQL、上传 SQL 文件、加载示例和重新生成。
### 阶段三:关系增强和编辑
完善显式外键、推断关系、手工关系编辑、关系删除、关系数量统计。允许用户调整关系后保存到草稿。
### 阶段四:验证和体验收口
补前端工具函数单测、后端 SQL 解析单测,验证中文注释、多表、大字段量、导出清晰度、移动端布局和草稿兼容。
## 验收标准
- ER 图中心入口只在 `easycode-web` 前台项目工作台出现。
- 当前项目数据库设计可以直接生成截图风格 ER 图。
- 粘贴 MySQL `CREATE TABLE` SQL 后可以生成实体、属性和关系。
- 实体列表勾选能实时控制画布显示。
- 主键属性默认下划线展示。
- 支持默认布局、一键美化、拖动实体、缩放和平移。
- 草稿保存后刷新页面能恢复 SQL、筛选、布局和样式。
- 支持 PNG、SVG、JSON 导出。
## 非目标
- 不在 `ruoyi-ui` 后台管理端新增 ER 图中心。
- 不在第一版实现多人协同编辑。
- 不在第一版实现完整数据库建模器替代功能。
- 不在第一版引入大型图形编辑库,除非原生 SVG 方案在验证中无法满足性能或交互要求。

View File

@@ -41,7 +41,7 @@
首屏右侧展示一个紧凑的能力摘要,而不是只展示 5 步流程:
- 一键模式 / 高级调整
- 一键生成 / 高级调整
- AI 生成需求
- 业务闭环报告
- 预览与下载

View File

@@ -0,0 +1,42 @@
# AI Usage CNY Cost Design
## Goal
AI 用量统计中的费用按人民币统计和展示,匹配 DeepSeek 模型价格表中的人民币单价。
## Scope
本次只调整现有 AI 用量统计的费用口径和展示:
- 后端费用计算使用人民币单价。
- `costCents` 继续表示人民币分。
- 管理端费用卡片和列表费用使用人民币符号展示。
- 数据库字段名 `cost_usd` 和 Java 属性 `costUsd` 暂时保留,作为兼容字段承载人民币金额,避免本次引入数据库迁移和历史数据改写。
## Pricing
DeepSeek V4 Flash:
- 缓存命中输入0.02 元 / 1M tokens
- 缓存未命中输入1 元 / 1M tokens
- 输出2 元 / 1M tokens
DeepSeek V4 Pro:
- 缓存命中输入0.025 元 / 1M tokens
- 缓存未命中输入3 元 / 1M tokens
- 输出6 元 / 1M tokens
未知 DeepSeek 模型沿用 Flash 单价。
## Data Flow
`HttpDeepSeekClient` 读取模型返回的 `usage``AiUsageRecorder` 调用 `AiCostCalculator` 计算费用并写入用量流水。`AiUsageLedgerMapper` 汇总流水金额,管理端页面通过 `/generator/aiUsage/list``/generator/aiUsage/summary` 读取并展示。
## UI
摘要卡片从 `费用 USD` 改为 `费用 CNY`。金额格式从 `$0.00000000` 改为 `¥0.00000000`,保留最多 8 位小数并去掉末尾多余 0。
## Testing
更新 `AiCostCalculatorTest`,先用旧实现验证人民币价格期望失败,再改实现使 Flash、Pro、无缓存拆分和分单位取整测试通过。更新 `AiUsageRecorderTest`,确认流水和 scope 中记录的是人民币金额。

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,89 @@
# Plugin Transition Durable Outbox Design
## Context
P2-D8e1 records transition executions and step receipts, but the request transaction still holds the Transition row lock while invoking the configured target Executor. That is acceptable for `dry-run:v1`; it is not acceptable for a remote SQL, permission, or menu adapter because network latency and process failure can leave a long transaction or an ambiguous external result.
P2-D8e2a moves execution behind a durable database Outbox before any real target adapter is enabled. The existing stable logical step idempotency key remains the external deduplication contract.
## Selected Approach
Use one Outbox command per Transition execution batch.
- A synchronous lease heartbeat inside the existing transaction was rejected because it preserves the long transaction and cannot make an external result atomic with the database commit.
- One Outbox message per step was deferred because the first version would need additional dependency, ordering, cancellation, and compensation coordination for little benefit.
- One message per batch preserves deterministic forward or reverse step order. Step receipts remain independently durable, so a reclaimed batch skips completed steps and redelivers only an ambiguous `RUNNING` step with the same idempotency key.
## State Model
Execution statuses are `QUEUED`, `RUNNING`, `SUCCEEDED`, and `FAILED`.
Step receipt statuses are `PENDING`, `RUNNING`, `SUCCEEDED`, and `FAILED`.
Outbox statuses are `PENDING`, `PROCESSING`, `DELIVERED`, and `FAILED`.
Creating an execution is one transaction:
1. Lock and verify the immutable Transition Plan.
2. Apply the existing execute/retry/compensate state guard.
3. Resolve all trusted contribution identities before creating durable state.
4. Insert a `QUEUED` execution and ordered `PENDING` step receipts.
5. Insert one `PENDING` Outbox command with a unique `execution_id`.
6. Commit without calling the target Executor.
## Lease Worker
The Worker polls claimable Outbox IDs. MySQL 5.7 does not provide the desired portable `SKIP LOCKED` behavior, so claiming is an atomic conditional update:
- `PENDING` commands are claimable when `available_at <= now`.
- `PROCESSING` commands are reclaimable only when `lease_until < now`.
- Claiming writes a unique lease token, worker identity, lease deadline, increments delivery count, and changes status to `PROCESSING`.
- All subsequent mutations require the same lease token.
After a claim, the coordinator resets an ambiguous `RUNNING` receipt to `PENDING`, marks the execution `RUNNING`, and returns. The Worker then resolves and verifies the immutable plan outside a database transaction. For each receipt it opens a short transaction to mark the step `RUNNING` and renew the lease, calls the Executor outside the transaction, and opens another short transaction to persist success. A runtime failure atomically marks the current receipt, execution, and Outbox `FAILED`.
If the process stops after an external success but before the receipt commit, the lease expires and another Worker redelivers that logical step with the exact same step idempotency key. Real target adapters must implement idempotent lookup/write semantics using that key and return the same target receipt.
## Reconciliation
The scheduler automatically claims expired leases. An explicit reconciliation API is also available to operators:
- A `PENDING` command is already recoverable and remains unchanged.
- An active `PROCESSING` lease cannot be stolen manually.
- An expired `PROCESSING` command is reset to `PENDING`; its ambiguous `RUNNING` receipt is reset, and its execution returns to `QUEUED`.
- `DELIVERED` and `FAILED` commands are terminal and are returned without replay. A failed execution uses the existing retry action to create a new attempt.
Execution history includes Outbox status, delivery count, lease owner/deadline, and last delivery error. The Plugin UI labels queued work distinctly and exposes reconciliation only for queued/running batches.
## Configuration
`factory.plugin-execution` gains:
- `outbox-polling-enabled`, default `true`.
- `outbox-poll-interval-seconds`, default `5`.
- `outbox-batch-size`, default `5`.
- `outbox-lease-seconds`, default `60`.
All numeric values are clamped to positive operational bounds. The existing `executor-code` remains `dry-run:v1` by default, so enabling the Worker still has no external side effects until a real Executor is explicitly configured.
## Failure Boundaries
- Plan, trusted payload, or fingerprint validation fails before durable execution state is inserted.
- Database failure before the enqueue transaction commits creates neither execution nor Outbox command.
- Executor failure is a terminal attempt failure and remains inspectable; retry creates a new execution and Outbox row.
- Database failure after an ambiguous external call leaves the Outbox lease to expire and relies on target-side idempotency for safe redelivery.
- A Worker never holds the Transition row lock or a Spring transaction while invoking an Executor.
## Scope
P2-D8e2a includes durable enqueueing, leases, polling, expired-lease recovery, reconciliation API/UI, schema migrations, and tests. It does not connect to a target database or mutate permission/menu services.
P2-D8e2b will add isolated-environment SQL, permission, and menu adapters plus target-side receipt lookup and reconciliation. Existing PageBlock plugins still have zero delivery steps until a trusted plugin declares a real payload.
## Testing
- Service tests prove enqueue atomicity, no synchronous Executor invocation, state guards, stable receipt identity, and compensation ordering.
- Worker tests prove atomic claim behavior, completed-step skipping, successful delivery, failure persistence, and stable-key redelivery after reclaim.
- Schema tests prove all tables, indexes, lease columns, mapper transitions, API routes, and permissions exist in every SQL distribution script.
- UI static tests prove queued/processing states, Outbox details, and reconciliation controls are present.
- Full generator and admin regression results are compared with the documented 18 generator baseline failures.

View File

@@ -0,0 +1,89 @@
# 一键生成当前阶段与失败修复建议设计
**状态:** 已实施并通过回归验证
## 1. 背景
一键生成工作台已经展示固定的 11 个步骤,但步骤状态主要由任务百分比和 `resultPayload.stage` 推断。首次生成时通常可用,跨会话恢复、多次尝试和检查点复用时会把“上一轮完成”“本轮重做”和“从检查点保留”混在一起,用户仍然不知道工厂本次实际执行了什么。
任务状态接口已经加载 `factory_ai_task_stage``factory_ai_task_checkpoint`,无需新增任务系统或历史接口。本轮在现有响应中增加脱敏执行摘要,并用独立前端组件展示权威阶段事实。
## 2. 用户目标
- 不打开生成历史也能看到当前是第几次执行。
- 区分已完成、执行中、失败、待执行和从检查点保留的阶段。
- 看到本次已完成阶段数和保留阶段数。
- 失败时得到针对当前阶段的中文说明和可执行建议。
- 明确继续生成会从哪里开始,以及已有成果是否保留。
## 3. 后端契约
在现有 `AiGenerationTaskStatusResponse` 中新增 `oneClickProgress`,仅对 `one_click_project` 任务返回:
| 字段 | 含义 |
| --- | --- |
| `attemptNo` | 当前或最近一次阶段尝试号 |
| `completedStageCount` | 本次完成与可信复用阶段总数 |
| `reusedStageCount` | 从检查点保留的阶段数 |
| `totalStageCount` | 固定阶段总数 |
| `currentStage` / `failedStage` | 当前与失败阶段 |
| `recoveryStage` | 再次继续时的服务器恢复起点 |
| `recoveryFromCheckpoint` | 是否会复用检查点 |
| `stages` | 11 个轻量阶段状态与耗时 |
| `repairAdvice` | 阶段化标题、说明、建议和恢复说明 |
阶段项只包含 `stageCode``sequenceNo``attemptNo``status``durationMillis` 和时间。不得复制 Prompt、模型、Handler 契约、Checkpoint Payload、请求或结果 Payload。
## 4. 状态合成
1. 优先使用任务阶段账本中最大 `attemptNo` 的记录。
2. 当前尝试存在阶段记录时直接采用 `RUNNING``SUCCEEDED``FAILED`
3. 当前尝试号大于检查点尝试号时,`SOURCE_PREVIEW` 检查点之前的阶段标记为 `REUSED`
4. 数据库检查点只复用应用蓝图、闭环计划、数据库和数据闭环审计;`PROJECT_PREPARE` 仍由恢复尝试重新执行。
5. 没有阶段账本的旧任务才回退到结果阶段和百分比推断。
6. 完成数包含 `SUCCEEDED``REUSED`,但二者在界面上必须分别显示。
## 5. 修复建议
- 建议由服务端按失败阶段生成,避免多个页面各自猜测业务含义。
- 建议不替换原始脱敏错误,而是补充“这一步负责什么、先检查什么、继续后从哪里开始”。
- `RUN_PREVIEW` 失败优先提示重启预览,明确源码已保留。
- `SOURCE_PREVIEW` 失败提示检查模板和生成前校验。
- 数据库与闭环阶段提示在高级调整中检查表、关系、状态和动作绑定。
- 无检查点时必须明确说明会从项目准备重新执行。
## 6. 前端交互
- 新增独立 `OneClickTaskProgressDetails` 组件,避免继续扩大 `GenerateView.vue`
- 进度区顶部显示执行次数、完成数和检查点保留数。
- 11 个阶段使用紧凑双列清单和状态图标,不再使用嵌套卡片。
- 每个阶段显示状态;有耗时时显示耗时。
- 原始失败摘要保留为 Alert阶段化修复建议显示在其后。
- 现有重试、重启预览、历史记录、路由恢复和轮询行为保持不变。
## 7. 边界
- 不新增接口、数据库表或迁移。
- 不允许浏览器指定恢复阶段。
- 不实现手动单阶段重跑、阶段取消或跨节点租约。
- 不修改开发阶段暂缓处理的模型凭据与 TLS 配置。
## 8. 验收标准
- 首次生成、数据库检查点恢复和源码检查点恢复均得到正确阶段状态。
- 成功、运行、失败、待执行和复用状态不会仅依赖百分比推断。
- 修复建议与失败阶段、恢复起点一致。
- 前端不展示 Prompt、模型、Handler、Payload 或哈希等内部字段。
- 后端聚焦测试、前端纯函数/组件契约测试、管理端测试和生产构建通过。
## 9. 实施结果
- 现有任务状态响应新增脱敏 `oneClickProgress`,不新增 API也不增加阶段/检查点查询次数。
- Composer 使用当前尝试的阶段账本作为权威事实,固定输出 11 个 `PENDING``RUNNING``SUCCEEDED``FAILED``REUSED` 阶段。
- 数据库检查点恢复只标记应用蓝图、闭环计划、数据库和数据闭环审计为已保留;源码检查点恢复标记源码之前 9 个阶段为已保留。
- 手动继续后的排队窗口会显示下一次执行和预计保留阶段,不再短暂显示上一轮失败;自动重试等待仍保留失败建议。
- 新增阶段化修复建议,覆盖项目准备、应用蓝图、业务闭环、数据库、页面、源码和运行预览,并明确继续位置与保留成果。
- 生成工作台改用独立进度详情组件,展示执行次数、阶段完成数、检查点保留数、阶段状态和耗时;桌面双列、窄屏单列。
- 旧后端响应仍可通过结果阶段和百分比降级展示,现有历史、重试、预览和下载行为未改变。
- 验证通过:后端聚焦 24 项、前端主流程 56 项、管理端全量 40 项EasyCode 生产构建通过。
- 本轮没有新增数据库表或迁移,也没有修改开发阶段暂缓处理的明文凭据与 TLS 校验配置。

View File

@@ -0,0 +1,116 @@
# 项目一键生成历史与阶段恢复设计
**状态:** 已实施并通过回归验证
## 1. 背景
项目工厂已经保存一键生成任务、逐阶段执行记录和不可变检查点,失败任务也可以通过原任务重试。但是这些能力主要存在于后端:用户只能看到当前任务,无法查看历次生成,也不知道失败发生在哪个阶段、重试会保留哪些结果。
现有 `front_project_generation` 更接近模型调用审计记录,包含请求和响应 Payload缺少任务阶段、尝试次数和检查点语义不适合作为用户历史列表。
## 2. 方案选择
### 方案 A直接展示 Generation Record
优点是已有查询接口。缺点是记录包含大字段,一个一键任务还可能关联多种模型调用语义,不能可靠表达失败阶段和恢复位置,因此不采用。
### 方案 B任务历史 + 阶段账本 + 检查点
每个 `one_click_project` 任务是一条生成历史;任务内的多次重试用尝试次数表达。历史摘要从任务表、阶段表和检查点表只读计算,继续操作复用现有 `retryTask`。该方案不新增表,和当前执行内核一致,采用此方案。
### 方案 C新增独立 Generation Attempt 聚合表
可以提供更强的报表能力,但需要迁移、双写和历史回填,当前用户主流程不需要这层复杂度,因此暂不采用。
## 3. 用户目标
- 从“我的项目”和一键生成工作台打开项目生成记录。
- 每条记录能看懂状态、时间、尝试次数、失败阶段和最后完成阶段。
- 失败记录明确说明继续生成会从哪个阶段开始,以及是否复用检查点。
- 用户确认后可直接从历史记录继续生成,不需要先寻找当前失败任务。
- 查看任意历史任务时,地址栏写入对应 `taskId`,刷新后仍显示同一任务。
## 4. 历史模型
历史摘要最多返回最近 20 个一键生成任务,字段如下:
| 字段 | 用途 |
| --- | --- |
| `taskId` / `projectId` | 任务定位与用户隔离 |
| `status` / `progress` / `currentStep` | 当前或最终状态 |
| `errorMessage` | 脱敏失败摘要 |
| `attempts` / `maxAttempts` | 尝试信息 |
| `latestAttemptNo` | 阶段账本中的最近尝试 |
| `failedStage` | 最近一次失败阶段 |
| `lastCompletedStage` | 最近完成阶段 |
| `resumeStage` | 再次执行时的起点 |
| `resumeFromCheckpoint` | 是否复用不可变检查点 |
| `canRetry` | 当前状态是否允许继续 |
| `createTime` / `updateTime` | 历史排序与展示 |
响应不得包含 `requestPayload``resultPayload`、Checkpoint Payload、Prompt、模型凭据或完整异常堆栈。
## 5. 恢复规则
恢复规则必须与 `OneClickProjectGenerationServiceImpl` 的真实行为一致:
1. 存在 `SOURCE_PREVIEW` 检查点时,从 `RUN_PREVIEW` 继续,保留已生成源码。
2. 否则存在 `DATABASE_LOOP_AUDIT` 检查点时,从 `BUSINESS_BLUEPRINT` 继续,保留应用蓝图、闭环计划和数据库设计。
3. 没有可用检查点时,从 `PROJECT_PREPARE` 重新开始。
4. 只有 `FAILED``RETRY_WAITING` 状态允许人工继续。
5. 同项目已有另一个同类型活动任务时,服务端拒绝重试旧任务,避免两个任务并发修改同一项目。
## 6. API
新增:
```http
GET /front/project/{projectId}/ai-tasks/one-click-history
```
接口使用当前登录用户身份,先验证项目所有权,再返回最近 20 条轻量摘要。
继续生成复用现有接口:
```http
POST /front/project/{projectId}/ai-tasks/{taskId}/retry
```
不新增“指定阶段重试”参数。恢复起点由服务器根据可信检查点计算,浏览器不能伪造。
## 7. 前端交互
- 项目列表的一键生成单元格增加“生成记录”时钟图标,打开生成工作台并携带 `history=1`
- 一键生成主按钮区域增加“生成记录”入口。
- 历史使用独立 Drawer 组件,按时间倒序展示,不把更多逻辑继续堆进 `GenerateView.vue` 模板。
- “查看任务”加载完整任务并同步路由 `taskId`
- “从最近阶段继续”先显示恢复位置和额度提示,确认后调用现有重试接口。
- 当前项目有活动任务时,历史中的继续按钮禁用;服务端仍执行最终并发校验。
## 8. 错误与并发
- 历史接口失败只影响 Drawer不影响项目工作台和当前任务。
- 历史为空时显示明确空状态。
- 选择另一个历史任务前使旧轮询失效,避免旧任务覆盖当前任务。
- 重试成功但历史刷新失败时,保留“任务已开始”的成功事实,只提示历史刷新失败。
- 项目切换或 Drawer 关闭时清理 `history` 路由参数,不删除任务状态。
## 9. 验收标准
- 历史接口只查询当前用户拥有的项目,最多返回 20 条。
- 历史列表不读取或返回请求、结果和检查点 Payload。
- 失败阶段、最后完成阶段和恢复位置与阶段/检查点账本一致。
- 从历史继续时复用原任务和原请求,服务器决定恢复起点。
- 同项目存在另一个活动一键任务时,重试被拒绝。
- 项目列表和生成工作台均可打开历史;查看任务和继续生成均可跨刷新恢复。
- 后端聚焦测试、前端契约测试、管理端测试和生产构建通过。
## 10. 实施结果
- 新增最近 20 条一键生成任务历史接口,仅返回轻量任务、阶段和恢复摘要。
- 项目列表和一键生成工作台均已接入“生成记录”入口,历史抽屉支持刷新、空状态、失败诊断、查看指定任务和继续生成。
- 恢复提示严格对应现有检查点规则:源码检查点继续运行预览,数据库闭环检查点继续业务蓝图,否则重新准备项目。
- 历史重试复用原任务与原请求,浏览器不传恢复阶段;同项目存在另一个活动任务时,服务端在额度预留前拒绝冲突重试。
- 成功或活动任务不会展示旧失败阶段与恢复提示,避免把前一次尝试的失败误报为当前状态。
- 验证通过:后端聚焦 19 项、控制器聚焦 13 项、管理端全量 40 项、前端主流程 53 项EasyCode 生产构建通过。
- 本轮没有新增数据库表或迁移,也没有修改开发阶段暂缓处理的明文凭据与 TLS 校验配置。

View File

@@ -0,0 +1,108 @@
# 项目一键生成任务总览与跨会话恢复设计
**状态:** 已确认并实施
## 1. 背景
一键生成页已经能够展示生成阶段、进度、失败原因、重试、源码预览和下载,但任务恢复依赖浏览器 `localStorage`。用户清理缓存、换浏览器或从“我的项目”重新进入时,无法可靠定位服务器上的生成任务。
“我的项目”也只展示草稿、已生成数据库和已预览等静态状态,不能回答三个最重要的问题:项目是否正在生成、生成到了哪一步、点击后能否继续查看同一个任务。
本轮回到 AI Software Factory 的用户主流程,不继续扩展 Plugin 交付基础设施。
## 2. 目标
- “我的项目”展示每个项目最新的一键生成任务状态、进度和当前步骤。
- 用户点击进度入口后,生成页通过服务器任务 ID 恢复同一个任务。
- 新建一键生成任务后立即把任务 ID 写入路由,刷新或分享当前地址仍能定位任务。
- `localStorage` 继续作为旧入口的兼容兜底,但不再是唯一任务来源。
- 总览接口保持轻量,不返回请求提示词、生成结果、阶段清单或检查点大字段。
- 生成进度接口失败时,“我的项目”仍能正常展示项目数据。
## 3. 非目标
- 不修改一键生成 Pipeline、Stage Handler、Prompt 或模型调用语义。
- 不新增数据库表或执行数据库迁移。
- 不在项目列表直接执行重试、取消、预览或下载;这些操作仍由生成工作台承接。
- 明文开发凭据和跳过 TLS 校验按用户决定继续暂缓,本轮不修改相关配置。
## 4. 用户体验
“我的项目”新增“一键生成”列。每行固定展示状态文本、进度条、当前步骤或失败原因,以及一个紧凑图标入口。没有任务时入口用于开始生成;运行中用于查看进度;失败或等待重试时用于恢复处理;成功后用于查看结果。
入口路由格式为:
```text
/generate?projectId={projectId}&mode=one-click&taskId={taskId}
```
没有历史任务时省略 `taskId`。生成页优先按路由任务 ID 查询服务器;没有路由任务 ID 时才读取本地缓存中的旧任务记录。
## 5. 服务端契约
新增接口:
```http
GET /front/project/ai-tasks/one-click-overview
```
接口从当前登录用户身份取数,并为每个项目返回 `generate_type = one_click_project` 的最新任务。最新任务按递增 `task_id` 判定,重试仍更新同一任务,不会制造额外列表项。
响应项只包含:
| 字段 | 用途 |
| --- | --- |
| `taskId` | 恢复具体任务 |
| `projectId` | 与项目列表关联 |
| `generateType` | 防止任务类型混用 |
| `status` | 状态展示与动作判断 |
| `progress` | 进度条 |
| `currentStep` | 当前阶段摘要 |
| `errorMessage` | 失败摘要 |
| `attempts` / `maxAttempts` | 重试信息 |
| `createTime` / `updateTime` | 排序和审计摘要 |
`requestPayload``resultPayload`、Prompt 身份、Stage Manifest 和 Checkpoint 不进入该响应。
## 6. 数据流
```mermaid
sequenceDiagram
participant U as 用户
participant L as 我的项目
participant A as FrontProject API
participant G as 生成工作台
U->>L: 打开项目列表
par 项目数据
L->>A: GET /front/project
and 最新一键任务
L->>A: GET /front/project/ai-tasks/one-click-overview
end
A-->>L: 项目与轻量任务总览
U->>L: 点击生成进度入口
L->>G: projectId + mode + taskId
G->>A: GET /front/project/{projectId}/ai-tasks/{taskId}
A-->>G: 完整任务状态
G->>G: 恢复进度、结果或轮询
```
## 7. 错误与并发处理
- 总览接口失败时只提示“生成进度暂时无法加载”,项目列表继续使用项目接口结果。
- 路由任务不存在、无权访问或类型不是一键生成时,生成页保留项目草稿并显示恢复错误。
- 项目切换时取消旧轮询;任务路由监听会忽略正在加载的项目和当前已显示的同一任务,避免重复轮询。
- 同一项目切换到另一个任务时先使旧轮询失效;即使新目标已经终态,旧轮询也不能覆盖路由指定任务。
- 新任务创建后先写入当前任务状态和本地兼容缓存,再同步路由并开始轮询。
- 路由同步失败不会中止已经在服务器创建的生成任务。
## 8. 验收标准
- 项目列表一次加载即可展示每个项目最新的一键生成状态。
- 点击列表入口后,地址包含对应 `projectId``taskId`
- 清除本地任务缓存后,使用该地址仍能从服务器恢复任务。
- 新建任务后地址中的 `taskId` 更新为新任务,不会在刷新后恢复旧任务。
- 同一项目切换不同 `taskId` 后,旧轮询不能覆盖当前任务或清除新任务的生成状态。
- 总览响应不包含请求和结果 Payload。
- 进度接口失败不影响项目列表主体。
- 前端契约测试、后端服务与控制器测试、生产构建全部通过。

View File

@@ -0,0 +1,72 @@
# Plugin Delivery Rehearsal Run Design
## Context
P2-D8e2h introduced a server-authoritative delivery acceptance report, but that report was transient. Operators could not prove which routing, environment, target ledger state, or approval policy they had reviewed before a deployment rehearsal. The next step must preserve that evidence without implying that a real target mutation already happened.
P2-D8e2i therefore adds an append-only `PLAN_ONLY` rehearsal Run. It freezes the non-secret acceptance response and an ordered scenario contract in the control database. It does not publish a Plugin, create an approval, enqueue an Outbox command, or execute target DML.
## Run Contract
Each `factory_plugin_delivery_rehearsal_run` row records:
- a unique SHA-256 Run key and schema version `1.0`;
- mode `PLAN_ONLY`;
- status `READY` when all acceptance checks pass, otherwise `BLOCKED`;
- frozen control and target environment codes, target catalog, and routing fingerprint;
- canonical acceptance JSON plus its SHA-256 fingerprint;
- an ordered scenario fingerprint;
- frozen passed/total check counts and scenario count;
- authenticated creator and creation time.
`READY` means the captured prerequisites permit a future isolated rehearsal. It does not mean that any rehearsal scenario has executed or succeeded. `BLOCKED` Runs are still persisted so configuration changes and failed readiness reviews remain auditable.
The Mapper intentionally exposes inserts and reads only. There is no application update or delete operation for either table.
## Scenario Contract
Every Run freezes the same six ordered scenarios in `factory_plugin_delivery_rehearsal_scenario`:
1. create and approve a separation-of-duties request;
2. expire pending or approved evidence;
3. revoke pending or approved evidence;
4. execute mixed SQL, permission, and menu apply routes;
5. preserve a failure and perform an idempotent retry;
6. compensate in reverse order while verifying ownership and receipts.
When the acceptance snapshot is ready, scenarios are `PLANNED`. Otherwise they are `BLOCKED`. No `PASSED`, `FAILED`, or execution receipt state exists in this phase because the service does not run those scenarios.
Each scenario stores the evidence required for eventual completion. Its fingerprint projection excludes database IDs and includes sequence, stable code, name, state, evidence, and planning result, so the same projection can be verified after reloading generated keys.
## Integrity Verification
Creation uses the existing canonical JSON codec and SHA-256 service. The acceptance fingerprint hashes the exact persisted canonical JSON. The scenario fingerprint hashes a canonical ordered projection of all six rows.
Detail reads fail closed unless all of the following match:
- acceptance JSON fingerprint;
- schema version and `PLAN_ONLY` mode;
- status derived from the frozen acceptance snapshot;
- control environment, target environment, target catalog, and routing fingerprint;
- passed/total check counts;
- scenario row count and scenario fingerprint.
The raw snapshot JSON is ignored by the REST serializer. Detail responses expose the parsed non-secret acceptance object only after integrity verification.
## API And UI
All endpoints reuse `generator:delivery:verify`:
- `POST /generator/plugin/delivery-rehearsals` creates a local plan snapshot;
- `GET /generator/plugin/delivery-rehearsals` returns the 50 most recent summaries;
- `GET /generator/plugin/delivery-rehearsals/{rehearsalId}` verifies and returns one full report.
The delivery page can create a snapshot, inspect recent Runs, and open a report containing the two integrity fingerprints, frozen checks, and ordered scenarios. It deliberately exposes no rehearsal execution command.
## Boundaries
- Creating a Run writes only the control database rehearsal tables. The existing acceptance service may perform its configured read-only readiness probe, but no target DML is issued.
- The snapshot contains no JDBC URL, username, password, SQLState, or raw target exception.
- This phase does not reserve an environment, lock a Transition, or grant future execution authority. A later execution must repeat preflight and approval validation.
- Switching from `PLAN_ONLY` to an isolated MySQL rehearsal requires an explicit target authorization and a separate execution-state design with per-scenario receipts.
- No external MySQL instance was connected or modified while implementing this stage. Plaintext development credentials and TLS verification remain deferred by explicit user decision.

View File

@@ -0,0 +1,115 @@
# Plugin Executor Routing And Approval Design
## Context
P2-D8e2c proves one real SQL delivery Plugin, but execution still chooses one global Executor code for an entire batch. Every step receipt copies that value and the Worker ignores the persisted receipt code when dispatching. A batch containing SQL plus permission or menu contributions therefore cannot use different adapters.
Real execution also records only the operator. It does not freeze the environment/routing configuration that was approved, nor persist a reason proving that the operator acknowledged the exact Transition and route policy.
P2-D8e2d adds a per-step routing snapshot and environment-level approval evidence while retaining the current dry-run behavior by default.
## Routing Configuration
`factory.plugin-execution.executor-code` remains the explicit default route for backward compatibility. The following new map can override it by normalized contribution type and target:
```yaml
factory:
plugin-execution:
environment-code: development
approval-required-for-non-dry-run: true
executor-routes:
database-migration-sql: sql-jdbc:v1
permission-backend: permission-api:v1
menu-admin-frontend: menu-api:v1
```
The route key is `lower-kebab(type + '-' + target)`. A missing or blank map value inherits the configured default route. This inheritance is resolved only while creating an execution batch. It is not a runtime failure fallback.
The default configuration remains `dry-run:v1`, environment `development`, no explicit route overrides, and approval required for every non-dry-run route.
## Routing Snapshot
`PluginExecutionRoutingPolicy` resolves one Executor code for each trusted contribution. Before any execution, it asks the installed Executor Registry to prove the selected code exists and supports the contribution type/target. Unknown or unsupported routes fail the enqueue transaction.
The policy computes a stable SHA-256 configuration fingerprint from:
- execution environment code;
- default Executor code;
- sorted non-empty route overrides;
- non-dry-run approval policy.
Each step receipt persists its resolved Executor code. The execution batch persists environment code, routing fingerprint, and a summary code: the concrete code when every step uses the same adapter, or `routed:v1` for a mixed batch. Zero-step batches retain the configured default code and never require approval.
The execution idempotency identity includes environment and routing fingerprint. The Worker dispatches with `receipt.executorCode`; it never re-reads route configuration and never falls back after a target error. Redelivery therefore uses the same adapter snapshot even when application configuration changes.
## Approval Contract
The execute, retry, and compensate APIs accept an optional request body:
```json
{
"expectedTransitionFingerprint": "...",
"expectedRoutingFingerprint": "...",
"environmentCode": "preview",
"approvalReason": "Reviewed preview deployment"
}
```
When every selected step uses `dry-run:v1`, the body remains optional and existing clients continue to work.
When at least one selected step uses a non-dry-run Executor and approval is enabled, the server requires:
- exact current Transition fingerprint;
- exact current routing configuration fingerprint;
- exact configured environment code;
- a non-blank approval reason of at most 500 characters;
- a non-blank authenticated operator.
Mismatches fail before execution, receipts, or Outbox rows are inserted. Each accepted attempt persists whether approval was required, approved operator/time/reason, environment, and routing fingerprint. Retry and compensation are new attempts and therefore require fresh evidence against their current route snapshot.
## Persistence
`factory_plugin_transition_execution` gains:
- `environment_code`;
- `routing_fingerprint`;
- `approval_required`;
- `approved_by`;
- `approved_at`;
- `approval_reason`.
The control schema upgrade adds these columns conditionally for existing MySQL 5.7 databases. Both full schema scripts include them directly. Step receipts already contain `executor_code`, so no second route table is required.
## Registry Status And UI
Registry status exposes only non-secret routing policy:
- execution environment code;
- routing fingerprint;
- approval-required flag;
- whether any non-dry-run route is configured;
- non-empty route overrides plus the existing default Executor code.
The Plugin page displays environment and a short routing fingerprint. When real execution is configured and approval is required, execution actions request an approval reason and send the exact Transition/routing/environment identities. Execution history displays environment, routing fingerprint, approval evidence, and the per-step Executor code.
No JDBC secret or target credential is added to these responses.
## Compatibility
- Default dry-run execution still accepts an empty request body.
- Existing `FACTORY_PLUGIN_EXECUTOR_CODE=sql-jdbc:v1` still selects SQL execution through the default route, but a mixed batch remains invalid unless explicit routes are configured.
- New deployments should keep the default route as dry-run and set only `FACTORY_PLUGIN_DATABASE_MIGRATION_SQL_EXECUTOR_CODE=sql-jdbc:v1`; this allows permission/menu steps to remain explicitly dry-run until real adapters exist.
- Existing queued executions retain the receipt Executor already stored in the database.
## Testing
- Routing tests cover normalized keys, mixed routes, stable fingerprints, missing/unsupported adapters, default inheritance, and no runtime fallback.
- Execution service tests prove per-step codes and approval evidence are frozen before Outbox insertion.
- Worker tests prove dispatch uses the receipt code after configuration changes.
- Schema/Mapper tests cover every approval column.
- Controller and frontend tests cover optional dry-run bodies, required approval payloads, route identity, and history rendering.
- Existing Plugin, generator, admin, and frontend regressions remain required.
## Scope
This stage creates the routing and approval substrate. It does not implement real permission/menu adapters, multi-person approval, persisted probe results, approval expiration, external secret storage, or production deployment. Plaintext development credentials and TLS verification settings remain deferred by explicit user decision.

View File

@@ -0,0 +1,68 @@
# Plugin Local H2 Delivery Rehearsal Design
## Context
P2-D8e2i persisted a trustworthy `PLAN_ONLY` Run, but its six scenarios were contracts rather than executions. P2-D8e2j executes those contracts without crossing the external-target authorization boundary.
Every Attempt receives a fresh in-memory H2 database in MySQL compatibility mode. The sandbox reuses the production SQL, permission, and menu JDBC Executors and the installed trusted baseline Plugin resources. Only Attempt and Receipt evidence is written to the control database. The H2 database disappears when its keeper connection closes.
## Attempt State
`factory_plugin_delivery_rehearsal_attempt` binds an execution to the immutable parent Run through copies of the Run key, acceptance fingerprint, and scenario fingerprint. It also stores:
- monotonic Attempt number and unique SHA-256 Attempt key;
- mode `LOCAL_H2` and a non-secret sandbox fingerprint;
- optional `retry_of_attempt_id`;
- `PENDING`, `RUNNING`, `SUCCEEDED`, `FAILED`, or `ABORTED` state;
- expected and succeeded Receipt counts;
- authenticated operator, timestamps, duration, and sanitized terminal error.
A replay reference is accepted only when the referenced Attempt belongs to the same Run and is `FAILED` or `ABORTED`. Replay creates a new sandbox and a new Attempt; it never pretends to resume the destroyed in-memory database.
`PENDING` and `RUNNING` Attempts can be manually aborted. Mapper updates include current-state predicates, so an abort and a concurrent completion cannot both win.
## Scenario Receipts
`factory_plugin_delivery_rehearsal_receipt` freezes the Scenario ID, sequence, code, and name for one Attempt. Receipt states are `PENDING`, `RUNNING`, `SUCCEEDED`, `FAILED`, and `ABORTED`.
Successful and failed Receipts persist canonical non-secret evidence JSON plus SHA-256. Detail reads verify:
- parent Run and Attempt fingerprint binding;
- Receipt count and ordered Scenario identity;
- evidence JSON fingerprint;
- succeeded count and terminal Attempt consistency.
Raw evidence JSON is ignored by REST serialization. Parsed evidence is returned only after verification. Pending, running, and aborted Receipts must not contain evidence payloads.
## Local H2 Scenarios
The Session enforces the six Scenario codes in frozen order:
1. **Approval request:** applies the real approval policy to synthetic requester/approver identities, records `PENDING -> APPROVED`, and proves distinct actors, role snapshots, reasons, and expiries.
2. **Approval expiry:** records an expired request and executes the deadline predicate that transitions it to `EXPIRED`.
3. **Approval revocation:** records an approved request, validates the revoker role and reason, and transitions it to `REVOKED`.
4. **Mixed apply:** executes the trusted database baseline through `sql-jdbc:v1`, the permission document through `permission-jdbc:v1`, and the menu document through `menu-jdbc:v1`; then verifies marker, ownership, role grants, and target Receipts.
5. **Failure retry:** submits an `INSERT IGNORE` into a deliberately absent table. The SQL Executor first leaves a `FAILED` target Receipt, the sandbox creates the missing table, and the exact same contribution and idempotency key are retried. Evidence requires `execution_count=2`, one marker row, and stable duplicate receipt identity.
6. **Reverse compensation:** runs menu, permission, and SQL rollback in that order. Evidence requires both ownership tombstones to be `REMOVED`, the baseline marker table to be absent, seven succeeded target Receipts, and no remaining failed target Receipt.
The H2 URL, internal username, and database name are never persisted or returned. Only the sandbox fingerprint is public.
## API And UI
All endpoints require `generator:delivery:verify`:
- `POST /generator/plugin/delivery-rehearsals/{rehearsalId}/attempts` starts a fresh or linked replay Attempt;
- `GET /generator/plugin/delivery-rehearsals/{rehearsalId}/attempts` lists summaries;
- `GET /generator/plugin/delivery-rehearsals/{rehearsalId}/attempts/{attemptId}` verifies and returns evidence;
- `POST /generator/plugin/delivery-rehearsals/{rehearsalId}/attempts/{attemptId}/abort` aborts an open Attempt.
Execution is synchronous in this phase. The delivery report shows Attempt history, progress, replay source, duration, and actions. A separate evidence dialog uses expandable rows so canonical evidence does not overwhelm the operational table.
## Security And Boundaries
- No arbitrary SQL, document, Plugin code, JDBC URL, or sandbox option comes from the request. The Session resolves only the three installed trusted baseline releases and one platform-owned retry statement.
- H2 is packaged as a runtime dependency solely for the local rehearsal Session. The normal configured target and its connection factory are not used.
- Synthetic approval identities prove policy mechanics; they are not real human approval evidence and cannot authorize a Plugin Transition.
- A process stop can leave an Attempt open in the control database. Because the H2 database is ephemeral, recovery is an explicit abort followed by a fresh Attempt, not same-process continuation.
- The synchronous API is suitable for the current six short scenarios, not long deployment workloads. Durable asynchronous leasing remains a later stage.
- No external MySQL instance was connected or modified. Plaintext development credentials and TLS verification remain deferred by explicit user decision.

View File

@@ -0,0 +1,107 @@
# Plugin Menu Target Executor Design
## Context
P2-D8e2e delivered `permission-jdbc:v1`, but its execution context did not carry the environment frozen in the approved execution batch. The JDBC adapters trusted only the locally configured target environment, so an operator could approve one environment while a changed connection configuration pointed at another. Menu contributions also had no real target adapter.
P2-D8e2f closes both gaps. Every JDBC adapter now requires the approved execution environment to exactly equal the isolated JDBC target environment before opening a connection. A new `menu-jdbc:v1` adapter then installs one narrow RuoYi page menu, records target ownership, grants existing roles, and conservatively compensates only resources it owns.
## Approved Environment Gate
`PluginTransitionOutboxWorker` copies `PluginTransitionExecution.environmentCode` into every `PluginContributionExecutionContext`. `PluginJdbcTargetEnvironmentPolicy` compares that immutable value with `factory.plugin-execution.sql-target.environment-code`.
The comparison is exact and happens before a JDBC connection is opened. A blank legacy value, a different environment, or a changed target configuration fails with a sanitized environment-mismatch error. `sql-jdbc:v1`, `permission-jdbc:v1`, and `menu-jdbc:v1` all share this policy.
## Menu Document
Each `MENU + admin_frontend` contribution contains exactly one JSON document:
```json
{
"schemaVersion": "1.0",
"menuKey": "menu:delivery-verification",
"displayName": "Delivery verification",
"parentPath": "generator",
"path": "deliveryVerification",
"component": "generator/delivery/index",
"routeName": "DeliveryVerification",
"icon": "check",
"orderNum": 16,
"requiredPermissionCode": "generator:delivery:verify",
"roleKeys": ["common"]
}
```
Validation is strict:
- only the published fields are accepted and the document is bounded to 64 KiB;
- `schemaVersion` must be `1.0` and `menuKey` must equal the immutable contribution key;
- parent and child paths are bounded route segments and cannot be equal;
- the component is a relative repository-style path ending in `/index`; traversal and URLs are rejected;
- route name, icon, order number, and display name have explicit formats and size limits;
- the required permission is a three-part lowercase permission identifier with no wildcard;
- one to 20 unique lowercase role keys are allowed.
Role keys are sorted before computing the semantic SHA-256. Apply and rollback resources may differ in formatting or role order, but must describe the same menu identity.
## Permission Dependency
A page menu does not duplicate the required permission in its own `sys_menu.perms` field. The page row uses an empty permission value, while `requiredPermissionCode` must resolve to an `ACTIVE` row in `factory_plugin_target_permission` whose target menu still exists as an enabled `F` button with the same permission code.
This preserves the permission adapter's unique permission identity and makes dependency order explicit. The menu adapter locks the menu ownership row before the permission ownership row in both apply and compensation, avoiding reverse lock order. The first menu Plugin also declares a manifest dependency on `delivery.preview-permission@^1.0.0`.
## Apply Semantics
After claiming the generic target receipt lease, the adapter runs one target transaction:
1. Resolve one enabled parent menu by stable `parentPath`.
2. Lock any existing menu ownership row.
3. Lock and verify the active factory-owned permission dependency.
4. Resolve every declared active role key and require both the complete parent-menu ancestry grant and the required permission grant.
5. Reject unowned path or route-name collisions.
6. Create one enabled RuoYi page menu (`menu_type = 'C'`) with fixed platform-owned field semantics.
7. Persist Plugin/version/contribution identity, semantic document fingerprint, resolved parent and permission IDs, generated menu ID, and `ACTIVE` status.
8. Insert only the declared role grants and matching ownership rows.
An already active ownership row is treated as a replay only after every immutable field, route identity, target menu field, role ID, owned grant, parent ID, and permission dependency has been revalidated. Drift is never overwritten.
## Compensation Semantics
Compensation validates the complete target state before deleting anything. It refuses when:
- Plugin owner, contribution key, version, or semantic document identity differs;
- the parent menu or required permission has moved, disappeared, or become inactive;
- the page menu route or managed fields have drifted;
- any owned role grant is missing;
- a declared role loses parent-menu ancestry or the required permission grant;
- an unmanaged role grant or child menu has been added.
On success, one transaction deletes only owned role grants and the owned page row, clears active role ownership rows, and marks the menu ownership `REMOVED`. The tombstone supports duplicate compensation and later reactivation by the same Plugin/contribution identity.
## Receipts And Failures
The external receipt is deterministic: `menu:<environment>:<step-idempotency-key>`. Duplicate `SUCCEEDED` delivery returns the stored receipt without touching menu data. Active target leases cannot be stolen; failed or expired attempts can be reclaimed through the shared target ledger.
All repository statements are fixed prepared statements owned by the platform. Plugins cannot supply SQL, arbitrary menu columns, target IDs, usernames, or raw error text.
## First Menu Delivery
`delivery.preview-menu@1.0.0` publishes the reviewed `menu:delivery-verification` contribution and an equivalent rollback document. Its H2 MySQL rehearsal first applies `delivery.preview-permission`, then proves Registry publication, Plan 1.2 resolution, menu apply, duplicate delivery, role visibility, reverse-order menu compensation, and final permission compensation.
The declared component is real. `generator/delivery/index.vue` calls a dedicated `GET /generator/plugin/delivery-verification` endpoint protected by `generator:delivery:verify`. The endpoint returns only execution environment, routing fingerprint, Executor routes, approval policy, installed Plugin count, and the non-secret JDBC target probe status.
## Probe And UI
When `menu-jdbc:v1` is selected by the default or route map, the manual JDBC probe requires the generic receipt ledger, both permission ownership tables, and both menu ownership tables. Missing menu tables return `MENU_LEDGER_UNAVAILABLE`; permission dependency failures remain distinguishable as `PERMISSION_LEDGER_UNAVAILABLE`.
The Plugin page shows permission and menu ownership readiness and treats all three JDBC adapters as sharing the isolated target configuration. The delivery verification page presents the frozen routing snapshot and target ledgers without exposing JDBC URL, credentials, driver exceptions, or SQLState.
## Compatibility And Scope
- Default routing remains `dry-run:v1`; real menu delivery requires an explicit `menu-admin-frontend` route.
- SQL-only and permission-only installations do not require menu ownership tables.
- The existing `sql-target` configuration prefix is retained for compatibility even though it now serves all JDBC adapters.
- The adapter creates one page menu under an existing parent, grants existing roles, and depends on an existing factory-owned permission. Each role must already own the full parent-menu ancestry and the required permission button. It does not create roles, shared parent grants, directories, frontend bundles, users, arbitrary menu trees, or external URLs.
- Compensation favors audit safety over forced cleanup and will not delete unmanaged grants or child menus.
- No external MySQL target was configured or changed in this stage; database integration tests use H2 in MySQL mode.
- Plaintext development credentials and TLS verification remain deferred by explicit user decision.

View File

@@ -0,0 +1,97 @@
# Plugin Permission Target Executor Design
## Context
P2-D8e2d can route each contribution to a frozen Executor and persist approval evidence, but only `sql-jdbc:v1` changes a real target. Permission contributions still use `dry-run:v1`; configuring the reserved permission route to any other code fails because no adapter is installed.
P2-D8e2e introduces `permission-jdbc:v1` for RuoYi-compatible target databases. It consumes a narrow structured document, creates one Plugin-owned button permission, grants it to named roles, records target ownership, and safely compensates only the resources it owns.
## Target And Isolation
The adapter reuses the existing isolated JDBC target configuration and connection factory:
- `factory.plugin-execution.sql-target` supplies environment, catalog, connection, and lease settings;
- `factory_plugin_target_receipt` remains the durable per-step lease and idempotency ledger;
- the target must be separate from the Factory control database;
- `sys_menu`, `sys_role`, and `sys_role_menu` must be RuoYi-compatible;
- permission ownership tables must be installed by the updated target bootstrap script.
The adapter never executes SQL supplied by a Plugin. All statements are fixed prepared statements owned by the platform.
## Permission Document
Each `PERMISSION + backend` contribution contains exactly one JSON document:
```json
{
"schemaVersion": "1.0",
"permissionCode": "generator:delivery:verify",
"displayName": "交付验证",
"parentPermissionCode": "generator:plugin:list",
"roleKeys": ["common"]
}
```
Validation is strict:
- only the five published fields are accepted;
- `schemaVersion` must be `1.0`;
- `permissionCode` must equal the immutable contribution key and use a three-part lowercase permission identifier;
- wildcard permission identifiers are rejected;
- `parentPermissionCode` must identify a different existing active menu;
- display name is non-blank and at most 50 characters;
- one to 20 unique lowercase role keys are allowed;
- the document is bounded to 64 KiB.
Role keys are sorted before computing a semantic document fingerprint. Apply and rollback resources may differ in whitespace or role ordering, but must describe the same semantic permission identity.
## Apply Semantics
After claiming the generic target receipt lease, the adapter runs one database transaction:
1. Resolve exactly one active parent menu by `parentPermissionCode`.
2. Resolve exactly one active `sys_role` row for every role key.
3. Lock the ownership row for `permissionCode`.
4. Reject an existing unowned `sys_menu.perms` collision.
5. Create a button (`menu_type = 'F'`) with a generated target-local `menu_id`.
6. Persist the Plugin owner, contribution key, semantic fingerprint, parent identity, generated menu ID, and `ACTIVE` status.
7. Insert only the declared `sys_role_menu` grants and matching ownership rows.
An interrupted attempt can be reclaimed through the existing lease. If the ownership row is already `ACTIVE`, every immutable identity, menu field, owned role grant, and role ID must still match before the attempt can complete. Drift is rejected rather than overwritten.
## Compensation Semantics
Compensation locks and validates the ownership row before deletion. It refuses to continue when:
- the permission is not owned by the same Plugin and contribution key;
- the semantic document fingerprint differs;
- the target menu or an owned role grant has drifted;
- another role was granted the Plugin-owned menu outside the ownership ledger;
- the button unexpectedly has child menus.
When validation succeeds, one transaction deletes the owned role grants and button menu, removes active grant rows, and marks the permission ownership row `REMOVED`. The tombstone makes an interrupted compensation retry idempotent and preserves audit identity. A later apply by the same Plugin/contribution key may reactivate the permission with a new target menu ID after the prior version has been compensated.
## Receipts And Failures
The external receipt is deterministic: `permission:<environment>:<step-idempotency-key>`. Duplicate `SUCCEEDED` delivery returns the stored receipt without touching target authorization rows. Active leases cannot be stolen; failed or expired attempts can be reclaimed.
All target errors are sanitized. JDBC URLs, credentials, SQL details, role IDs, and driver exceptions are not returned through control-side execution history.
## First Delivery Plugin
`delivery.preview-permission@1.0.0` publishes the reviewed `generator:delivery:verify` permission document and its compensation resource. The rehearsal target supplies an existing `generator:plugin:list` parent and `common` role, then proves publish-plan resolution, apply, duplicate delivery, role authorization, compensation, and duplicate compensation against H2 in MySQL mode.
## Probe And UI
When `permission-jdbc:v1` is selected by the default or route map, the existing manual JDBC target probe additionally checks both permission ownership tables. A missing permission schema has its own non-secret state and blocks a READY result. The Plugin page recognizes that state and treats both SQL and permission JDBC adapters as requiring the same isolated target readiness.
## Compatibility And Scope
- Default routing stays `dry-run:v1`.
- SQL-only installations do not require permission ownership tables unless `permission-jdbc:v1` is configured.
- Existing SQL receipts and SQL Executor behavior are unchanged.
- This stage does not create directory/menu pages, backend endpoints, users, roles, or arbitrary permission hierarchies.
- It does not adopt pre-existing permission rows or delete unmanaged role grants.
- Multi-person approval, external tickets, permission cache invalidation across a running target cluster, and the real admin-menu adapter remain later work.
- Plaintext development credentials and TLS verification remain deferred by explicit user decision.

View File

@@ -0,0 +1,80 @@
# Plugin Rehearsal Durable Outbox And Release Gate Design
## Context
P2-D8e2j executes six local H2 delivery scenarios and persists verified evidence, but the POST request remains open until all scenarios finish. A process exit can leave a RUNNING Attempt whose in-memory database no longer exists. The delivery report also shows individual Attempt evidence without a stable comparison or release decision.
P2-D8e2k moves local rehearsal execution behind a durable Outbox, adds lease heartbeat and takeover, and derives an integrity-checked release gate from successful Attempt evidence. External MySQL remains outside this scope.
## Durable Command
Each Attempt owns exactly one `factory_plugin_delivery_rehearsal_outbox` row.
- Statuses are `PENDING`, `PROCESSING`, `DELIVERED`, `FAILED`, and `ABORTED`.
- `available_at` controls initial delivery; `lease_owner`, private `lease_token`, `lease_until`, and `last_heartbeat_at` describe a fenced claim.
- `delivery_count` records first delivery and every takeover.
- Attempt, six Receipt rows, and the PENDING Outbox row are inserted in one transaction. The HTTP request returns immediately and never opens H2.
Claiming uses an atomic conditional update. A PENDING row is claimable after `available_at`; a PROCESSING row is claimable only after `lease_until`. All heartbeat, Receipt completion, Attempt completion, and Outbox completion mutations require the current lease token.
The lock order is Attempt then Outbox for claim, terminal completion/failure, abort, and reconciliation. This avoids the worker and operator paths acquiring the same records in opposite order.
## Heartbeat And Takeover
The Worker polls with the existing bounded Plugin Outbox settings. It renews the lease before every control-plane transition and schedules a periodic heartbeat at one third of the lease duration while a scenario is running.
An H2 database cannot survive a worker process exit. Therefore takeover does not resume at the first unfinished Receipt:
1. Claim the expired PROCESSING Outbox with a new fencing token.
2. Reset the non-terminal Attempt and all provisional Receipt state.
3. Create a new isolated H2 database from the same frozen Run and Attempt identity.
4. Replay all six scenarios from the beginning.
Previously persisted evidence on a non-terminal Attempt is provisional and is replaced during takeover. `delivery_count` and heartbeat history retain the recovery signal. Terminal Attempt evidence remains immutable.
An operator may reconcile only an expired PROCESSING lease. An active lease cannot be stolen. Reconciliation returns the command to PENDING; the normal Worker then claims it. Aborting a PENDING or RUNNING Attempt atomically marks open Receipts and the Outbox ABORTED, clears the token, and fences the active Worker.
## Attempt Comparison
Raw `evidence_fingerprint` remains the per-Attempt integrity check. Raw evidence intentionally differs because it includes sandbox identity, external target Receipts, and idempotency keys.
Comparison calculates a second, non-persisted semantic fingerprint after recursively removing volatile transport identity fields:
- sandbox and execution identity;
- external target Receipt payloads;
- stable step/idempotency keys;
- generated approval/Attempt/Receipt IDs.
Scenario order, scenario code, status, and all remaining evidence must match. Both Attempts must be complete and successful. The report exposes raw and semantic fingerprints but never raw database connection details or the lease token.
## Release Gate
The release gate evaluates the latest Attempt for one immutable Run:
1. The parent Run is READY.
2. A latest Attempt exists and is SUCCEEDED.
3. Its Outbox is DELIVERED.
4. All six Receipt evidence fingerprints pass integrity verification.
5. Evidence semantics match the previous successful Attempt when one exists.
The first successful delivered Attempt establishes the initial semantic baseline. Any newer PENDING, RUNNING, FAILED, ABORTED, incomplete, tampered, or semantically drifting Attempt blocks the gate.
The gate is computed from durable source records rather than persisted as a second authority. It is a local delivery-readiness report, not permission to execute against an external target.
## API And UI
- Attempt creation now means enqueue.
- Reconcile: `POST /delivery-rehearsals/{rehearsalId}/attempts/{attemptId}/reconcile`.
- Compare: `GET /delivery-rehearsals/{rehearsalId}/attempt-comparison`.
- Gate: `GET /delivery-rehearsals/{rehearsalId}/release-gate`.
The delivery page polls an open Attempt, displays Outbox status, owner, heartbeat, deadline, and delivery count, exposes expired-lease reconciliation, renders semantic comparison, and shows gate checks before evidence history.
## Failure Boundaries
- A failed enqueue transaction creates no partial Attempt or command.
- A stale Worker cannot persist after its token is replaced or cleared.
- A crash before terminal commit leaves PROCESSING state for automatic takeover.
- A scenario failure atomically fails its Receipt, aborts later Receipts, and fails Attempt and Outbox.
- Control database migration remains required before use.
- No external MySQL connection, target selection, or target mutation is added.

View File

@@ -0,0 +1,82 @@
# Plugin Separation-of-Duties Approval Design
## Context
P2-D8e2d bound a real execution to the Transition fingerprint, routing fingerprint, and environment, but the execution operator supplied the approval reason in the same request. That evidence was durable, yet it did not represent an independent decision and had no pending lifetime, approved lifetime, or revocation lifecycle.
P2-D8e2g replaces that shortcut with persistent approval requests. A real execution can consume only one independently approved record whose complete identity still matches the current Transition and routing policy. Dry-run execution remains approval-free.
## Lifecycle
`factory_plugin_transition_approval` owns five terminal or active states:
- `PENDING`: an authorized requester submitted a bounded reason; the request remains decidable until `request_expires_at`.
- `APPROVED`: a different authorized actor approved it; the evidence remains executable until `expires_at`.
- `REVOKED`: an approver-role actor revoked a pending or approved request with a reason.
- `CONSUMED`: one execution batch atomically claimed the evidence and stored its execution ID.
- `EXPIRED`: the pending or approved lifetime elapsed, or a sibling request became obsolete after another approval was consumed.
Expiry is applied lazily under the Transition row lock before list, decision, revocation, and execution operations. State-changing SQL also repeats status and time predicates, so a stale in-memory decision cannot overwrite a concurrent terminal state.
## Frozen Identity
Every request freezes:
- Transition ID and SHA-256 fingerprint;
- effective execution mode (`APPLY`, `RETRY`, or `COMPENSATE`);
- routing configuration fingerprint;
- execution environment and summary Executor code;
- requester account, canonical sorted role snapshot, reason, request time, and request expiry.
Approval adds the approver account, canonical role snapshot, decision reason, approval time, and evidence expiry. Routing identity now includes separation policy, both TTL values, and canonical requester/approver role allowlists. Any of those configuration changes invalidates unconsumed evidence.
The execute request accepts only an `approvalId` plus the exact Transition, routing, and environment identity. The old direct `approvalReason` request field and routing-policy self-approval helper were removed.
## Actor And Role Policy
`PluginApprovalPolicy` requires a valid authenticated account and one to 50 lowercase role keys for every approval actor. Role keys are deduplicated, sorted, and frozen in a comma-separated snapshot bounded to 1,000 characters.
Two optional allowlists constrain who may request and approve. Empty lists preserve permission-based compatibility; a non-empty list requires at least one matching current role. With separation enabled by default, `requested_by` and `approved_by` must differ both at decision time and again at execution consumption time.
The independent API permission is `generator:plugin:approve`. Request and execution operations retain `generator:plugin:execute`; approve and revoke require the new permission; either permission may inspect approval history. Server-side account and role checks remain authoritative regardless of frontend visibility.
## Locking And Consumption
All lifecycle operations lock the owning Transition first. Decisions and execution then lock the approval row, preserving one lock order. Execution performs the following work in one local transaction:
1. Verify the immutable Transition plan and resolve the effective mode and trusted contributions.
2. Resolve the current route and require an exact request identity.
3. Expire stale requests and lock the selected approval.
4. Require `APPROVED`, unexpired, complete evidence with the exact mode, fingerprints, environment, and Executor.
5. Copy the complete requester and approver snapshot into the immutable execution audit row.
6. Insert the execution batch and atomically update the approval to `CONSUMED` with its execution ID.
7. Expire active sibling approvals, then create step receipts and the Outbox command.
If consumption loses a race, the transaction fails before receipts or Outbox creation. Once an execution has consumed evidence, revocation is rejected. The target-side effects still occur asynchronously through the existing leased Outbox.
## API And UI
The Plugin API adds request, list, approve, and revoke endpoints below each Transition. Registry and delivery status expose only non-secret policy settings: separation enabled, request TTL, approved-evidence TTL, and role allowlists where relevant.
The Plugin center provides a dedicated approval table. It shows lifecycle status, effective mode, requester and approver timestamps, reasons, role snapshots, expiry, revocation, consumption, and both fingerprints. A real execution no longer opens a combined approval-and-execute prompt. It searches for an approved, unexpired record matching the visible Transition, route, environment, and effective retry/compensation mode; otherwise it opens the approval table. Execution history retains the consumed evidence snapshot after the source approval later becomes terminal.
## Configuration
The defaults are environment-overridable:
- `FACTORY_PLUGIN_APPROVAL_SEPARATION_ENABLED=true`
- `FACTORY_PLUGIN_APPROVAL_REQUEST_TTL_MINUTES=1440` (bounded to 1..10080)
- `FACTORY_PLUGIN_APPROVAL_VALIDITY_MINUTES=30` (bounded to 1..1440)
- `FACTORY_PLUGIN_APPROVAL_REQUESTER_ROLE_KEYS=`
- `FACTORY_PLUGIN_APPROVAL_APPROVER_ROLE_KEYS=`
The existing `FACTORY_PLUGIN_APPROVAL_REQUIRED_FOR_NON_DRY_RUN` switch remains the outer real-execution gate.
## Compatibility And Boundaries
- Existing execution rows receive nullable/defaulted audit columns through the MySQL 5.7-compatible upgrade script. New full schemas create the approval table before new executions are accepted.
- Approval evidence is an application database record, not a cryptographic signature, external ticket, or identity-provider attestation.
- The UI conservatively opens approval workflow when Registry status reports real Executors; the service resolves each Transition's actual contributions and remains the final authority.
- This stage does not connect to or mutate an external MySQL target. SQL, permission, and menu target behavior continues to be covered by isolated H2 MySQL-mode rehearsals until a preview target is explicitly authorized.
- Plaintext development credentials and TLS verification remain deferred by explicit user decision.

View File

@@ -0,0 +1,116 @@
# Plugin SQL Target Executor Design
## Context
P2-D8e2a makes Transition execution durable and recoverable, but the only installed Executor is `dry-run:v1`. P2-D8e2b adds the first real target adapter without turning the Plugin system into an unrestricted database administration channel.
The adapter is disabled by default and points at one explicitly configured isolated MySQL catalog. It must never connect to the RuoYi control catalog, must not accept cross-catalog SQL, and must make an ambiguous Outbox redelivery safe through both a stable step idempotency key and a target-side receipt lease.
## Approaches Considered
- Dynamic Flyway migrations provide mature history semantics, but arbitrary in-memory Plugin payload registration would add a new runtime/dependency model and still requires policy around failed non-transactional MySQL 5.7 DDL.
- Transactional DML only gives strong rollback but cannot validate the database-migration path that this stage is intended to establish.
- A strict idempotent SQL subset plus a target-side receipt ledger uses the existing Druid AST parser, supports a small real migration surface, and makes MySQL 5.7 implicit DDL commits recoverable by safe repetition. This is the selected approach.
## Payload Contract
`PluginContributionPayload` and immutable Transition steps gain `idempotent`. Transition Plan schema becomes 1.2. Publication fingerprints include this declaration, and execution re-resolution verifies that the installed payload still has the recorded value.
`sql-jdbc:v1` accepts only contributions with all of these properties:
- type `DATABASE_MIGRATION`;
- target `sql`;
- `idempotent=true` recorded in the Transition Plan and trusted installed payload;
- non-empty UTF-8 content accepted by the strict MySQL parser.
Historical 1.0 and 1.1 plans remain usable with `dry-run:v1`. They cannot be promoted to real SQL execution because they do not carry the immutable idempotence declaration. Schema 1.1 and 1.2 both carry compensation identity and remain eligible for compensation through dry-run.
## SQL Policy
Druid 1.2.23 parses the complete payload into MySQL AST statements. The Executor never splits SQL by string delimiters.
The first policy allows no more than 100 statements and 1 MiB of SQL. It accepts only:
- `CREATE TABLE IF NOT EXISTS`;
- `DROP TABLE IF EXISTS`;
- MySQL `INSERT IGNORE` or `INSERT ... ON DUPLICATE KEY UPDATE`;
- `DELETE` with a non-empty `WHERE` clause.
It rejects ALTER, UPDATE, TRUNCATE, CREATE/DROP DATABASE, USE, SET, transaction control, account/GRANT statements, stored programs, and every other AST type. Every `SQLExprTableSource` must be unqualified or explicitly use the configured allowed catalog; cross-catalog references are rejected.
This is intentionally not a general migration engine. Broader operations require a future versioned Executor code and a stronger verification contract.
## Isolated Target Configuration
`factory.plugin-execution.sql-target` contains:
- `enabled`, default `false`;
- `environment-code`, required safe identifier when enabled;
- `jdbc-url`, required and restricted to `jdbc:mysql:`;
- `username` and `password`;
- `driver-class-name`, default `com.mysql.cj.jdbc.Driver`;
- `allowed-catalog`, required safe catalog identifier;
- `receipt-lease-seconds`, default 300 and bounded to 30..3600.
Opening a connection verifies the target connection catalog matches `allowed-catalog`. It also opens the configured RuoYi master DataSource and rejects an equal normalized JDBC URL or equal control catalog. The target connection is closed before propagating any validation failure.
No target secret is returned by Registry status or execution history. The admin status exposes only enabled/readiness, environment code, allowed catalog, and configured Executor code.
## Target Receipt Ledger
The isolated target database must run `sql/factory_plugin_sql_target.sql` before `sql-jdbc:v1` is enabled. It creates `factory_plugin_target_receipt` with a unique `step_idempotency_key` and these durable fields:
- environment, Plugin/version, direction and content hash identity;
- RUNNING, SUCCEEDED, or FAILED status;
- target lease token/deadline;
- execution count, external receipt, last error, and timestamps.
Claiming a new key inserts RUNNING and commits before executing migration statements. A duplicate key is locked and checked:
- SUCCEEDED with matching identity returns the existing external receipt without executing SQL.
- RUNNING with an unexpired target lease is rejected and cannot be stolen.
- FAILED or expired RUNNING with matching identity is claimed with a new token and execution count.
- Any direction, content hash, Plugin release, or environment mismatch is rejected.
After all parsed statements execute, the Executor updates SUCCEEDED only when the lease token still matches. On SQL failure it rolls back what MySQL can roll back, then records FAILED with the same token. MySQL 5.7 DDL may have committed before a process interruption; safe recovery therefore depends on the immutable `idempotent=true` contract and strict repeatable SQL subset.
The returned external receipt is deterministic: `sql:<environment-code>:<step-idempotency-key>`.
## Execution Flow
1. The D8e2a Worker passes the trusted resolved contribution and stable step key to `sql-jdbc:v1` outside its database transaction.
2. The Executor validates target configuration, immutable idempotence, SQL AST policy, and isolated connection identity.
3. The target ledger returns existing success, rejects an active lease, or grants a target lease.
4. The Executor runs normalized AST statements on the target connection.
5. The target receipt is completed and returned to the control-side step receipt.
The control Outbox lease and target receipt lease are independent. A stale control Worker cannot overwrite control state, while duplicate target calls converge on the target receipt key.
## UI And Operations
Registry status displays the configured Executor and a concise SQL target state: disabled, incomplete, or ready. It never displays JDBC URL, username, or password. The execution dialog continues to show the concrete Executor code and external receipt.
Operators must apply both the control Outbox migration and the isolated target receipt script before enabling:
```text
FACTORY_PLUGIN_EXECUTOR_CODE=sql-jdbc:v1
FACTORY_PLUGIN_SQL_TARGET_ENABLED=true
FACTORY_PLUGIN_SQL_TARGET_ENVIRONMENT_CODE=preview
FACTORY_PLUGIN_SQL_TARGET_JDBC_URL=jdbc:mysql://.../ruoyi_plugin_preview
FACTORY_PLUGIN_SQL_TARGET_USERNAME=...
FACTORY_PLUGIN_SQL_TARGET_PASSWORD=...
FACTORY_PLUGIN_SQL_TARGET_ALLOWED_CATALOG=ruoyi_plugin_preview
```
## Testing
- Contract tests cover bounded configuration, schema 1.2 propagation, and historical compensation compatibility.
- Parser tests use real Druid AST parsing, including semicolons inside literals, whitelist enforcement, and cross-catalog rejection.
- Target connection tests use mocked JDBC metadata to prove allowlist and control-catalog isolation.
- Executor tests use H2 1.4.200 in MySQL mode from the existing Spring Boot BOM to execute a reversible migration, prove duplicate SUCCEEDED delivery does not re-run SQL, exercise failed/expired receipt recovery, and verify compensation.
- Existing Plugin, generator, admin, and frontend regressions remain required. No production target connection is attempted during tests.
## Scope
P2-D8e2b implements only `DATABASE_MIGRATION + sql`. Permission and menu adapters remain on `dry-run:v1`. Existing PageBlock plugins still carry no real delivery payload. A production Plugin must opt in with a reviewed idempotent payload before real SQL is executed.

View File

@@ -0,0 +1,75 @@
# Plugin Transition Preflight And Delivery Acceptance Design
## Context
P2-D8e2g made approval evidence independent and single-use, but the Plugin page still decided whether to enter approval flow from the Registry-wide `realExecutorConfigured` flag. In a mixed route configuration, one installed real Executor made every Transition look approval-sensitive even when that Transition resolved only to `dry-run:v1`. The browser also reconstructed effective retry and compensation identity from stale list fields.
P2-D8e2h moves that decision to a Transition-scoped server preflight and adds one operational delivery acceptance report. The execute endpoint remains the final authority and repeats all checks; preflight is an exact preview, not a capability token.
## Transition Preflight
`GET /generator/plugin/{pluginId}/transitions/{transitionId}/preflight?executionMode=...` accepts the requested action mode (`APPLY`, `RETRY`, or `COMPENSATE`). It uses the same service, Transition lock, immutable plan verifier, `nextMode` state machine, trusted contribution resolver, and routing policy as execution.
The response freezes:
- requested and effective mode, including failed compensation retry resolving back to `COMPENSATE`;
- current Transition fingerprint;
- environment, routing fingerprint, and summary Executor;
- ordered execution steps with direction, contribution identity, target, selected Executor, and real-side-effect marker;
- whether any step is real and whether the effective route requires approval;
- only currently executable approval records.
Preflight has three states:
- `READY`: dry-run, real execution with approval disabled, or real execution with at least one exact approved record;
- `APPROVAL_REQUIRED`: the actual route requires approval but no executable evidence exists;
- `NO_ACTION`: the requested action is already an idempotent terminal/no-op according to the execution state machine.
Invalid requested transitions still fail with the same service errors as execution rather than returning a misleading ready state.
## Approval Eligibility
An approval is returned as executable only when all of the following remain true:
- status is `APPROVED` and `expires_at` is in the future;
- effective mode, Transition fingerprint, routing fingerprint, environment, and summary Executor exactly match;
- requester account, requester roles, request reason/time/expiry, approver account, approver roles, approval reason/time/expiry are all present;
- requester and approver still satisfy the configured separation rule.
Execution repeats those checks after locking the selected approval. The preflight result can therefore become stale safely: a concurrent expiry, revocation, configuration change, or consumption causes execution to fail before Outbox work is created.
## Frontend Flow
Every execute, retry, and compensate action now starts with server preflight:
1. `NO_ACTION` reports the current no-op state.
2. `APPROVAL_REQUIRED` opens the approval workspace and disables request submission until the current preflight identity is loaded.
3. `READY + approvalRequired` consumes the newest executable approval returned by the server after explicit confirmation.
4. `READY + realExecution` without an approval requirement displays an explicit real-side-effect warning.
5. `READY` dry-run retains the normal queue confirmation.
The execution body copies fingerprints and environment only from the preflight response. The approval workspace refreshes preflight and approval history together and displays the exact ordered route before a request or decision.
## Delivery Acceptance
`PluginDeliveryAcceptanceService` owns the delivery verification response. It returns seven explicit checks:
1. the SQL, permission, and menu baseline Plugins are installed;
2. the active built-in or database Plugin release catalog is ready;
3. all three mixed routes explicitly select `sql-jdbc:v1`, `permission-jdbc:v1`, and `menu-jdbc:v1`;
4. control-plane and JDBC target environment codes match exactly;
5. real execution approval and separation are enabled;
6. the isolated JDBC target configuration is complete;
7. target receipt, permission ownership, and menu ownership ledgers are queryable.
The report is `READY` only when every check passes. The delivery page renders each check as passed or blocked, then shows the non-secret routing, approval, Registry, and JDBC snapshots.
## Security And Boundaries
- Preflight requires execute or approve permission and never returns JDBC URL, username, password, driver errors, SQLState, or raw target exceptions.
- The acceptance probe uses zero-row SELECT statements only. It does not run Plugin contributions or modify the target.
- Preflight performs lazy approval expiry under the Transition lock, matching the existing approval-list behavior; it does not create execution, receipt, or Outbox rows.
- No schema migration is required for this stage.
- No external MySQL instance was connected or modified during implementation. Target adapter tests continue to use H2 in MySQL mode; an explicitly authorized isolated MySQL rehearsal remains separate.
- Plaintext development credentials and TLS verification remain deferred by explicit user decision.

View File

@@ -0,0 +1,69 @@
# Preview SQL Delivery Rehearsal Design
## Context
P2-D8e2b provides a real, default-disabled `sql-jdbc:v1` Executor, but every installed PageBlock release still has an empty delivery payload. The execution framework therefore has no reviewed built-in release that proves a real classpath SQL artifact can move through publication, immutable Transition planning, trusted content re-resolution, target deduplication, and compensation.
P2-D8e2c adds one deliberately small delivery Plugin and a read-only target probe. It does not enable the SQL target, publish a release, enqueue a Transition, or connect to a production database automatically.
## Delivery Plugin
The installed release is `delivery.preview-baseline@1.0.0`, provided by `ruoyi-factory` with Plugin type `delivery`. It declares one `DATABASE_MIGRATION` contribution named `V1__preview_delivery_baseline`.
The apply resource:
- creates the dedicated `factory_delivery_baseline_marker` table with `IF NOT EXISTS`;
- uses `INSERT IGNORE` to add the immutable `delivery.preview-baseline` marker;
- is declared `idempotent=true`;
- contains no control-database table and no dynamic catalog name.
The rollback resource uses only `DROP TABLE IF EXISTS factory_delivery_baseline_marker`. The table is dedicated to this Plugin release, so compensation does not remove data owned by another feature.
The Plugin is a normal Spring `FeaturePlugin`. It appears in installed Registry manifests and can be bootstrapped through the existing publication workflow. Installation alone has no target-side effect. Operators must still publish the release and explicitly enqueue its Transition.
## Manual Target Probe
Registry status continues to report only local configuration readiness. A separate `POST /generator/plugin/sql-target/probe` operation performs an explicit, read-only probe under `generator:plugin:list` permission.
The probe:
1. returns `DISABLED` without opening a connection when the target is disabled;
2. returns `INVALID_CONFIGURATION` without opening a connection when local validation fails;
3. opens the guarded isolated connection, which rechecks target catalog and control-database identity;
4. runs a zero-row query against `factory_plugin_target_receipt`;
5. returns `READY`, `UNAVAILABLE`, or `LEDGER_UNAVAILABLE` with a generic message.
The response contains only state, enabled/config-ready/reachable/ledger-ready booleans, environment code, allowed catalog, and a generic message. It has no JDBC URL, username, password, driver exception, SQLState, or server error text. Probe results are not persisted and never run migration payloads.
## Admin UI
When SQL target configuration is enabled, the Registry status band shows a compact manual probe command and the last result held in the browser. The initial state is `NOT_CHECKED`. A successful result confirms isolation checks and receipt-table visibility, not permission to modify arbitrary application tables.
The existing execution dialog remains the only place where an operator starts a published Transition. The probe cannot publish, execute, retry, or compensate a Plugin.
## End-To-End Rehearsal Test
The integration test uses H2 1.4.200 in MySQL mode and the real classpath apply/rollback resources. It performs this path without replacing core steps with mocks:
1. construct the installed Plugin and validate its manifest/payload fingerprint;
2. plan a schema 1.2 PUBLISH Transition;
3. re-resolve the apply content from the installed release;
4. execute through `sql-jdbc:v1` and the target receipt ledger;
5. redeliver the same logical step and verify one marker row and one target execution;
6. resolve the planned compensation and execute it under a separate stable key;
7. verify the marker table is absent and both target receipts are SUCCEEDED.
Separate probe tests cover disabled, invalid, ready, unreachable, and missing-ledger outcomes with mocked JDBC boundaries. No test attempts a production target connection.
## Activation Order
1. Apply `sql/factory_plugin_sql_target.sql` to an isolated preview catalog.
2. Configure and enable the SQL target, then restart.
3. Use the manual target probe and require `READY`.
4. Bootstrap or create the `delivery.preview-baseline` definition and publish version `1.0.0`.
5. Inspect the immutable Transition Plan and enqueue execution.
6. Verify the marker and target receipt, then exercise compensation before considering a broader Plugin.
## Scope
This stage proves one small database delivery artifact and closes the target-readiness observability gap. It does not add permission/menu adapters, arbitrary SQL upload, automatic execution, scheduled probes, production credentials, or production rollout approval. Existing plaintext development credential and TLS settings remain deferred by explicit user decision.

View File

@@ -0,0 +1,103 @@
# 项目交付验收摘要与下载门禁设计
**状态:** 已实现P1-K
## 1. 背景
当前 `previewStatus=1``downloadReady=true` 代表项目结构已经可以渲染成 ZIP但不代表运行预览成功也不代表生成产物具备可追溯的 ProjectSpec、Generation Run 和文件清单。项目列表、个人中心、源码预览和一键生成工作台都把这一状态统一显示为“可下载”,公开下载接口也没有服务端质量门禁。
运行预览服务内部需要调用现有打包方法创建隔离工作区,因此不能在底层 `downloadAll` 上增加公开下载门禁。本轮把“草稿源码”和“基础验收产物”明确分开,并在 HTTP 下载边界执行最终校验。
## 2. 用户目标
- 明确区分源码已生成、运行预览已通过、基础验收已通过和允许认证下载。
- 运行预览失败时仍能下载源码草稿排查问题,但不能把它误称为验收产物。
- 一键生成成功且具备版本、Generation Run、文件清单和运行预览证据时可以下载基础验收产物。
- 高级调整或旧项目仍可下载草稿,不因新门禁突然失去已有能力。
- 服务端拒绝伪造的认证下载请求,不能只依赖按钮禁用。
## 3. 产物类型
### `DRAFT`
- 条件:项目当前 `previewStatus=1`,且最新源码生产任务确认源码已经生成。
- 用途:预览、调试、运行预览工作区和人工排查。
- 不声明已经通过运行或质量验收。
### `CERTIFIED`
- 条件:源码草稿可用;最新源码生产任务是一键生成;绑定的 Generation Run 成功且包含 Spec、Adapter、Template 和文件清单身份;最新尝试的 `RUN_PREVIEW` 阶段成功。
- 当前认证等级:`BASELINE`
- 后续 Java 测试、前端 Build、SQL 导入和 API Smoke 会继续加入同一门禁,当前不得把 BASELINE 描述为完整生产认证。
## 4. 交付状态
| 状态 | 含义 |
| --- | --- |
| `DRAFT` | 尚无可下载源码 |
| `SOURCE_READY` | 源码草稿已生成 |
| `PREVIEW_PASSED` | 运行预览通过,但版本追踪或基础验收尚未齐全 |
| `ACCEPTED` | 基础验收通过,允许认证下载 |
交付摘要固定返回四个用户检查项:源码生成、运行预览、基础验收、认证下载。每项状态为 `PASSED``PENDING``FAILED`,并附短说明和下一步动作。
## 5. 证据计算
1. 查询项目最近一个会改变源码的任务:`one_click_project``database_change_sync`
2. `previewStatus=1` 且任务结果确认 `downloadReady=true` 时,源码草稿可用;没有任务的旧项目只按 `previewStatus` 进入兼容草稿模式。
3. 一键任务本身必须为 `SUCCEEDED`,其结果或源码阶段必须关联一个属于当前用户和项目的成功 Generation Run。
4. Generation Run 必须包含 Spec 版本/哈希、Adapter 版本、Template 版本、聚合内容哈希和非空文件清单。
5. 最新尝试的 `RUN_PREVIEW` 阶段必须为 `SUCCEEDED`;旧阶段账本为空时可回退到结果中的 `previewStatus=RUNNING`
6. 数据库变更同步产生的源码只能作为草稿,直到后续质量运行创建新的可追溯认证证据。
## 6. API
新增:
```http
GET /front/project/{projectId}/delivery-readiness
```
现有下载接口增加参数:
```http
GET /front/project/{projectId}/download?artifactType=DRAFT|CERTIFIED
```
- 未传参数继续按 `DRAFT` 处理,兼容现有调用。
- `CERTIFIED` 必须通过服务端门禁。
- `DRAFT` 也必须确认当前源码草稿可用。
- 响应增加 `X-Factory-Artifact-Type``X-Factory-Certification-Level`,不改变 ZIP 内部生成方式。
## 7. 前端交互
- 一键生成结果区新增交付验收带,显示四项检查和下一步。
- 基础验收通过时按钮显示“下载验收源码”,请求 `CERTIFIED`
- 只有源码就绪时按钮显示“下载源码草稿”,请求 `DRAFT`
- 项目列表、个人中心和源码预览页现有下载入口明确改名为“下载源码草稿”。
- 交付摘要加载失败不覆盖已经完成的生成任务,只降级为草稿状态并让服务端做最终判断。
## 8. 边界
- 不新增数据库表或迁移。
- BASELINE 门禁暂不纳入生成项目的 Java 测试、前端 Build、SQL 导入和 API Smoke本轮只建立可扩展门禁与基础证据。
- 不改变运行预览内部打包流程。
- 不修改开发阶段暂缓处理的模型凭据和 TLS 配置。
## 9. 验收标准
- 运行预览失败的一键任务只能下载草稿,认证下载被服务端拒绝。
- 一键任务具有完整 Generation Run 与成功运行阶段时可以认证下载。
- 数据库同步或旧项目不会被误认证,但保留草稿下载。
- 项目修改导致 `previewStatus=0` 后,草稿和认证下载均被拒绝。
- 前端四种状态、按钮文案和实际请求产物类型一致。
## 10. 实施结果
- 新增 `ProjectDeliveryReadinessService`、交付摘要 API、四项用户检查和公开下载边界的服务端门禁。
- `CERTIFIED` 只接受当前源码对应的完整一键 Generation Run 与最新尝试运行预览证据;数据库同步和兼容旧项目只保留草稿能力。
- 阶段账本非空时不会回退任务结果中的旧预览状态;最新尝试尚未执行 `RUN_PREVIEW` 时保持待验收。任务非 `SUCCEEDED` 时,即使残留旧证据也不能认证。
- 项目编辑将 `previewStatus` 复位后,旧的阶段或 Generation Run 证据不能继续开放下载。
- 一键生成工作台展示交付验收摘要并按服务端结果选择产物类型;项目列表、个人中心和源码预览入口均明确为草稿下载。
- 独立运行预览重启仍是调试能力,不会补写认证证据。运行预览失败后,用户必须重新执行完整一键生成才能建立新的可追溯验收事实。
- P1-J/P1-K 后端聚焦测试 32 项、管理端全量测试 42 项、EasyCode 主流程测试 73 项、生产构建、Mapper XML 解析与开发服务检查均已通过。

View File

@@ -0,0 +1,61 @@
# 项目生成工厂 V1 收口设计
**状态:** 已实施并通过代码回归P1-M / P1-N / P1-O2026-07-12
## 1. 目标
在 P1-L 持久化 Quality Run 之上完成三个剩余轮次:冻结认证源码制品;把运行预览迁移到持久化 Docker Preview Worker使用黄金样例形成可追溯的 V1 端到端验收记录。完成后,项目生成工厂 V1 主线关闭Knowledge Pack、多技术栈 Adapter 和插件市场继续留在 V2。
## 2. P1-M 认证制品固化
- 新增 `factory_project_certified_artifact`,唯一绑定通过的 Quality Run、Generation Run 和源码聚合哈希。
- Quality Worker 四项检查通过后,重新读取同一服务端源码 ZIP计算 ZIP SHA-256生成规范化质量报告并计算报告 SHA-256。
- ZIP 和报告通过 `CertifiedArtifactStore` 写入不可变对象路径;本地实现使用临时文件、原子移动和只读最终路径,接口允许后续替换 S3/OSS。
- 制品记录只在 ZIP、报告及两项哈希全部持久化后进入 `READY`。失败记录不可下载,不覆盖旧制品。
- `CERTIFIED` 下载必须读取当前 Quality Run 对应的 READY 冻结 ZIP并返回制品 ID、ZIP SHA-256、报告 SHA-256 和冻结时间响应头;不得调用临时源码重新打包。
- `DRAFT` 下载保持兼容,仍可按当前项目即时打包。
## 3. P1-N 隔离容器预览
- 新增 `factory_project_preview_job`,状态为 `QUEUED``CLAIMED``STARTING``RUNNING``STOP_REQUESTED``STOPPED``FAILED``EXPIRED`
- Web 请求只校验项目、创建或返回活动 Job不解压、不分配本机进程、不直接运行生成项目。
- `ProjectPreviewJobWorker` 可独立部署,通过租约和 fencing token 认领 JobWeb 节点默认关闭 WorkerPreview Worker 部署显式开启。
- `PreviewContainerRuntime` 负责启动、检查和停止容器。本地生产实现只调用 Docker CLI并强制 CPU、内存、PIDs、只读根文件系统、临时文件系统、标签、网络和 TTL 参数。
- 源码 ZIP 只写入 Worker 输入目录,容器以只读挂载读取;运行输出、脱敏日志和页面截图写入独立输出目录。
- Job 持久化公开 URL、容器 ID 哈希、截图 SHA-256、过期时间和有界日志不公开宿主路径、Docker socket、数据库凭据或原始容器 ID。
- 过期 Job 由 Worker 停止容器并标记 `EXPIRED`;停止操作幂等。
## 4. P1-O 端到端验收
- 新增 `factory_project_v1_acceptance_run` 和有序检查表绑定用户、项目、Generation Run、Quality Run、认证制品和 Preview Job。
- 固定五项黄金样例检查:`GENERATION``FAILURE_RECOVERY``VERSION_ROLLBACK``CONTAINER_PREVIEW``CERTIFIED_DOWNLOAD`
- 验收器只调用项目工厂公开服务边界,不直接修改内部状态。每项记录脱敏摘要、证据 SHA-256、时间和耗时。
- 认证下载检查要求连续读取两次得到相同制品 ID、长度和 SHA-256预览检查要求 Docker Job 为 RUNNING、截图存在且未过 TTL。
- 版本回滚必须产生新版本/Generation Run不能覆写历史版本失败恢复必须证明失败任务保留、重试产生新尝试且从可信检查点继续。
- 五项全部通过才将 V1 Acceptance Run 标记为 `PASSED`。失败保留证据,可新建下一次验收,不覆盖历史。
- 提供启动、最新和历史 APIEasyCode 交付区展示 V1 验收状态,不展示内部指纹和执行 Payload。
## 5. 数据与安全边界
- 三组新表同步写入独立升级脚本、`front_workbench.sql``db.sql`
- 不自动连接或迁移外部数据库。
- 不修改开发阶段暂缓的模型明文凭据和 TLS 设置。
- Docker 不可用时 Preview Job 明确失败并保留诊断,不回退到主进程直接运行。
- 所有路径必须位于配置根目录下ZIP 解压继续拒绝路径穿越,日志和错误信息继续脱敏和限长。
## 6. V1 完成标准
- Quality Run 通过后可查询且只能下载冻结认证制品。
- 相同认证下载重复读取字节和哈希完全一致。
- Web 进程不再直接启动生成项目进程;预览由持久化 Worker 和 Docker Runtime 执行。
- 预览具备截图、TTL、停止、资源限制和失败诊断。
- 黄金样例五项验收可持久化执行并形成可重跑证据。
- 旧临时认证下载入口和不一致文案清理完成,草稿与认证制品语义明确。
## 7. 实施与部署边界
- P1-M 已完成Quality Worker 在终态前冻结 ZIP 和规范化质量报告;`CERTIFIED` 下载只读取 READY 对象并校验 SHA-256。
- P1-N 已完成:生产 Spring 边界改为持久化 Preview Job旧宿主进程类不再注册。独立 Worker 使用 Docker CLI、资源限制、TTL、截图和 fencing仓库包含可构建镜像。
- P1-O 已完成:五项黄金样例 Acceptance Run、生产探针、API 和 EasyCode 状态面板已落地。
- 本机没有 Docker CLI因此没有在本机声称真实容器和黄金项目已经跑通。部署环境需构建 Worker 镜像、启用独立 Worker、准备黄金项目证据后执行 V1 验收 API。
- 本轮未连接或迁移外部数据库,未修改暂缓的模型凭据与 TLS 配置。

View File

@@ -0,0 +1,104 @@
# 项目持久化 Quality Run 设计
**状态:** 已实施并验证P1-L2026-07-12
## 1. 目标
把“运行预览成功”升级为可追溯的项目质量运行。每次 Quality Run 必须绑定当前用户、项目、Generation Run 和源码聚合哈希,并持久化 Java 测试、前端生产构建、SQL 隔离导入、API 冒烟四项结果。只有当前源码对应的最新 Quality Run 全部通过,才允许 `CERTIFIED` 下载。
## 2. 边界
- 本轮不冻结 ZIP 字节;认证制品固化属于 P1-M。
- 本轮复用现有运行预览,不建设 Docker/Preview Worker容器隔离属于 P1-N。
- 不把 Plugin 交付演练、目标库脚本或菜单 SQL 混入项目质量运行。
- 不修改开发阶段暂缓处理的模型凭据和 TLS 配置。
- Quality Run 不保存密码、数据库 URL、完整命令或无限日志只保存脱敏摘要和有界日志片段。
## 3. 持久化模型
新增 `factory_project_quality_run`
- 绑定 `project_id``user_id``generation_run_id``source_aggregate_hash`
- 状态为 `QUEUED``RUNNING``PASSED``FAILED``INTERRUPTED`
- 保存检查总数、通过数、失败数、开始/结束时间、耗时、尝试号和脱敏失败摘要。
- 保存 Worker、租约截止时间和更新时间。服务重启后过期运行关闭为 `INTERRUPTED`,用户创建新运行,不恢复原外部进程。
新增 `factory_project_quality_check`
- 每个 Run 固定四条:`JAVA_TEST``FRONTEND_BUILD``SQL_IMPORT``API_SMOKE`
- 保存固定顺序、状态、摘要、有界日志、时间和耗时。
- 同一 Run 的检查 code 和顺序分别唯一。
升级脚本同时写入 `sql/factory_project_quality_run.sql``sql/front_workbench.sql``sql/db.sql`,确保升级库和新库结构一致。
## 4. 执行流程
1. 用户创建质量运行。
2. 服务端读取交付就绪度,要求当前源码、一键任务成功、完整 Generation Run 和运行预览证据均有效。
3. 若项目已有 `QUEUED``RUNNING` 质量运行,直接返回该运行,不重复入队。
4. 在一个事务内写入 Run 和四条 `PENDING` Check。
5. Worker 条件认领 Run创建独立质量工作区并依次执行四项检查。
6. 每项检查在开始和结束时持久化状态;单项失败不阻止其他可独立执行的检查。
7. 四项全部通过时 Run 为 `PASSED`,否则为 `FAILED`;清理临时数据库、预览进程和工作区。
8. 交付就绪度只接受与当前 Generation Run ID 和聚合哈希都匹配的最新 `PASSED` Run。
## 5. 四项检查
### Java Test
- 定位生成后端的 `pom.xml`
- 执行 `mvn test`,使用独立 Maven 本地缓存和配置超时。
- 缺少后端工程或命令超时均失败。
### Frontend Build
- 定位所有启用且包含 `package.json` 的前端目录。
- 有 lockfile 时执行 `npm ci`,否则执行 `npm install`,随后执行 `npm run build`
- 所有启用前端都必须通过;项目明确未启用用户前台时不要求该目标,但管理端仍必须构建。
### SQL Import
- 复用 `RunPreviewDatabaseInitializer`,创建名称受限的独立 MySQL 数据库。
- 导入生成 SQL 后立即删除数据库;清理失败写入摘要并使检查失败。
- 不连接 Plugin 目标库,也不把目标库 bootstrap 合并到控制库。
### API Smoke
- 复用 `IFrontProjectRunPreviewService` 启动当前项目运行预览。
- 轮询到 `RUNNING` 才通过;`FAILED`、超时或进程提前退出均失败。
- 无论结果如何都调用 stop避免遗留进程和预览数据库。
## 6. API 与前端
新增:
```http
POST /front/project/{projectId}/quality-runs
GET /front/project/{projectId}/quality-runs
GET /front/project/{projectId}/quality-runs/latest
```
- 所有接口使用当前登录用户,不能从请求体指定 userId、Generation Run 或源码哈希。
- 一键生成结果区展示四项技术检查、运行状态、耗时和失败摘要。
- 基线证据齐全但没有通过的质量运行时显示“开始质量验收”;失败后显示“重新质量验收”;运行中禁用重复提交并轮询。
- `CERTIFIED` 按钮只由服务端返回的 `certifiedDownloadAllowed` 控制。
## 7. 验收标准
- 质量运行和四项检查可以在服务重启后查询。
- 旧 Generation Run 的成功质量记录不能认证当前源码。
- `RUNNING``FAILED``INTERRUPTED` 或检查不完整的 Run 不能认证下载。
- 四项检查全部通过后,交付摘要进入 `ACCEPTED` 并允许 `CERTIFIED`
- 项目编辑或数据库同步使当前源码身份变化后,旧 Quality Run 自动失效。
- 新增 SQL 同时存在于升级脚本、`front_workbench.sql``db.sql`
## 8. 实施结果
- Quality Run、四项 Check、条件认领、租约和中断状态均已持久化最终写入同样受 Worker owner 条件保护,丢失租约不会误报处理成功。
- Java 测试、前端构建、SQL 隔离导入和 API 冒烟已接入真实执行器,并复用运行预览的 MySQL 配置和预览服务。
- 交付摘要只在当前 Generation Run、源码聚合哈希和四项通过证据完全匹配时进入 `ACCEPTED`;旧 Run、失败 Run 和不完整 Run 均不能开放认证下载。
- EasyCode 已提供开始、重试、运行中禁用、四项结果展示和终态轮询刷新。
- 聚焦后端 50 项、`ruoyi-admin` 全量 43 项、EasyCode 主流程 73 项和生产构建均通过Mapper XML 解析通过,开发服务返回 HTTP 200。
- 未连接或迁移外部数据库。部署前必须应用 Quality Run 表结构。
下一轮 P1-M 固化通过质量检查的 ZIP 字节、哈希和质量报告;在此之前,`CERTIFIED` 下载仍会按请求重新打包,不具备不可变制品语义。