openxiangda 2.21.0 → 2.21.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/documentation/appspec.md +8 -1
  2. package/documentation/backend.md +38 -0
  3. package/documentation/data-authz.md +35 -0
  4. package/documentation/declarations-cheatsheet.md +7 -1
  5. package/documentation/development.md +2 -2
  6. package/documentation/frontend.md +8 -1
  7. package/documentation/getting-started.md +41 -9
  8. package/documentation/manifest.json +14 -14
  9. package/documentation/public-access.md +16 -6
  10. package/documentation/reference/cli.md +3 -0
  11. package/documentation/reference/mcp.md +12 -6
  12. package/documentation/testing.md +2 -0
  13. package/documentation/upgrading.md +1 -1
  14. package/documentation/workflow-events.md +42 -0
  15. package/package.json +20 -19
  16. package/releases/2.21.1.json +36 -0
  17. package/skills/manifest.json +1 -1
  18. package/skills/openxiangda-v2/SKILL.md +4 -4
  19. package/skills/openxiangda-v2/references/appspec.md +8 -1
  20. package/skills/openxiangda-v2/references/backend.md +38 -0
  21. package/skills/openxiangda-v2/references/cli.md +3 -0
  22. package/skills/openxiangda-v2/references/data-authz.md +35 -0
  23. package/skills/openxiangda-v2/references/declarations-cheatsheet.md +7 -1
  24. package/skills/openxiangda-v2/references/development.md +2 -2
  25. package/skills/openxiangda-v2/references/frontend.md +8 -1
  26. package/skills/openxiangda-v2/references/getting-started.md +41 -9
  27. package/skills/openxiangda-v2/references/mcp.md +12 -6
  28. package/skills/openxiangda-v2/references/public-access.md +16 -6
  29. package/skills/openxiangda-v2/references/testing.md +2 -0
  30. package/skills/openxiangda-v2/references/upgrading.md +1 -1
  31. package/skills/openxiangda-v2/references/workflow-events.md +42 -0
@@ -36,11 +36,18 @@ appspec/
36
36
  ```bash
37
37
  pnpm openxiangda spec init
38
38
  pnpm openxiangda spec new booking-window --title "限制可预约时段" --risk L2
39
+ pnpm openxiangda spec add-capability CAP-REPAIR-REQUEST --title "报修申请能力" --resources repair-requests
39
40
  pnpm openxiangda spec context booking-window --json
40
41
  pnpm openxiangda spec check
41
42
  ```
42
43
 
43
- 已有工作区优先用 `spec new` 生成本地记录。访谈尚未创建应用时,可在 `appspec/changes/active/<变更ID>.md` 手工建草稿:front matter 使用 `schema: openxiangda.appspec/change/v1`、与文件名一致的 `id`、`title`、`status: draft`、`currentSpec: pending`、适用的 `risk`,以及 `documents: [实际评审ID]`。不必为整理设计先创建远端应用。
44
+ 已有工作区优先用 `spec new` 生成本地记录。跨变更复用的能力规格用
45
+ `spec add-capability <CAP-*> --title <名称>` 单独建立(可带 `--resources`/`--actions`
46
+ 逗号分隔引用),排他创建 `appspec/capabilities/` 下的规格文件,绝不覆盖同名文件;
47
+ ChangeSpec 通过 `capabilities` 引用这些 CAP-* 规格。访谈尚未创建应用时,可在
48
+ `appspec/changes/active/<变更ID>.md` 手工建草稿:front matter 使用
49
+ `schema: openxiangda.appspec/change/v1`、与文件名一致的 `id`、`title`、`status: draft`、
50
+ `currentSpec: pending`、适用的 `risk`,以及 `documents: [实际评审ID]`。不必为整理设计先创建远端应用。
44
51
 
45
52
  | 阶段或风险 | ChangeSpec 必需正文 |
46
53
  | --- | --- |
@@ -189,6 +189,44 @@ await businessData.transaction({
189
189
  分派已接受后撤销角色,不会自动撤销历史分派;后续处理动作必须重新验证当前权限,
190
190
  由管理员重新分派。此规则应写入 AppSpec,并实测撤销先发生和分派先发生两种顺序。
191
191
 
192
+ ## AI 能力目录与 MCP Facade {#ai-catalog}
193
+
194
+ 编译器为每个应用生成不可变的 AI 能力目录:资源的标准 CRUD 面(query/get/create/update/delete,
195
+ 按 `generated` 与 `mutationOwner` 实际开放的操作)自动成为 `generatedCrud` 能力;`backend.operations[]`
196
+ 中带 `ai` 声明的操作成为 `customAction` 能力。目录摘要(`aiCatalogDigest`)随当前声明生成并进入契约;
197
+ 平台 `GET .../native/ai/catalog` 按当前用户角色与字段权限裁剪后返回。为操作声明 `ai` 即把它加入目录:
198
+
199
+ ```ts
200
+ operations: [{
201
+ code: 'dispatch-repair', method: 'POST', path: '/dispatch', capability: dispatchCapability,
202
+ ai: {
203
+ name: '受理派单', description: '把报修单派给指定技师并写入派工记录',
204
+ risk: 'write', resources: ['repair-requests'],
205
+ sideEffects: ['更新报修单状态为处理中', '写入一条派工记录'],
206
+ },
207
+ }],
208
+ ```
209
+
210
+ `ai` 的规则:`name` 与 `description` 必填;`risk` 取 `read | write | destructive | external`,
211
+ `read` 等价于 `method: 'GET'`,DELETE 方法只能是 `destructive` 或 `external`;`resources`
212
+ 引用 1–16 个已声明资源;`sideEffects` 最多 20 条——只读必须为零,写操作至少一条具体副作用;
213
+ `concurrency` 可选 `none | revision`,`timeoutMs` 限 100–30000。
214
+
215
+ 宿主(平台 AI 网关)用该目录装配 MCP Facade,应用不自己实现协议:
216
+
217
+ - **单应用 Facade**(`createApplicationAiMcpServer`):每个能力一个工具,只读能力直接以
218
+ `capability.code` 命名执行;写能力命名为 `capability.code + '.preview'`,只生成预览不落库;
219
+ 存在写能力时额外提供 `openxiangda.ai.confirm`(入参 `previewId`),在用户明确确认预览摘要后
220
+ 由宿主重新校验身份、权限、版本和幂等性再执行。目录本体通过资源 `openxiangda://ai/catalog`
221
+ 读取。
222
+ - **平台聚合 Facade**(`createAggregatedAiMcpServer`):跨应用统一工具面
223
+ `apps.search`、`resources.describe`、`records.query`、`records.get`、`records.create`、
224
+ `records.update`、`records.delete`、`actions.invoke`、`mutations.confirm`;聚合器只把工具
225
+ 路由到所属应用的执行器,不合并权限、不产生第二个数据或授权边界。
226
+
227
+ 这套 Facade 服务平台侧 AI 入口,与应用工作区开发用的 MCP(见[MCP 参考](./reference/mcp.md))
228
+ 是两组互不重叠的工具。
229
+
192
230
  ## 启动与依赖注入 {#bootstrap}
193
231
 
194
232
  ```ts
@@ -29,6 +29,41 @@ pnpm openxiangda check
29
29
 
30
30
  角色成员、维度授权和平台管理员由平台管理面维护,不属于应用开发 CLI。
31
31
 
32
+ ### 授权来源声明
33
+
34
+ 应用可以在 `authz` 中声明四类授权来源,让平台从业务数据投影出维度授权、应用角色成员和
35
+ 行级关系授权;投影事实由平台物化并按当前配置重算,应用不维护第二份权限状态。各声明的
36
+ `userIdField` 等字段路径支持 `field`、`field.value` 和 `field.snapshot.<子字段>` 投影形式。
37
+
38
+ - `scopeDimensions`:`{ code, name, resourceCode?, valueType?: 'string'|'uuid',
39
+ hierarchyMode?: 'flat'|'self_parent', valueSource?: { kind: 'native_resource',
40
+ resourceCode, labelField, enabledField? } }`。定义数据范围的取值域;`valueSource` 把
41
+ Native 资源绑定取值来源,选择器只展示平台按当前 membership 与 create/update 闭包返回的
42
+ 值;`self_parent` 表示取值记录通过父引用形成层级。
43
+ - `scopeSources`:`{ code, name, resourceCode, subject, grants, operationField?,
44
+ enabledField?, effectiveFromField?, effectiveToField?, failureMode }`。从业务资源行投影
45
+ 维度授权:`subject` 为 `{ type: 'user', userIdField }` 或
46
+ `{ type: 'role_membership', userIdField, roleCode }`,`grants: [{ dimensionCode,
47
+ valueField, parentValueField? }]` 把行字段值授为对应维度;生效窗口和启用开关由字段控制;
48
+ `failureMode: 'strict'` 投影失败即判定失败,`'last_known_good'` 在源数据暂不可读时沿用
49
+ 最近一次成功投影。
50
+ - `roleMembershipSources`:`{ code, name, resourceCode, userIdField, roleCode,
51
+ enabledField?, effectiveFromField?, effectiveToField?, failureMode: 'strict' }`。从业务
52
+ 数据行授予应用角色,例如"成员表"一行代表某人拥有某角色。
53
+ - `relationshipGrantSources`:`{ code, name, resourceCode, subject, relationCode,
54
+ targetResourceCode, resourceIdField, operations, enabledField?, effectiveFromField?,
55
+ effectiveToField?, failureMode: 'strict' }`。通过业务关系授予目标资源上指定操作
56
+ (1–20 个)的行级授权,例如"订单负责人可更新该订单"。
57
+
58
+ `authorizationTransitions: [{ fromAuthzDigest, removeRoleCodes?,
59
+ removeCapabilityCodes?, reason }]` 记录授权合同的关键收缩:从 `fromAuthzDigest`
60
+ (64 位十六进制)标识的授权版本移除角色或能力时,必须逐条声明并给出原因,平台在两个授权
61
+ 修订之间核对覆盖情况后才放行发布;它不用于新增授权。
62
+
63
+ `authz.capabilities` 的完整形状是 `{ code, kind: 'backend' | 'ui', name, description? }`;
64
+ `kind: 'ui'` 声明页面级能力,`kind: 'backend'` 声明后端操作能力并配合
65
+ [按需后端](./backend.md)的 `platformAccess` 使用。
66
+
32
67
  自定义 PC/移动页面需要维护当前应用角色时,使用
33
68
  `openxiangda/core` 的 `loadRoleManagementCatalog`、
34
69
  `listRoleMemberships`、`searchRoleManagementUsers`、成员 mutation 与
@@ -41,6 +41,10 @@
41
41
  | workflow definition 必须显式 `launch`(编译器强制) | `definitions: [{ version: 1, definition, launch: { mode: 'standalone' } }]` |
42
42
  | option/user/department/resource-ref/cascade 字段投影进工作流事实是 { label, value } 对象,不能声明为标量;条件比较用 `<fact>.value` | `inputSchema.properties.urgency = { type: 'object', ... }` + `path: 'urgency.value'` |
43
43
  | `cascade.*` 的写入/比较值形状是**数组路径** | `category: [{ label: '办公设备', value: 'office' }]` |
44
+ | 行级策略按**角色并集取最宽**:多角色身份的可见行 = 各角色可见行的并集;基线角色与限制性规则并存时,限制会被宽松规则覆盖(平台语义,不是缺陷) | 给某角色做行级收窄前,先确认其角色并集里没有更宽的 unrestricted 角色 |
45
+ | `matchMode: 'AND'` 且多条规则面向**不同角色**时,非目标角色规则恒 false → 全拒(编译器会警告) | 多角色白名单用 `matchMode: 'OR'`;单条规则用 AND/OR 等价 |
46
+ | `created_by`/`updated_by` 审计列支持 `current_user` 行规则("只看自己创建");运行时 WITH CHECK 正向匹配依赖平台版本,使用前在目标平台实测确认 | `{ subject: 'current_user', field: 'created_by', roleCodes: ['app-user'] }` |
47
+ | 匿名策略 `requiredFields` 比模型必填更严格时编译器**警告**:标准控件不为这些字段生成必填校验,空值提交会被服务端 `REQUIRED_FIELD_MISSING` 拒绝 | 在模型字段上声明 `required: true`,使模型必填与策略对齐 |
44
48
  | 平台保留能力(如 `app:<app>:directory:read`)**不能**在 `capabilities` 里重复声明,直接在角色中引用即可 | `const directoryRead = \`app:\${APP_CODE}:directory:read\`` → `roles: [{ code: 'admin', capabilities: [directoryRead] }]` |
45
49
  | 资源 CRUD 能力码用 `resourceCapabilityCodes(appCode, resourceCode)` 生成 | `const crud = resourceCapabilityCodes(APP_CODE, 'repair-requests')` → `capabilities: [crud.read, crud.create]` |
46
50
  | `authenticatedUserRoleCode` 是平台登录用户的基线角色 | `authz: { authenticatedUserRoleCode: 'app-user', ... }` |
@@ -190,11 +194,13 @@ export default defineOpenXiangdaApp({
190
194
  { code: 'app-user', name: '应用用户', capabilities: [requestCrud.read, requestCrud.create] },
191
195
  { code: 'admin', name: '管理员', capabilities: [requestCrud.read, requestCrud.create, requestCrud.update, requestCrud.delete] },
192
196
  ],
193
- scopeDimensions: [], scopeSources: [], dataPolicies: [], authorizationTransitions: [],
197
+ scopeDimensions: [], scopeSources: [], roleMembershipSources: [], relationshipGrantSources: [], dataPolicies: [], authorizationTransitions: [],
194
198
  },
195
199
  });
196
200
  ```
197
201
 
202
+ 授权来源声明的字段路径(`userIdField` 等)支持 `field` / `field.value` / `field.snapshot.<子字段>` 三种投影形式;`roleMembershipSources`/`relationshipGrantSources` 只允许 `failureMode: 'strict'`,`scopeSources` 额外允许 `last_known_good`。`authorizationTransitions` 只记录移除(`removeRoleCodes`/`removeCapabilityCodes`)并要求 `fromAuthzDigest` 匹配原授权摘要;新增授权不需要 transition。
203
+
198
204
  `request-items` 不出现在 `crud` 里:子表行随 `requests` 表单的 `subtable` 字段写入(在 `requests.fields` 里补 `{ code: 'items', type: 'subtable', subtable: { resourceCode: 'request-items', foreignKey: 'requestId', orderField: 'sortOrder', maxRows: 20 } }`)。
199
205
 
200
206
  ## 图片上传的像素上限
@@ -38,8 +38,8 @@
38
38
  | 列表、筛选、排序、分页接口 | `createNativeResourceClient` 的 `list`,服务端条件树与分页 | [前端数据访问](./frontend.md#data-access) |
39
39
  | 新增 / 编辑 / 删除接口 | 标准 CRUD 页面,或同一客户端的 `create` / `update` / `remove`(`expectedRevision` 乐观锁) | [前端数据访问](./frontend.md#data-access) |
40
40
  | 提交防重、幂等重试 | `transactNativeData` / 事务请求自带 `idempotencyKey` 幂等回执 | [前端数据访问](./frontend.md#data-access)、[按需后端](./backend.md#business-action) |
41
- | 时间窗、状态前置、指定人角色校验 | 平台事务守卫:`operation-time`、`record-assert`、`record-exists`、`role-member` | [按需后端](./backend.md#business-action) |
42
- | 统计报表数据 | Data API 服务端聚合 `batchAggregateNativeResources`(单个指标也用它),前端不拉全量求和 | [前端](./frontend.md#component-selection) |
41
+ | 时间窗、状态前置、指定人角色校验 | 平台事务守卫:`operation-time`、`record-assert`、`record-exists`、`record-match`、`role-member`、`databaseNowAssertion` | [按需后端](./backend.md#business-action) |
42
+ | 统计报表数据 | Data API 服务端聚合 `batchAggregateNativeResources`(单个指标也用它),前端不拉全量求和 | [前端](./frontend.md#data-access) |
43
43
  | 导入 / 导出 | 标准 CRUD 的 `import` / `export` 动作声明 | [业务模块](./application-foundation.md) |
44
44
  | 跨模型原子写、外部 API、硬件或第三方推送 | Nest 具名 operation + 平台事务,必要时事务内 `emitEvent` | [按需后端](./backend.md) |
45
45
 
@@ -365,7 +365,14 @@ renderer,但共享同一授权与命令生命周期。主决策操作固定在
365
365
 
366
366
  资源用 `mutationOwner: 'native' | 'action' | 'readonly' | 'workflow'` 声明 mutation owner,
367
367
  并可用 `generated.list/detail/create/update/delete` 精确选择标准 surface。非 Native owner
368
- 不能生成或向应用角色授予 Native mutation;零可写业务字段不能开放 create/update
368
+ 不能生成或向应用角色授予 Native mutation;零可写业务字段不能开放 create/update。运行时直接对
369
+ 非 Native owner 资源调用 Data API create/update/delete 会被平台以 409
370
+ `OPENXIANGDA_NATIVE_DATA_CAPABILITY_MISSING` 拒绝——这不是权限配置错误,而是 mutation owner
371
+ 契约:`readonly` 资源只能读,`action` 资源的写入走 named operation,`workflow` 资源由流程命令推进。
372
+ `workflow` 资源的数据修正不绕过流程:平台为应用超级管理员提供专门的 correction 入口(记录的
373
+ correction-surface/corrections),保留审批快照;非 workflow 资源走该入口返回 409
374
+ `WORKFLOW_OWNER_REQUIRED`,修正失败错误码为 `OPENXIANGDA_WORKFLOW_CORRECTION_<reason>` 族。
375
+ 排查这两类错误先核对资源的 `mutationOwner` 声明和期望的写入通道,不要试图用扩角色绕过。
369
376
  Workflow definition 用 `launch.mode` 声明 `standalone`、`custom-page`、`hidden-handoff` 或
370
377
  `work-center-only`;只有 `standalone` 可进入菜单,`hidden-handoff` 保留同一标准 PC/移动路由
371
378
  但不进菜单。缺省 submission 使用 compiler 生成的标准 process operation。action-owned 资源
@@ -68,10 +68,10 @@ MCP 服务随项目根包一起安装,AI 客户端的 stdio 连接仍需配置
68
68
  以下命令的版本占位符由随包资料替换为该根包的精确版本。网站源码阅读者应先确认要使用的发行版本。
69
69
 
70
70
  ```bash
71
- pnpm dlx openxiangda@2.21.0 skill install --force
72
- pnpm dlx openxiangda@2.21.0 auth status --base-url <平台地址> --json
73
- pnpm dlx openxiangda@2.21.0 login --cwd my-app --base-url https://platform.example.com
74
- pnpm dlx openxiangda@2.21.0 create my-app --base-url https://platform.example.com
71
+ pnpm dlx openxiangda@2.21.1 skill install --force
72
+ pnpm dlx openxiangda@2.21.1 auth status --base-url <平台地址> --json
73
+ pnpm dlx openxiangda@2.21.1 login --cwd my-app --base-url https://platform.example.com
74
+ pnpm dlx openxiangda@2.21.1 create my-app --base-url https://platform.example.com
75
75
  cd my-app
76
76
  pnpm openxiangda context --json
77
77
  pnpm openxiangda dev
@@ -79,7 +79,7 @@ pnpm openxiangda dev
79
79
 
80
80
  将两处示例平台地址替换为同一个目标地址。`create` 先核对目标与当前登录平台,再创建本地目录、安装依赖并初始化远端应用;新应用不会自动沿用文件中最后登录的站点。CI 使用原有成对的 `OPENXIANGDA_BASE_URL` 和 `OPENXIANGDA_TOKEN` 时,可以由该显式地址指定目标。
81
81
 
82
- 初始化中断后重试同一命令;已有目录会核对原平台绑定,不能通过 `create` 改绑到其他站点。地址不一致时先检查目标并登录正确的平台,不修改 link 文件绕过检查,也不要使用内部 provision 接口另建应用。创建操作应在用户要求创建应用的范围内执行。
82
+ 初始化中断后重试同一命令;已有目录会核对原平台绑定,不能通过 `create` 改绑到其他站点。地址不一致时先检查目标并登录正确的平台;确需把工作区切换到其他站点时,使用 `openxiangda link rebind --base-url <新平台>` 显式换绑(见[跨站点与换绑](#cross-site)),不手改 link 文件绕过检查,也不要使用内部 provision 接口另建应用。创建操作应在用户要求创建应用的范围内执行。
83
83
 
84
84
  进入项目后使用 `pnpm openxiangda`,由项目依赖和锁文件决定版本。查看使用资料运行 `pnpm openxiangda docs`;查看单一主题运行 `pnpm openxiangda docs frontend`。安装到其他 AI 工具时使用 `skill install --destination <Skill根目录>`。
85
85
 
@@ -145,6 +145,7 @@ pnpm openxiangda source push -m "完成本轮应用开发"
145
145
  | --- | --- |
146
146
  | 新应用 | 正常执行 `create`,平台启用后自动建仓、配置凭据和首次推送 |
147
147
  | 已有项目首次交接给另一个 AI | 先读 `context --json` 和 `source status`,沿用项目绑定与版本 |
148
+ | 交接给另一位开发者 | 平台管理员先把对方加为该应用的应用管理员;对方执行 `source clone <仓库URL> <新目录> --base-url <平台>`,克隆后在同目录 `login --base-url <平台>` 继续开发 |
148
149
  | 换电脑或初始化中断 | 在应用目录执行 `pnpm openxiangda source setup`;已有提交及未提交修改会保留 |
149
150
  | 从个人远端迁入 | 明确迁入后运行 `source setup --import`,原远端保留为 `external-source` |
150
151
  | 本轮修改完成 | 检查差异后运行 `source push -m "AppSpec: <本轮变更ID> 变更说明"`;多个任务共享目录时先精确提交本轮文件,再不带 `-m` 推送 |
@@ -173,9 +174,9 @@ MCP 的 `docs_read` 可以读取本说明,当前没有独立的源码操作 MC
173
174
  无需本地工作区,使用本 Skill 随包精确版本或已安装的对应 CLI:
174
175
 
175
176
  ```bash
176
- pnpm dlx openxiangda@2.21.0 auth status --base-url <平台> --json
177
- pnpm dlx openxiangda@2.21.0 source resolve <仓库URL> --base-url <平台> --json
178
- pnpm dlx openxiangda@2.21.0 source clone <仓库URL> <新目录> --base-url <平台> --json
177
+ pnpm dlx openxiangda@2.21.1 auth status --base-url <平台> --json
178
+ pnpm dlx openxiangda@2.21.1 source resolve <仓库URL> --base-url <平台> --json
179
+ pnpm dlx openxiangda@2.21.1 source clone <仓库URL> <新目录> --base-url <平台> --json
179
180
  ```
180
181
 
181
182
  登录缺失或站点不匹配时,先按该平台执行 login。resolve 根据平台已经登记的绑定返回
@@ -200,6 +201,32 @@ pnpm dlx openxiangda@2.21.0 source clone <仓库URL> <新目录> --base-url <平
200
201
  使用 CLI 时无需自行调用凭据 API;支持工具不得记录其响应或另建身份体系。
201
202
  未登记的外部仓库先在原工作区执行 `source setup --import`,再使用平台返回的仓库 URL。
202
203
 
204
+ ## 跨站点与平台换绑 {#cross-site}
205
+
206
+ 源码仓库、应用与数据都归属各自站点:A 站点的平台只认 A 站点登记的仓库绑定,
207
+ 发布校验要求当前工作区 `origin` 与该站点绑定仓库一致。站点之间流动的是代码(git),
208
+ 应用的数据库数据、发布历史与环境配置不会自动迁移。
209
+
210
+ ```bash
211
+ openxiangda link # 查看当前绑定、登录态匹配与 origin 归属
212
+ openxiangda link rebind --base-url https://platform-b.example.com # 显式换绑
213
+ ```
214
+
215
+ 一个工作区同一时间只绑定一个平台;换绑只改写 `.openxiangda/link.json`
216
+ (清空旧站点环境列表),不修改 git remote、不迁移登录凭据,也不在远端产生变更。
217
+ 换绑后必须重新登录,`create` 会在新平台幂等初始化同 code 应用。
218
+
219
+ | 场景 | 执行方式 |
220
+ | --- | --- |
221
+ | 在 A 开发、也要发布到 B | 在 B 站点创建同 code 应用并由其管理员启用源码;`source clone <B仓库URL> <B目录> --base-url <B平台>` 得到第二个检出;同步代码用 `git remote add external-source <A仓库URL>` 后 fetch/merge,再 `source push`;在 B 目录执行 `deploy` |
222
+ | 后续发布永久切换到 B | 在原目录依次执行 `link rebind --base-url <B平台>` → `login --base-url <B平台>` → `create <目录> --base-url <B平台>`(幂等初始化)→ `source status` 核对 origin;如报告 `APPLICATION_SOURCE_ORIGIN_CONFLICT`,用 `source setup --import` 把 origin 切到 B 仓库(原 origin 保留为 `external-source`) |
223
+ | 换绑后想回退 | `link rebind --base-url <原平台>` 即恢复;link.json 随仓库提交时也可用 git 还原该文件 |
224
+ | 换绑对象不是同一应用 | 拒绝执行(`OPENXIANGDA_LINK_APP_CODE_CONFLICT`);请在对应应用的目录操作 |
225
+
226
+ 换绑命令对地址做与 `login` 相同的归一化校验;地址拼错时失败会在下一步登录或
227
+ 幂等初始化处暴露,随时可以再次 rebind 修正。详细决策记录见仓库
228
+ `docs/architecture-decisions/workspace-platform-rebind.md`。
229
+
203
230
  ## 检查与交付 {#delivery}
204
231
 
205
232
  只检查时运行 `pnpm openxiangda check`。需要部署测试环境时直接运行 `pnpm openxiangda deploy`,它已包含检查、测试和构建;无需再连续重复运行全部脚本。
@@ -220,8 +247,13 @@ pnpm exec openxiangda --mcp-stdio --cwd <应用绝对路径>
220
247
 
221
248
  ## 工作区登录态
222
249
 
223
- 平台授权保存到所选工作区的 `.openxiangda/session.json`,CLI、MCP、刷新与退出共用该文件。不再读取或迁移旧全局会话;升级后需在每个项目重新登录。已有项目可在根目录或子目录运行 `openxiangda login --base-url <platform>`;`login --cwd <directory>` 和 `auth --cwd <directory>` 明确选定工作区。嵌套应用不会继承父应用会话。
250
+ 平台授权保存到所选工作区的 `.openxiangda/session.json`,CLI、MCP、刷新与退出共用该文件。不再读取或迁移旧全局会话;升级后需在每个项目重新登录。已有项目可在根目录或子目录运行 `openxiangda login --base-url <platform>`;`login --cwd <directory>` 和 `auth status --cwd <directory>` 明确选定工作区。首次 `create` 目标目录尚无会话时,按工作区发现规则向上继承父目录会话,并在 `.git` 仓库边界停止,不跨仓借用账号。
224
251
 
225
252
  创建应用前先执行 `openxiangda login --cwd my-app --base-url <platform>`,再执行 `openxiangda create my-app --base-url <platform>`。仅含受管登录文件的目录允许初始化,凭据会保留并自动加入 Git 忽略规则。
226
253
 
227
254
  本地文件优先;仅在文件缺失时使用成对的 `OPENXIANGDA_BASE_URL` 与 `OPENXIANGDA_TOKEN` CI 环境凭据。损坏、过期或平台不符的文件不会触发其他身份回退。请勿提交或打包登录文件。钉钉支持由 DWS 管理自己的授权,不与平台会话混用。
255
+
256
+ 查看当前绑定与登录态匹配情况运行 `openxiangda link`;登录地址与绑定平台不一致时
257
+ `login` 会拒绝执行(`OPENXIANGDA_PLATFORM_SESSION_MISMATCH`),防止凭据跨平台发送。
258
+ 需要切换站点时先运行 `openxiangda link rebind --base-url <新平台>`,再登录,见
259
+ [跨站点与平台换绑](#cross-site)。
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "schemaVersion": "openxiangda.documentation/v1",
3
- "version": "2.21.0",
3
+ "version": "2.21.1",
4
4
  "topics": [
5
5
  {
6
6
  "id": "getting-started",
7
7
  "title": "安装与开始开发",
8
8
  "file": "getting-started.md",
9
- "sha256": "df3108bb6bc1ce6c0eeb22f1992ae4670a2b07e643e1ca9c7bcf35bd3620059f"
9
+ "sha256": "2d77e554cfc59d096ce2380d0e3f374985957d1e5a580b1a817bdcb53dea6fd1"
10
10
  },
11
11
  {
12
12
  "id": "product-design",
@@ -30,13 +30,13 @@
30
30
  "id": "development",
31
31
  "title": "需求与开发流程",
32
32
  "file": "development.md",
33
- "sha256": "e0cbd3be352b32927b2d99135fb9731f3aa0ad6b4c532091f714815de5555ae0"
33
+ "sha256": "3ff8114424828b6652c23b9e00826ebb87754742751589b65c034ec87eb1f54b"
34
34
  },
35
35
  {
36
36
  "id": "declarations-cheatsheet",
37
37
  "title": "声明速查:一次写对 config",
38
38
  "file": "declarations-cheatsheet.md",
39
- "sha256": "b7943a245f2a40599c90adc72cbc42280c65e22513b3df7cac6c1adaa8e6c48c"
39
+ "sha256": "db20c125256d1491772b82d864c36662894a77aa64d0ccd2d54766a6bbc8e836"
40
40
  },
41
41
  {
42
42
  "id": "application-foundation",
@@ -48,7 +48,7 @@
48
48
  "id": "appspec",
49
49
  "title": "需求、设计与交付记录",
50
50
  "file": "appspec.md",
51
- "sha256": "608562c55c6bbd79d488598df1b02cfa1a5ce8e77ccead9f80e922bbcb6b6093"
51
+ "sha256": "9bc1d14c6df6fbd12531ca1806722bd9e60922d0851846ca37a56d59798cb572"
52
52
  },
53
53
  {
54
54
  "id": "concepts",
@@ -60,7 +60,7 @@
60
60
  "id": "frontend",
61
61
  "title": "页面与标准组件扩展",
62
62
  "file": "frontend.md",
63
- "sha256": "088c516a55aab359a6efeb6165d394919c3df8a4cf7fe2689911800e891692e7"
63
+ "sha256": "7e1fbee98dcaba9c6228e095ebe639055ed2271890b51d6337ce66cb9e647193"
64
64
  },
65
65
  {
66
66
  "id": "field-components",
@@ -72,25 +72,25 @@
72
72
  "id": "data-authz",
73
73
  "title": "数据查询与权限",
74
74
  "file": "data-authz.md",
75
- "sha256": "64fb55b2c9048ed3b62158c01f126984d35c394230aeb176e562bac8a59317ce"
75
+ "sha256": "f00c0dcd79c1ccf9ded0b3131ad304b671cebf475cf1ea20404da665dc2c0bb6"
76
76
  },
77
77
  {
78
78
  "id": "public-access",
79
79
  "title": "无账号的匿名公开访问",
80
80
  "file": "public-access.md",
81
- "sha256": "a93476aab6bc64b321974dfb130a31e8416e0dbfd953bfb51564dbf8667cd1c2"
81
+ "sha256": "6f910f1e6c40f7534a0ed9263ee6af8f3d3e1074faa4523fa26f680c14ee37b6"
82
82
  },
83
83
  {
84
84
  "id": "workflow-events",
85
85
  "title": "审批、事件与通知",
86
86
  "file": "workflow-events.md",
87
- "sha256": "9d78faf470638c33d4bf8b0f7b7540c9adb10e0661d4ec790779c304fc9a7bb6"
87
+ "sha256": "5eec24d16fbcea7f94155b0d68beeacbc7d86dec4f06b6c87dda89245843f621"
88
88
  },
89
89
  {
90
90
  "id": "backend",
91
91
  "title": "按需后端与业务动作",
92
92
  "file": "backend.md",
93
- "sha256": "323d585d3069e0eb7f649eb2c5d0bf0f7344266ce446f539e872b4ec7bc0fbd9"
93
+ "sha256": "2facd758ad802ee18c3291d4dfe1a9d27467f988a1071fcedf9f61f7519c1e36"
94
94
  },
95
95
  {
96
96
  "id": "administration",
@@ -102,7 +102,7 @@
102
102
  "id": "testing",
103
103
  "title": "检查与真实业务验收",
104
104
  "file": "testing.md",
105
- "sha256": "241f800e906213a3bc5a6dbd8931f9a6d55cb0d01d0e5f5be355e7bec6078f5f"
105
+ "sha256": "c40017e019f3ff2ba19b0995c4bfd597d6cc33164faab6a754692f810849429c"
106
106
  },
107
107
  {
108
108
  "id": "delivery",
@@ -114,19 +114,19 @@
114
114
  "id": "upgrading",
115
115
  "title": "版本升级与资料刷新",
116
116
  "file": "upgrading.md",
117
- "sha256": "b90f7c7217651ddac0b1bc2f2419dbfe9c6a6732ffac3e17c114a51da062e219"
117
+ "sha256": "ec2bffabbc80da7e79007867fcd915e1a56a4e4fd85e83df98a405f672beaf74"
118
118
  },
119
119
  {
120
120
  "id": "cli",
121
121
  "title": "CLI 命令参考",
122
122
  "file": "reference/cli.md",
123
- "sha256": "f17c3f2bb78bb61382f51ba7ab36a89f228f116e1f020b30ad833a0f4a89d57d"
123
+ "sha256": "63b50ae974d2bf82eab32e05666ae5c500318b484d3cee96a3930d59bc020a63"
124
124
  },
125
125
  {
126
126
  "id": "mcp",
127
127
  "title": "MCP 配置与工具参考",
128
128
  "file": "reference/mcp.md",
129
- "sha256": "509c274e53f6f70db579807fcdfb2598b59d0ae3814b6e4646291ba802c873de"
129
+ "sha256": "47379c8235995547fd674fb438759cdbbbd72e54c6ed446cedf1aacff0047b74"
130
130
  }
131
131
  ]
132
132
  }
@@ -92,8 +92,11 @@ export default defineOpenXiangdaApp({
92
92
 
93
93
  附件、图片、多选等多值字段使用数组,存储不允许 null;这不意味着用户必须填写。上例照片可选,
94
94
  可以省略或提交空数组,不放入 `requiredFields`。如果业务确实要求上传,资源字段声明 `required: true`,
95
- 公开策略也必须将它列入 `requiredFields`;提交时省略、null 或空数组都会被拒绝。策略可以提出更严格的
96
- 必填要求,但不能漏掉资源已有的必填业务字段。缺失覆盖的诊断会指出字段和 `fields`/`requiredFields` 路径。
95
+ 公开策略也必须将它列入 `requiredFields`;提交时省略、null 或空数组都会被拒绝。`requiredFields` 必须覆盖
96
+ 资源已有的必填业务字段;比模型必填**更严**(列入模型未声明 `required: true` 的字段)时编译器给出警告
97
+ `APP_CONFIG_ANONYMOUS_POLICY_REQUIRED_FIELDS_STRICTER_THAN_MODEL`:标准表单控件不为这些字段生成必填
98
+ 校验,空值提交会被服务端 `REQUIRED_FIELD_MISSING` 拒绝。需要前端必填校验时在模型字段上声明
99
+ `required: true`,使模型必填与策略对齐。缺失覆盖的诊断会指出字段和 `fields`/`requiredFields` 路径。
97
100
 
98
101
  操作按页面实际需要最小声明:
99
102
 
@@ -158,15 +161,22 @@ publicFilters: [{ field: 'enabled', operator: 'eq', value: true }]
158
161
 
159
162
  匿名创建需要平台生成的不可预测字段时,使用 `serverGeneratedFields`。这些字段不属于
160
163
  `fields`,调用方不能在草稿中写入;提交事务会由平台生成随机值并在提交回执的 `generated`
161
- 对象中返回。`random-token` 只适用于不承载身份信息的核验令牌等用途:
164
+ 对象中返回。目前仅支持 `random-token` 一种 kind,只适用于不承载身份信息的核验令牌等用途:
162
165
 
163
166
  ```ts
164
167
  serverGeneratedFields: [{ field: 'qrToken', kind: 'random-token' }]
165
168
  ```
166
169
 
167
- 需要跨资源复核预约窗口等业务不变量时,可声明 `schedule`,绑定两个只读公开策略和资源字段。
168
- 平台会在最终创建事务中重新读取启用校区与规则,校验星期、日期范围、提前小时数和离散时段;页面端
169
- 校验只能改善体验,不能替代这次服务端复核。
170
+ 需要跨资源复核预约窗口等业务不变量时,可声明 `schedule`。它把本策略资源上的
171
+ `campusField`(校区)、`dateField`(日期)和 `timeField`(时间)绑定到另外两个**只读公开
172
+ 策略**(`campusPolicyCode`/`rulePolicyCode`,必须是同 `publicAccess` 中已声明的策略 code),
173
+ 并在规则策略暴露的资源上指定读取哪些字段:`ruleCampusField`(规则所属校区)、
174
+ `ruleWeekdaysField`(开放星期)、`ruleOpenAtField`/`ruleCloseAtField`(每日开放窗口)、
175
+ `ruleSlotMinutesField`(离散时段分钟数)、`ruleAdvanceHoursField`/`ruleAdvanceDaysField`
176
+ (可提前预约的小时/天数上限),以及两个策略各自的启用开关
177
+ `campusEnabledField`/`ruleEnabledField`。十四个键全部必填,字段 code 必须真实存在于
178
+ 对应资源。平台会在最终创建事务中重新读取启用的校区与规则,校验星期、日期范围、提前量
179
+ 和离散时段;页面端校验只能改善体验,不能替代这次服务端复核。
170
180
 
171
181
  子表中的文件引用会自动带上受控的 `resourceCode` 与父字段绑定;应用如需为附件生成下载地址,使用
172
182
  `fileContentUrl(fileId, disposition, variant, resourceCode, parentFieldCode)`,不要自行拼接文件路径。
@@ -22,7 +22,10 @@
22
22
  | `pnpm openxiangda stop` | 远端变更 | 将应用环境缩容为零并保留数据 |
23
23
  | `pnpm openxiangda rollback` | 远端变更 | 回滚测试或生产环境 |
24
24
  | `pnpm openxiangda login` | 本地写入 | 通过平台浏览器授权登录 |
25
+ | `pnpm openxiangda link` | 本地写入 | 查看平台绑定或显式换绑到其他站点 |
25
26
  | `pnpm openxiangda skill` | 本地写入 | 安装当前版本的 AI Skill |
26
27
  | `pnpm openxiangda spec` | 本地写入 | 维护需求、设计、变更与业务验收记录 |
27
28
 
28
29
  只验证时运行 check;部署测试环境时直接运行 deploy,它已包含检查、测试和构建。生产使用 deploy --environment production --from <测试运行ID>;加 --dry-run 只读预览。登录、创建和长期 dev 进程由 CLI 管理。
30
+
31
+ 上表为项目工作区的 Devkit 命令。统一入口在转发给引擎之前还自带 `version`、`update check|install`、`changelog`、`migrate assess` 和 `support status|bootstrap|login|join`,分别用于版本诊断、工具链升级、变更日志、V1 项目迁移评估和支持通道授权;它们不属于 Devkit 注册表,用法与示例见「开始开发」主题(getting-started)。
@@ -139,7 +139,8 @@ MCP 使用项目锁定的根包;在客户端配置下列 stdio 启动参数,
139
139
  "minimum": 0,
140
140
  "maximum": 9007199254740991
141
141
  }
142
- }
142
+ },
143
+ "additionalProperties": false
143
144
  }
144
145
  ```
145
146
 
@@ -169,7 +170,8 @@ MCP 使用项目锁定的根包;在客户端配置下列 stdio 启动参数,
169
170
  },
170
171
  "required": [
171
172
  "deploymentId"
172
- ]
173
+ ],
174
+ "additionalProperties": false
173
175
  }
174
176
  ```
175
177
 
@@ -229,7 +231,8 @@ MCP 使用项目锁定的根包;在客户端配置下列 stdio 启动参数,
229
231
  "production"
230
232
  ]
231
233
  }
232
- }
234
+ },
235
+ "additionalProperties": false
233
236
  }
234
237
  ```
235
238
 
@@ -265,7 +268,8 @@ MCP 使用项目锁定的根包;在客户端配置下列 stdio 启动参数,
265
268
  },
266
269
  "required": [
267
270
  "workflowCode"
268
- ]
271
+ ],
272
+ "additionalProperties": false
269
273
  }
270
274
  ```
271
275
 
@@ -298,7 +302,8 @@ MCP 使用项目锁定的根包;在客户端配置下列 stdio 启动参数,
298
302
  "description": "仅本地完整检查;结果 validationScope=local,不核对现场条件",
299
303
  "type": "boolean"
300
304
  }
301
- }
305
+ },
306
+ "additionalProperties": false
302
307
  }
303
308
  ```
304
309
 
@@ -523,7 +528,8 @@ MCP 使用项目锁定的根包;在客户端配置下列 stdio 启动参数,
523
528
  "description": "持续跟踪原运行至结束,最多 15 分钟",
524
529
  "type": "boolean"
525
530
  }
526
- }
531
+ },
532
+ "additionalProperties": false
527
533
  }
528
534
  ```
529
535
 
@@ -32,6 +32,8 @@ Web 默认保留开发服务回环访问检查。按需 Nest 使用 `tsx --test`
32
32
 
33
33
  应用入口或导航变更必须从平台应用列表实际点击进入,再验证应用根路径、登录返回和刷新后的深链接。使用后台的应用检查 `/admin` 进入首个有权菜单、自定义页只出现一个 Shell、菜单切换与未保存保护、无权限拒绝;纯用户应用验证其声明首页,不强制增加后台。直达开发者给出的 `/home` 或报表链接通过,不能替代平台实际入口验收。
34
34
 
35
+ 平台浏览器入口有两个:`/view/<应用标识>/...` 是正式环境入口,`/dev/<应用标识>/...` 是预发(测试环境)入口,路径中的应用标识即 `app.code`。测试部署通过后从 `/dev/<应用标识>` 做浏览器验收;生产晋级后用 `/view/<应用标识>` 复核。`/dev` 入口要求已登录平台账号(应用内数据和功能仍按应用角色权限控制),未部署的环境返回明确的 503 页面,响应头 `X-OpenXiangda-Environment` 标识当前环境。本地 `pnpm openxiangda dev` 的回环地址只用于开发迭代,不能替代这两个平台入口的验收。
36
+
35
37
  只改文案时验证受影响页面。复杂事务、并发和值转换使用聚焦测试。浏览器验收实际操作并检查错误,不能用模拟响应或空页面加载代替真实角色验收。
36
38
 
37
39
  匿名访问另验证续填、上传、校验、幂等提交、own.list/own.read;另一浏览器应无法获取前一浏览器的记录。工作流按已启用功能检查发起、处理、历史详情和消息跳转,不为未启用通道增加测试负担。
@@ -16,7 +16,7 @@
16
16
 
17
17
  共享校验使用 `configuration-compatibility/v2` 描述,包含校验实现摘要。切换到这一契约时,平台和项目工具链需按同一发布说明配套升级。随后每次发布在构建前核对实现摘要;不要反复修改业务代码来处理工具版本不配套。
18
18
 
19
- 引导式开发使用 workspace-context/v3 与 AppSpec context/v3。升级后按实际业务补齐总纲与关联变更;空模板不能正式测试发布,生产晋级需要原测试版本的验收计划和实际报告。旧候选缺少计划时建立新的测试候选并验收,不自动补写过去的确认或通过记录。普通 dev/check 仍可用于整理和验证尚未完成的项目。
19
+ 引导式开发使用 workspace-context/v3 与 AppSpec context/v4。升级后按实际业务补齐总纲与关联变更;空模板不能正式测试发布,生产晋级需要原测试版本的验收计划和实际报告。旧候选缺少计划时建立新的测试候选并验收,不自动补写过去的确认或通过记录。普通 dev/check 仍可用于整理和验证尚未完成的项目。
20
20
 
21
21
  ## 更新项目 {#upgrade}
22
22
 
@@ -58,6 +58,46 @@ events: {
58
58
  外部 Webhook 和自定义代码仍使用已有签名、回执、重试及接收端幂等协议,按至少
59
59
  一次投递处理。轻量操作历史仍通过已有审计 API 查询,不依赖是否订阅了事件。
60
60
 
61
+ ## 定时与日期触发
62
+
63
+ 除订阅平台数据事件外,`events` 还能声明两类自有时程触发器;两者都只负责在到期时
64
+ 发出应用事件,业务效果仍由订阅(含 `execution: native-data`)或应用后端处理。
65
+
66
+ `events.timers` 按 cron 周期发事件。`code` 为 kebab-case 且唯一;`eventType` 必须是
67
+ `events.schemas` 已声明的应用事件;`cronExpression` 为六段 cron(秒 分 时 日 月 周),
68
+ `timezone` 使用 IANA 名称(如 `Asia/Shanghai`),最短触发间隔为 60 秒;`misfirePolicy`
69
+ 目前仅支持 `coalesce_one`(错过合并为一次);`payload` 是普通对象,必须完整满足所引用
70
+ 事件的 JSON Schema 且不超过 64 KiB。定时声明属于环境中立的 AppVersion,不写
71
+ `environmentKey`;启用/暂停和下次触发时间由平台按环境管理。
72
+
73
+ ```ts
74
+ events: {
75
+ schemas: [{
76
+ eventType: 'app.report.digest.v1',
77
+ dataSchemaVersion: '1',
78
+ jsonSchema: {
79
+ type: 'object', additionalProperties: false,
80
+ required: ['kind'], properties: { kind: { type: 'string' } },
81
+ },
82
+ }],
83
+ timers: [{
84
+ code: 'daily-digest',
85
+ eventType: 'app.report.digest.v1',
86
+ cronExpression: '0 0 9 * * *',
87
+ timezone: 'Asia/Shanghai',
88
+ payload: { kind: 'daily' },
89
+ }],
90
+ },
91
+ ```
92
+
93
+ `events.dateTriggers` 相对记录的 date/datetime 字段发事件:字段值加 `offset`(ISO-8601
94
+ 时长,十年内,如 `-PT1H` 表示提前一小时)到达时触发。适合到期提醒、超期升级等场景;
95
+ 同一条记录的字段更新后按新值重算。`code` 唯一,`eventType` 同样引用已声明应用事件。
96
+
97
+ 两类触发器各最多 100 条。事件 Schema 用 `events.schemas` 声明
98
+ (`{ eventType, dataSchemaVersion, jsonSchema, sensitiveFields? }`),`eventType` 遵循
99
+ `xxx.yyy.v1` 版本后缀模式;触发器只发事件,不直接写数据或调用流程。
100
+
61
101
  ## 标准详情与当前用户入口
62
102
 
63
103
  普通记录、流程记录、任务和实例复用同一详情框架。流程详情提供申请内容、审批历史和变更记录三个标签页;管理员在当前抽屉或页面中切换到普通表单编辑,直接保存并自动留下变更记录,审批结果保持不变。PC 子表在表格内编辑,父表提交时统一校验。
@@ -113,6 +153,8 @@ Workflow 发起只接受 `{ resourceCode, id }` 形式的 `dataRef`,并要求
113
153
 
114
154
  首期只支持 `approval`、`condition`、`end`,以及 `single`、`any`、`all`、`sequence` 审批模式。标准操作为提交、同意、拒绝、退回、重新提交、转交、委托、前/后加签、撤回、管理员改派、管理员终止和不改变流程状态的催办。
115
155
 
156
+ 除命令集之外,平台为实例管理员提供两个维护动作:`admin_jump`(把处于运行或退回状态的实例跳转到指定节点)与 `admin_delete`(删除实例,可选同时删除表单数据、是否触发自动化)。两者走平台管理端点的预览/执行两步流程并要求同源浏览器请求,不属于应用声明的工作流命令,也不占用 `commandToken` 命令合同。
157
+
116
158
  复杂业务状态机继续放在应用领域服务。不要把任意 JavaScript、Service Task、BPMN、通用长事务或业务记录复制进 Workflow。
117
159
 
118
160
  所有页面和消息动作必须来自后端 Workflow Surface 的 `operations[]`。前端、模板和渠道 Adapter 不自行推断操作权限。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openxiangda",
3
- "version": "2.21.0",
3
+ "version": "2.21.1",
4
4
  "description": "OpenXiangda 2.0 的统一命令、应用 SDK、MCP 与中文 AI 技能资料。",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -60,13 +60,13 @@
60
60
  "antd-mobile": "5.42.3",
61
61
  "dayjs": "1.11.18",
62
62
  "docx-preview": "0.3.7",
63
- "openxiangda-cli": "2.4.25",
63
+ "openxiangda-cli": "2.5.0",
64
64
  "openxiangda-contracts": "2.16.1",
65
- "openxiangda-devkit-core": "2.18.4",
65
+ "openxiangda-devkit-core": "2.19.0",
66
66
  "openxiangda-legacy": "npm:openxiangda@1.0.269",
67
- "openxiangda-mcp": "2.0.31",
67
+ "openxiangda-mcp": "2.0.32",
68
68
  "openxiangda-nest": "2.4.6",
69
- "openxiangda-skill-kit": "2.3.4",
69
+ "openxiangda-skill-kit": "2.3.5",
70
70
  "xlsx": "https://github.com/1377385356/openxiangda/releases/download/vendor-mirror/xlsx-0.20.3.tgz"
71
71
  },
72
72
  "peerDependencies": {
@@ -132,28 +132,29 @@
132
132
  },
133
133
  "openxiangdaRelease": {
134
134
  "schemaVersion": "openxiangda.release-notes/v1",
135
- "version": "2.21.0",
135
+ "version": "2.21.1",
136
136
  "status": "reviewed",
137
- "title": "OpenXiangda 2.21.0:Agent 原生视觉开发流程",
138
- "summary": "移除效果和维护成本不匹配的 OpenDesign 运行时、命令与桥接层,把界面设计收敛为同一 AI Agent 内完成的闭环:按需使用 Image 2.5 等图片能力生成少量视觉参考,直接实现真实 React 页面,并以浏览器交互证据完成修正和验收。",
137
+ "title": "OpenXiangda 2.21.1:工作区平台绑定查看与显式换绑",
138
+ "summary": "新增 `openxiangda link` 命令:只读查看工作区绑定的平台、登录态匹配与 git origin 归属,并通过 `link rebind --base-url` 提供此前缺失的受支持换绑路径,替代手改 `.openxiangda/link.json`;同时补齐应用交接与跨站点发布的使用说明。",
139
139
  "newFeatures": [
140
- "提供 Agent 原生设计工作流:视觉方向、React 实现、浏览器修正和 AppSpec 记录在同一个 OpenXiangda 工作区内完成。",
141
- "支持把 Image 2.5 或当前可用的图片生成能力作为可选视觉参考;图片不可用或质量不足时可直接基于设计约束、成熟组件和浏览器迭代继续开发。"
140
+ "新增 `openxiangda link` 命令:默认只读展示工作区平台绑定(appCode、平台地址、环境列表)、当前登录态匹配情况和 git origin 归属提示。",
141
+ "新增 `openxiangda link rebind --base-url <平台>` 显式换绑:只改写本地绑定文件(appCode 取自工作区声明、地址归一化、清空旧站点环境列表),不调用平台 API、不修改 git remote、不迁移登录凭据;绑定属于其他应用时拒绝执行,目标与当前一致时幂等,换错可随时 rebind 回退。",
142
+ "文档新增「跨站点与平台换绑」章节,覆盖应用交接给其他开发者、A 站点开发发布到 B 站点、发布永久切换站点与回退的完整步骤。"
142
143
  ],
143
144
  "fixes": [
144
- "移除 openxiangda design 命令、OpenDesign CLI/桌面/MCP 桥接、环境变量、内置方法资源和上游同步流程,避免 AI 自动开发依赖额外设计项目。",
145
- "统一文档、Skill 和工作区模板中的界面开发指导,同时保留 AppSpec 设计资产、平台 Shell、Field Kit、权限、Data API、主题边界和真实角色验收契约。"
145
+ "`login`、平台会话校验与错误恢复目录中「平台不一致」的提示全部改为指向显式换绑命令,避免使用者手改 link 文件绕过检查。",
146
+ "随车发布此前评审的 Skill 文档一致性审计:声明速查表回填行级策略条目,补齐 `/view`、`/dev` 浏览器入口、mutationOwner 错误族、events.timers 等平台能力与授权来源声明文档。"
146
147
  ],
147
148
  "affectedUsers": [
148
- "使用 OpenXiangda 2.0 创建或改版管理后台、PC 用户页和移动端页面的 AI 开发会话与维护者。"
149
+ "在多个站点之间迁移或发布 OpenXiangda 2.0 应用、以及把应用交接给其他开发者的开发与维护会话。"
149
150
  ],
150
151
  "upgradeSteps": [
151
- "将应用精确依赖升级到 openxiangda 2.21.0,按锁文件安装,并执行 pnpm openxiangda skill install --force 刷新本地 Skill。",
152
- "删除自动化脚本中对 openxiangda design 的调用;改为按 Agent 原生设计工作流直接实现真实页面并运行项目检查与浏览器验收。"
152
+ "将应用精确依赖升级到 openxiangda 2.21.1,按锁文件安装,并执行 `pnpm openxiangda skill install --force` 刷新本地 Skill。",
153
+ "需要切换发布站点时,先运行 `openxiangda link` 查看当前绑定,再按文档执行 `openxiangda link rebind --base-url <新平台>`,随后登录新平台并运行 `openxiangda create <目录> --base-url <新平台>` 幂等初始化。"
153
154
  ],
154
155
  "knownLimitations": [
155
- "Image 2.5 等图片能力只生成参考,不定义交互、权限、业务数据或验收结论;最终结果仍以真实 React 页面和浏览器证据为准。",
156
- "本版本不会自动迁移或删除应用工作区中既有的 AppSpec 设计资产。"
156
+ "换绑只切换本地绑定,不在新平台创建应用或源码仓库;新平台的应用初始化与源码接入仍由 create source 命令完成。",
157
+ "站点之间流动的只有代码(git);应用的数据库数据、发布历史与环境配置不会自动迁移。"
157
158
  ],
158
159
  "issues": [],
159
160
  "compatibility": {
@@ -162,8 +163,8 @@
162
163
  "platform": "无需服务端、数据库或平台容器升级。",
163
164
  "v1": "V1 工作区与维护引擎不受影响。"
164
165
  },
165
- "sha256": "18c7d0d0c508dec38ab1685d649103dcd820c75203d4a67eddd9c17aabc7651d",
166
- "url": "https://github.com/1377385356/openxiangda/releases/tag/v2.21.0"
166
+ "sha256": "a9e73cc57ddbb5febded6000c8c88a0fc83a64802d29bd82572fe6b65422e715",
167
+ "url": "https://github.com/1377385356/openxiangda/releases/tag/v2.21.1"
167
168
  },
168
169
  "scripts": {
169
170
  "build": "node ../../scripts/prune-package-dist.mjs && tsc -p tsconfig.json && node scripts/copy-assets.mjs",
@@ -0,0 +1,36 @@
1
+ {
2
+ "schemaVersion": "openxiangda.release-notes/v1",
3
+ "version": "2.21.1",
4
+ "status": "reviewed",
5
+ "title": "OpenXiangda 2.21.1:工作区平台绑定查看与显式换绑",
6
+ "summary": "新增 `openxiangda link` 命令:只读查看工作区绑定的平台、登录态匹配与 git origin 归属,并通过 `link rebind --base-url` 提供此前缺失的受支持换绑路径,替代手改 `.openxiangda/link.json`;同时补齐应用交接与跨站点发布的使用说明。",
7
+ "newFeatures": [
8
+ "新增 `openxiangda link` 命令:默认只读展示工作区平台绑定(appCode、平台地址、环境列表)、当前登录态匹配情况和 git origin 归属提示。",
9
+ "新增 `openxiangda link rebind --base-url <平台>` 显式换绑:只改写本地绑定文件(appCode 取自工作区声明、地址归一化、清空旧站点环境列表),不调用平台 API、不修改 git remote、不迁移登录凭据;绑定属于其他应用时拒绝执行,目标与当前一致时幂等,换错可随时 rebind 回退。",
10
+ "文档新增「跨站点与平台换绑」章节,覆盖应用交接给其他开发者、A 站点开发发布到 B 站点、发布永久切换站点与回退的完整步骤。"
11
+ ],
12
+ "fixes": [
13
+ "`login`、平台会话校验与错误恢复目录中「平台不一致」的提示全部改为指向显式换绑命令,避免使用者手改 link 文件绕过检查。",
14
+ "随车发布此前评审的 Skill 文档一致性审计:声明速查表回填行级策略条目,补齐 `/view`、`/dev` 浏览器入口、mutationOwner 错误族、events.timers 等平台能力与授权来源声明文档。"
15
+ ],
16
+ "affectedUsers": [
17
+ "在多个站点之间迁移或发布 OpenXiangda 2.0 应用、以及把应用交接给其他开发者的开发与维护会话。"
18
+ ],
19
+ "upgradeSteps": [
20
+ "将应用精确依赖升级到 openxiangda 2.21.1,按锁文件安装,并执行 `pnpm openxiangda skill install --force` 刷新本地 Skill。",
21
+ "需要切换发布站点时,先运行 `openxiangda link` 查看当前绑定,再按文档执行 `openxiangda link rebind --base-url <新平台>`,随后登录新平台并运行 `openxiangda create <目录> --base-url <新平台>` 幂等初始化。"
22
+ ],
23
+ "knownLimitations": [
24
+ "换绑只切换本地绑定,不在新平台创建应用或源码仓库;新平台的应用初始化与源码接入仍由 create 与 source 命令完成。",
25
+ "站点之间流动的只有代码(git);应用的数据库数据、发布历史与环境配置不会自动迁移。"
26
+ ],
27
+ "issues": [],
28
+ "compatibility": {
29
+ "node": ">=24",
30
+ "workspaceGenerations": "v2",
31
+ "platform": "无需服务端、数据库或平台容器升级。",
32
+ "v1": "V1 工作区与维护引擎不受影响。"
33
+ },
34
+ "sha256": "a9e73cc57ddbb5febded6000c8c88a0fc83a64802d29bd82572fe6b65422e715",
35
+ "url": "https://github.com/1377385356/openxiangda/releases/tag/v2.21.1"
36
+ }
@@ -4,7 +4,7 @@
4
4
  {
5
5
  "name": "openxiangda-v2",
6
6
  "description": "使用 OpenXiangda 2.0 从模糊业务想法、已有资料或具体变更出发,通过对话发现模块、完成详细产品设计,由当前 AI Agent 按需用 Image 2.5 等图片能力形成视觉参考,直接实现真实页面并在浏览器修正,再检查和交付应用;维护 1.x 应用时使用对应的 1.x 技能。",
7
- "sha256": "5b13a19363ecefe2417d9b6e730db7c7d8eaa12bbab253fffe9bef6fb5820886"
7
+ "sha256": "da8d59598a9fbd9217f7df8e730e9dafa32716144676b29d669c8022bef4070b"
8
8
  }
9
9
  ]
10
10
  }
@@ -40,10 +40,10 @@ AI 接到新应用、页面或改版任务时,在同一个 OpenXiangda 工作
40
40
  未创建工作区时使用本 Skill 随根包发布的精确版本:
41
41
 
42
42
  ```bash
43
- pnpm dlx openxiangda@2.21.0 auth status --cwd <应用目录> --base-url <平台地址> --json
44
- pnpm dlx openxiangda@2.21.0 login --cwd <应用目录> --base-url <平台地址>
45
- pnpm dlx openxiangda@2.21.0 create <应用目录> --base-url <同一平台地址>
46
- pnpm dlx openxiangda@2.21.0 skill install --force
43
+ pnpm dlx openxiangda@2.21.1 auth status --cwd <应用目录> --base-url <平台地址> --json
44
+ pnpm dlx openxiangda@2.21.1 login --cwd <应用目录> --base-url <平台地址>
45
+ pnpm dlx openxiangda@2.21.1 create <应用目录> --base-url <同一平台地址>
46
+ pnpm dlx openxiangda@2.21.1 skill install --force
47
47
  ```
48
48
 
49
49
  创建前把产品要求的目标平台明确带入命令,不从旧登录态推断站点。已有工作区从原绑定恢复,平台不一致时先解决登录与目标,不改 link 文件跨站创建。
@@ -36,11 +36,18 @@ appspec/
36
36
  ```bash
37
37
  pnpm openxiangda spec init
38
38
  pnpm openxiangda spec new booking-window --title "限制可预约时段" --risk L2
39
+ pnpm openxiangda spec add-capability CAP-REPAIR-REQUEST --title "报修申请能力" --resources repair-requests
39
40
  pnpm openxiangda spec context booking-window --json
40
41
  pnpm openxiangda spec check
41
42
  ```
42
43
 
43
- 已有工作区优先用 `spec new` 生成本地记录。访谈尚未创建应用时,可在 `appspec/changes/active/<变更ID>.md` 手工建草稿:front matter 使用 `schema: openxiangda.appspec/change/v1`、与文件名一致的 `id`、`title`、`status: draft`、`currentSpec: pending`、适用的 `risk`,以及 `documents: [实际评审ID]`。不必为整理设计先创建远端应用。
44
+ 已有工作区优先用 `spec new` 生成本地记录。跨变更复用的能力规格用
45
+ `spec add-capability <CAP-*> --title <名称>` 单独建立(可带 `--resources`/`--actions`
46
+ 逗号分隔引用),排他创建 `appspec/capabilities/` 下的规格文件,绝不覆盖同名文件;
47
+ ChangeSpec 通过 `capabilities` 引用这些 CAP-* 规格。访谈尚未创建应用时,可在
48
+ `appspec/changes/active/<变更ID>.md` 手工建草稿:front matter 使用
49
+ `schema: openxiangda.appspec/change/v1`、与文件名一致的 `id`、`title`、`status: draft`、
50
+ `currentSpec: pending`、适用的 `risk`,以及 `documents: [实际评审ID]`。不必为整理设计先创建远端应用。
44
51
 
45
52
  | 阶段或风险 | ChangeSpec 必需正文 |
46
53
  | --- | --- |
@@ -189,6 +189,44 @@ await businessData.transaction({
189
189
  分派已接受后撤销角色,不会自动撤销历史分派;后续处理动作必须重新验证当前权限,
190
190
  由管理员重新分派。此规则应写入 AppSpec,并实测撤销先发生和分派先发生两种顺序。
191
191
 
192
+ ## AI 能力目录与 MCP Facade {#ai-catalog}
193
+
194
+ 编译器为每个应用生成不可变的 AI 能力目录:资源的标准 CRUD 面(query/get/create/update/delete,
195
+ 按 `generated` 与 `mutationOwner` 实际开放的操作)自动成为 `generatedCrud` 能力;`backend.operations[]`
196
+ 中带 `ai` 声明的操作成为 `customAction` 能力。目录摘要(`aiCatalogDigest`)随当前声明生成并进入契约;
197
+ 平台 `GET .../native/ai/catalog` 按当前用户角色与字段权限裁剪后返回。为操作声明 `ai` 即把它加入目录:
198
+
199
+ ```ts
200
+ operations: [{
201
+ code: 'dispatch-repair', method: 'POST', path: '/dispatch', capability: dispatchCapability,
202
+ ai: {
203
+ name: '受理派单', description: '把报修单派给指定技师并写入派工记录',
204
+ risk: 'write', resources: ['repair-requests'],
205
+ sideEffects: ['更新报修单状态为处理中', '写入一条派工记录'],
206
+ },
207
+ }],
208
+ ```
209
+
210
+ `ai` 的规则:`name` 与 `description` 必填;`risk` 取 `read | write | destructive | external`,
211
+ `read` 等价于 `method: 'GET'`,DELETE 方法只能是 `destructive` 或 `external`;`resources`
212
+ 引用 1–16 个已声明资源;`sideEffects` 最多 20 条——只读必须为零,写操作至少一条具体副作用;
213
+ `concurrency` 可选 `none | revision`,`timeoutMs` 限 100–30000。
214
+
215
+ 宿主(平台 AI 网关)用该目录装配 MCP Facade,应用不自己实现协议:
216
+
217
+ - **单应用 Facade**(`createApplicationAiMcpServer`):每个能力一个工具,只读能力直接以
218
+ `capability.code` 命名执行;写能力命名为 `capability.code + '.preview'`,只生成预览不落库;
219
+ 存在写能力时额外提供 `openxiangda.ai.confirm`(入参 `previewId`),在用户明确确认预览摘要后
220
+ 由宿主重新校验身份、权限、版本和幂等性再执行。目录本体通过资源 `openxiangda://ai/catalog`
221
+ 读取。
222
+ - **平台聚合 Facade**(`createAggregatedAiMcpServer`):跨应用统一工具面
223
+ `apps.search`、`resources.describe`、`records.query`、`records.get`、`records.create`、
224
+ `records.update`、`records.delete`、`actions.invoke`、`mutations.confirm`;聚合器只把工具
225
+ 路由到所属应用的执行器,不合并权限、不产生第二个数据或授权边界。
226
+
227
+ 这套 Facade 服务平台侧 AI 入口,与应用工作区开发用的 MCP(见[MCP 参考](mcp.md))
228
+ 是两组互不重叠的工具。
229
+
192
230
  ## 启动与依赖注入 {#bootstrap}
193
231
 
194
232
  ```ts
@@ -22,7 +22,10 @@
22
22
  | `pnpm openxiangda stop` | 远端变更 | 将应用环境缩容为零并保留数据 |
23
23
  | `pnpm openxiangda rollback` | 远端变更 | 回滚测试或生产环境 |
24
24
  | `pnpm openxiangda login` | 本地写入 | 通过平台浏览器授权登录 |
25
+ | `pnpm openxiangda link` | 本地写入 | 查看平台绑定或显式换绑到其他站点 |
25
26
  | `pnpm openxiangda skill` | 本地写入 | 安装当前版本的 AI Skill |
26
27
  | `pnpm openxiangda spec` | 本地写入 | 维护需求、设计、变更与业务验收记录 |
27
28
 
28
29
  只验证时运行 check;部署测试环境时直接运行 deploy,它已包含检查、测试和构建。生产使用 deploy --environment production --from <测试运行ID>;加 --dry-run 只读预览。登录、创建和长期 dev 进程由 CLI 管理。
30
+
31
+ 上表为项目工作区的 Devkit 命令。统一入口在转发给引擎之前还自带 `version`、`update check|install`、`changelog`、`migrate assess` 和 `support status|bootstrap|login|join`,分别用于版本诊断、工具链升级、变更日志、V1 项目迁移评估和支持通道授权;它们不属于 Devkit 注册表,用法与示例见「开始开发」主题(getting-started)。
@@ -29,6 +29,41 @@ pnpm openxiangda check
29
29
 
30
30
  角色成员、维度授权和平台管理员由平台管理面维护,不属于应用开发 CLI。
31
31
 
32
+ ### 授权来源声明
33
+
34
+ 应用可以在 `authz` 中声明四类授权来源,让平台从业务数据投影出维度授权、应用角色成员和
35
+ 行级关系授权;投影事实由平台物化并按当前配置重算,应用不维护第二份权限状态。各声明的
36
+ `userIdField` 等字段路径支持 `field`、`field.value` 和 `field.snapshot.<子字段>` 投影形式。
37
+
38
+ - `scopeDimensions`:`{ code, name, resourceCode?, valueType?: 'string'|'uuid',
39
+ hierarchyMode?: 'flat'|'self_parent', valueSource?: { kind: 'native_resource',
40
+ resourceCode, labelField, enabledField? } }`。定义数据范围的取值域;`valueSource` 把
41
+ Native 资源绑定取值来源,选择器只展示平台按当前 membership 与 create/update 闭包返回的
42
+ 值;`self_parent` 表示取值记录通过父引用形成层级。
43
+ - `scopeSources`:`{ code, name, resourceCode, subject, grants, operationField?,
44
+ enabledField?, effectiveFromField?, effectiveToField?, failureMode }`。从业务资源行投影
45
+ 维度授权:`subject` 为 `{ type: 'user', userIdField }` 或
46
+ `{ type: 'role_membership', userIdField, roleCode }`,`grants: [{ dimensionCode,
47
+ valueField, parentValueField? }]` 把行字段值授为对应维度;生效窗口和启用开关由字段控制;
48
+ `failureMode: 'strict'` 投影失败即判定失败,`'last_known_good'` 在源数据暂不可读时沿用
49
+ 最近一次成功投影。
50
+ - `roleMembershipSources`:`{ code, name, resourceCode, userIdField, roleCode,
51
+ enabledField?, effectiveFromField?, effectiveToField?, failureMode: 'strict' }`。从业务
52
+ 数据行授予应用角色,例如"成员表"一行代表某人拥有某角色。
53
+ - `relationshipGrantSources`:`{ code, name, resourceCode, subject, relationCode,
54
+ targetResourceCode, resourceIdField, operations, enabledField?, effectiveFromField?,
55
+ effectiveToField?, failureMode: 'strict' }`。通过业务关系授予目标资源上指定操作
56
+ (1–20 个)的行级授权,例如"订单负责人可更新该订单"。
57
+
58
+ `authorizationTransitions: [{ fromAuthzDigest, removeRoleCodes?,
59
+ removeCapabilityCodes?, reason }]` 记录授权合同的关键收缩:从 `fromAuthzDigest`
60
+ (64 位十六进制)标识的授权版本移除角色或能力时,必须逐条声明并给出原因,平台在两个授权
61
+ 修订之间核对覆盖情况后才放行发布;它不用于新增授权。
62
+
63
+ `authz.capabilities` 的完整形状是 `{ code, kind: 'backend' | 'ui', name, description? }`;
64
+ `kind: 'ui'` 声明页面级能力,`kind: 'backend'` 声明后端操作能力并配合
65
+ [按需后端](backend.md)的 `platformAccess` 使用。
66
+
32
67
  自定义 PC/移动页面需要维护当前应用角色时,使用
33
68
  `openxiangda/core` 的 `loadRoleManagementCatalog`、
34
69
  `listRoleMemberships`、`searchRoleManagementUsers`、成员 mutation 与
@@ -41,6 +41,10 @@
41
41
  | workflow definition 必须显式 `launch`(编译器强制) | `definitions: [{ version: 1, definition, launch: { mode: 'standalone' } }]` |
42
42
  | option/user/department/resource-ref/cascade 字段投影进工作流事实是 { label, value } 对象,不能声明为标量;条件比较用 `<fact>.value` | `inputSchema.properties.urgency = { type: 'object', ... }` + `path: 'urgency.value'` |
43
43
  | `cascade.*` 的写入/比较值形状是**数组路径** | `category: [{ label: '办公设备', value: 'office' }]` |
44
+ | 行级策略按**角色并集取最宽**:多角色身份的可见行 = 各角色可见行的并集;基线角色与限制性规则并存时,限制会被宽松规则覆盖(平台语义,不是缺陷) | 给某角色做行级收窄前,先确认其角色并集里没有更宽的 unrestricted 角色 |
45
+ | `matchMode: 'AND'` 且多条规则面向**不同角色**时,非目标角色规则恒 false → 全拒(编译器会警告) | 多角色白名单用 `matchMode: 'OR'`;单条规则用 AND/OR 等价 |
46
+ | `created_by`/`updated_by` 审计列支持 `current_user` 行规则("只看自己创建");运行时 WITH CHECK 正向匹配依赖平台版本,使用前在目标平台实测确认 | `{ subject: 'current_user', field: 'created_by', roleCodes: ['app-user'] }` |
47
+ | 匿名策略 `requiredFields` 比模型必填更严格时编译器**警告**:标准控件不为这些字段生成必填校验,空值提交会被服务端 `REQUIRED_FIELD_MISSING` 拒绝 | 在模型字段上声明 `required: true`,使模型必填与策略对齐 |
44
48
  | 平台保留能力(如 `app:<app>:directory:read`)**不能**在 `capabilities` 里重复声明,直接在角色中引用即可 | `const directoryRead = \`app:\${APP_CODE}:directory:read\`` → `roles: [{ code: 'admin', capabilities: [directoryRead] }]` |
45
49
  | 资源 CRUD 能力码用 `resourceCapabilityCodes(appCode, resourceCode)` 生成 | `const crud = resourceCapabilityCodes(APP_CODE, 'repair-requests')` → `capabilities: [crud.read, crud.create]` |
46
50
  | `authenticatedUserRoleCode` 是平台登录用户的基线角色 | `authz: { authenticatedUserRoleCode: 'app-user', ... }` |
@@ -190,11 +194,13 @@ export default defineOpenXiangdaApp({
190
194
  { code: 'app-user', name: '应用用户', capabilities: [requestCrud.read, requestCrud.create] },
191
195
  { code: 'admin', name: '管理员', capabilities: [requestCrud.read, requestCrud.create, requestCrud.update, requestCrud.delete] },
192
196
  ],
193
- scopeDimensions: [], scopeSources: [], dataPolicies: [], authorizationTransitions: [],
197
+ scopeDimensions: [], scopeSources: [], roleMembershipSources: [], relationshipGrantSources: [], dataPolicies: [], authorizationTransitions: [],
194
198
  },
195
199
  });
196
200
  ```
197
201
 
202
+ 授权来源声明的字段路径(`userIdField` 等)支持 `field` / `field.value` / `field.snapshot.<子字段>` 三种投影形式;`roleMembershipSources`/`relationshipGrantSources` 只允许 `failureMode: 'strict'`,`scopeSources` 额外允许 `last_known_good`。`authorizationTransitions` 只记录移除(`removeRoleCodes`/`removeCapabilityCodes`)并要求 `fromAuthzDigest` 匹配原授权摘要;新增授权不需要 transition。
203
+
198
204
  `request-items` 不出现在 `crud` 里:子表行随 `requests` 表单的 `subtable` 字段写入(在 `requests.fields` 里补 `{ code: 'items', type: 'subtable', subtable: { resourceCode: 'request-items', foreignKey: 'requestId', orderField: 'sortOrder', maxRows: 20 } }`)。
199
205
 
200
206
  ## 图片上传的像素上限
@@ -38,8 +38,8 @@
38
38
  | 列表、筛选、排序、分页接口 | `createNativeResourceClient` 的 `list`,服务端条件树与分页 | [前端数据访问](frontend.md#data-access) |
39
39
  | 新增 / 编辑 / 删除接口 | 标准 CRUD 页面,或同一客户端的 `create` / `update` / `remove`(`expectedRevision` 乐观锁) | [前端数据访问](frontend.md#data-access) |
40
40
  | 提交防重、幂等重试 | `transactNativeData` / 事务请求自带 `idempotencyKey` 幂等回执 | [前端数据访问](frontend.md#data-access)、[按需后端](backend.md#business-action) |
41
- | 时间窗、状态前置、指定人角色校验 | 平台事务守卫:`operation-time`、`record-assert`、`record-exists`、`role-member` | [按需后端](backend.md#business-action) |
42
- | 统计报表数据 | Data API 服务端聚合 `batchAggregateNativeResources`(单个指标也用它),前端不拉全量求和 | [前端](frontend.md#component-selection) |
41
+ | 时间窗、状态前置、指定人角色校验 | 平台事务守卫:`operation-time`、`record-assert`、`record-exists`、`record-match`、`role-member`、`databaseNowAssertion` | [按需后端](backend.md#business-action) |
42
+ | 统计报表数据 | Data API 服务端聚合 `batchAggregateNativeResources`(单个指标也用它),前端不拉全量求和 | [前端](frontend.md#data-access) |
43
43
  | 导入 / 导出 | 标准 CRUD 的 `import` / `export` 动作声明 | [业务模块](application-foundation.md) |
44
44
  | 跨模型原子写、外部 API、硬件或第三方推送 | Nest 具名 operation + 平台事务,必要时事务内 `emitEvent` | [按需后端](backend.md) |
45
45
 
@@ -365,7 +365,14 @@ renderer,但共享同一授权与命令生命周期。主决策操作固定在
365
365
 
366
366
  资源用 `mutationOwner: 'native' | 'action' | 'readonly' | 'workflow'` 声明 mutation owner,
367
367
  并可用 `generated.list/detail/create/update/delete` 精确选择标准 surface。非 Native owner
368
- 不能生成或向应用角色授予 Native mutation;零可写业务字段不能开放 create/update
368
+ 不能生成或向应用角色授予 Native mutation;零可写业务字段不能开放 create/update。运行时直接对
369
+ 非 Native owner 资源调用 Data API create/update/delete 会被平台以 409
370
+ `OPENXIANGDA_NATIVE_DATA_CAPABILITY_MISSING` 拒绝——这不是权限配置错误,而是 mutation owner
371
+ 契约:`readonly` 资源只能读,`action` 资源的写入走 named operation,`workflow` 资源由流程命令推进。
372
+ `workflow` 资源的数据修正不绕过流程:平台为应用超级管理员提供专门的 correction 入口(记录的
373
+ correction-surface/corrections),保留审批快照;非 workflow 资源走该入口返回 409
374
+ `WORKFLOW_OWNER_REQUIRED`,修正失败错误码为 `OPENXIANGDA_WORKFLOW_CORRECTION_<reason>` 族。
375
+ 排查这两类错误先核对资源的 `mutationOwner` 声明和期望的写入通道,不要试图用扩角色绕过。
369
376
  Workflow definition 用 `launch.mode` 声明 `standalone`、`custom-page`、`hidden-handoff` 或
370
377
  `work-center-only`;只有 `standalone` 可进入菜单,`hidden-handoff` 保留同一标准 PC/移动路由
371
378
  但不进菜单。缺省 submission 使用 compiler 生成的标准 process operation。action-owned 资源
@@ -68,10 +68,10 @@ MCP 服务随项目根包一起安装,AI 客户端的 stdio 连接仍需配置
68
68
  以下命令的版本占位符由随包资料替换为该根包的精确版本。网站源码阅读者应先确认要使用的发行版本。
69
69
 
70
70
  ```bash
71
- pnpm dlx openxiangda@2.21.0 skill install --force
72
- pnpm dlx openxiangda@2.21.0 auth status --base-url <平台地址> --json
73
- pnpm dlx openxiangda@2.21.0 login --cwd my-app --base-url https://platform.example.com
74
- pnpm dlx openxiangda@2.21.0 create my-app --base-url https://platform.example.com
71
+ pnpm dlx openxiangda@2.21.1 skill install --force
72
+ pnpm dlx openxiangda@2.21.1 auth status --base-url <平台地址> --json
73
+ pnpm dlx openxiangda@2.21.1 login --cwd my-app --base-url https://platform.example.com
74
+ pnpm dlx openxiangda@2.21.1 create my-app --base-url https://platform.example.com
75
75
  cd my-app
76
76
  pnpm openxiangda context --json
77
77
  pnpm openxiangda dev
@@ -79,7 +79,7 @@ pnpm openxiangda dev
79
79
 
80
80
  将两处示例平台地址替换为同一个目标地址。`create` 先核对目标与当前登录平台,再创建本地目录、安装依赖并初始化远端应用;新应用不会自动沿用文件中最后登录的站点。CI 使用原有成对的 `OPENXIANGDA_BASE_URL` 和 `OPENXIANGDA_TOKEN` 时,可以由该显式地址指定目标。
81
81
 
82
- 初始化中断后重试同一命令;已有目录会核对原平台绑定,不能通过 `create` 改绑到其他站点。地址不一致时先检查目标并登录正确的平台,不修改 link 文件绕过检查,也不要使用内部 provision 接口另建应用。创建操作应在用户要求创建应用的范围内执行。
82
+ 初始化中断后重试同一命令;已有目录会核对原平台绑定,不能通过 `create` 改绑到其他站点。地址不一致时先检查目标并登录正确的平台;确需把工作区切换到其他站点时,使用 `openxiangda link rebind --base-url <新平台>` 显式换绑(见[跨站点与换绑](#cross-site)),不手改 link 文件绕过检查,也不要使用内部 provision 接口另建应用。创建操作应在用户要求创建应用的范围内执行。
83
83
 
84
84
  进入项目后使用 `pnpm openxiangda`,由项目依赖和锁文件决定版本。查看使用资料运行 `pnpm openxiangda docs`;查看单一主题运行 `pnpm openxiangda docs frontend`。安装到其他 AI 工具时使用 `skill install --destination <Skill根目录>`。
85
85
 
@@ -145,6 +145,7 @@ pnpm openxiangda source push -m "完成本轮应用开发"
145
145
  | --- | --- |
146
146
  | 新应用 | 正常执行 `create`,平台启用后自动建仓、配置凭据和首次推送 |
147
147
  | 已有项目首次交接给另一个 AI | 先读 `context --json` 和 `source status`,沿用项目绑定与版本 |
148
+ | 交接给另一位开发者 | 平台管理员先把对方加为该应用的应用管理员;对方执行 `source clone <仓库URL> <新目录> --base-url <平台>`,克隆后在同目录 `login --base-url <平台>` 继续开发 |
148
149
  | 换电脑或初始化中断 | 在应用目录执行 `pnpm openxiangda source setup`;已有提交及未提交修改会保留 |
149
150
  | 从个人远端迁入 | 明确迁入后运行 `source setup --import`,原远端保留为 `external-source` |
150
151
  | 本轮修改完成 | 检查差异后运行 `source push -m "AppSpec: <本轮变更ID> 变更说明"`;多个任务共享目录时先精确提交本轮文件,再不带 `-m` 推送 |
@@ -173,9 +174,9 @@ MCP 的 `docs_read` 可以读取本说明,当前没有独立的源码操作 MC
173
174
  无需本地工作区,使用本 Skill 随包精确版本或已安装的对应 CLI:
174
175
 
175
176
  ```bash
176
- pnpm dlx openxiangda@2.21.0 auth status --base-url <平台> --json
177
- pnpm dlx openxiangda@2.21.0 source resolve <仓库URL> --base-url <平台> --json
178
- pnpm dlx openxiangda@2.21.0 source clone <仓库URL> <新目录> --base-url <平台> --json
177
+ pnpm dlx openxiangda@2.21.1 auth status --base-url <平台> --json
178
+ pnpm dlx openxiangda@2.21.1 source resolve <仓库URL> --base-url <平台> --json
179
+ pnpm dlx openxiangda@2.21.1 source clone <仓库URL> <新目录> --base-url <平台> --json
179
180
  ```
180
181
 
181
182
  登录缺失或站点不匹配时,先按该平台执行 login。resolve 根据平台已经登记的绑定返回
@@ -200,6 +201,32 @@ pnpm dlx openxiangda@2.21.0 source clone <仓库URL> <新目录> --base-url <平
200
201
  使用 CLI 时无需自行调用凭据 API;支持工具不得记录其响应或另建身份体系。
201
202
  未登记的外部仓库先在原工作区执行 `source setup --import`,再使用平台返回的仓库 URL。
202
203
 
204
+ ## 跨站点与平台换绑 {#cross-site}
205
+
206
+ 源码仓库、应用与数据都归属各自站点:A 站点的平台只认 A 站点登记的仓库绑定,
207
+ 发布校验要求当前工作区 `origin` 与该站点绑定仓库一致。站点之间流动的是代码(git),
208
+ 应用的数据库数据、发布历史与环境配置不会自动迁移。
209
+
210
+ ```bash
211
+ openxiangda link # 查看当前绑定、登录态匹配与 origin 归属
212
+ openxiangda link rebind --base-url https://platform-b.example.com # 显式换绑
213
+ ```
214
+
215
+ 一个工作区同一时间只绑定一个平台;换绑只改写 `.openxiangda/link.json`
216
+ (清空旧站点环境列表),不修改 git remote、不迁移登录凭据,也不在远端产生变更。
217
+ 换绑后必须重新登录,`create` 会在新平台幂等初始化同 code 应用。
218
+
219
+ | 场景 | 执行方式 |
220
+ | --- | --- |
221
+ | 在 A 开发、也要发布到 B | 在 B 站点创建同 code 应用并由其管理员启用源码;`source clone <B仓库URL> <B目录> --base-url <B平台>` 得到第二个检出;同步代码用 `git remote add external-source <A仓库URL>` 后 fetch/merge,再 `source push`;在 B 目录执行 `deploy` |
222
+ | 后续发布永久切换到 B | 在原目录依次执行 `link rebind --base-url <B平台>` → `login --base-url <B平台>` → `create <目录> --base-url <B平台>`(幂等初始化)→ `source status` 核对 origin;如报告 `APPLICATION_SOURCE_ORIGIN_CONFLICT`,用 `source setup --import` 把 origin 切到 B 仓库(原 origin 保留为 `external-source`) |
223
+ | 换绑后想回退 | `link rebind --base-url <原平台>` 即恢复;link.json 随仓库提交时也可用 git 还原该文件 |
224
+ | 换绑对象不是同一应用 | 拒绝执行(`OPENXIANGDA_LINK_APP_CODE_CONFLICT`);请在对应应用的目录操作 |
225
+
226
+ 换绑命令对地址做与 `login` 相同的归一化校验;地址拼错时失败会在下一步登录或
227
+ 幂等初始化处暴露,随时可以再次 rebind 修正。详细决策记录见仓库
228
+ `docs/architecture-decisions/workspace-platform-rebind.md`。
229
+
203
230
  ## 检查与交付 {#delivery}
204
231
 
205
232
  只检查时运行 `pnpm openxiangda check`。需要部署测试环境时直接运行 `pnpm openxiangda deploy`,它已包含检查、测试和构建;无需再连续重复运行全部脚本。
@@ -220,8 +247,13 @@ pnpm exec openxiangda --mcp-stdio --cwd <应用绝对路径>
220
247
 
221
248
  ## 工作区登录态
222
249
 
223
- 平台授权保存到所选工作区的 `.openxiangda/session.json`,CLI、MCP、刷新与退出共用该文件。不再读取或迁移旧全局会话;升级后需在每个项目重新登录。已有项目可在根目录或子目录运行 `openxiangda login --base-url <platform>`;`login --cwd <directory>` 和 `auth --cwd <directory>` 明确选定工作区。嵌套应用不会继承父应用会话。
250
+ 平台授权保存到所选工作区的 `.openxiangda/session.json`,CLI、MCP、刷新与退出共用该文件。不再读取或迁移旧全局会话;升级后需在每个项目重新登录。已有项目可在根目录或子目录运行 `openxiangda login --base-url <platform>`;`login --cwd <directory>` 和 `auth status --cwd <directory>` 明确选定工作区。首次 `create` 目标目录尚无会话时,按工作区发现规则向上继承父目录会话,并在 `.git` 仓库边界停止,不跨仓借用账号。
224
251
 
225
252
  创建应用前先执行 `openxiangda login --cwd my-app --base-url <platform>`,再执行 `openxiangda create my-app --base-url <platform>`。仅含受管登录文件的目录允许初始化,凭据会保留并自动加入 Git 忽略规则。
226
253
 
227
254
  本地文件优先;仅在文件缺失时使用成对的 `OPENXIANGDA_BASE_URL` 与 `OPENXIANGDA_TOKEN` CI 环境凭据。损坏、过期或平台不符的文件不会触发其他身份回退。请勿提交或打包登录文件。钉钉支持由 DWS 管理自己的授权,不与平台会话混用。
255
+
256
+ 查看当前绑定与登录态匹配情况运行 `openxiangda link`;登录地址与绑定平台不一致时
257
+ `login` 会拒绝执行(`OPENXIANGDA_PLATFORM_SESSION_MISMATCH`),防止凭据跨平台发送。
258
+ 需要切换站点时先运行 `openxiangda link rebind --base-url <新平台>`,再登录,见
259
+ [跨站点与平台换绑](#cross-site)。
@@ -139,7 +139,8 @@ MCP 使用项目锁定的根包;在客户端配置下列 stdio 启动参数,
139
139
  "minimum": 0,
140
140
  "maximum": 9007199254740991
141
141
  }
142
- }
142
+ },
143
+ "additionalProperties": false
143
144
  }
144
145
  ```
145
146
 
@@ -169,7 +170,8 @@ MCP 使用项目锁定的根包;在客户端配置下列 stdio 启动参数,
169
170
  },
170
171
  "required": [
171
172
  "deploymentId"
172
- ]
173
+ ],
174
+ "additionalProperties": false
173
175
  }
174
176
  ```
175
177
 
@@ -229,7 +231,8 @@ MCP 使用项目锁定的根包;在客户端配置下列 stdio 启动参数,
229
231
  "production"
230
232
  ]
231
233
  }
232
- }
234
+ },
235
+ "additionalProperties": false
233
236
  }
234
237
  ```
235
238
 
@@ -265,7 +268,8 @@ MCP 使用项目锁定的根包;在客户端配置下列 stdio 启动参数,
265
268
  },
266
269
  "required": [
267
270
  "workflowCode"
268
- ]
271
+ ],
272
+ "additionalProperties": false
269
273
  }
270
274
  ```
271
275
 
@@ -298,7 +302,8 @@ MCP 使用项目锁定的根包;在客户端配置下列 stdio 启动参数,
298
302
  "description": "仅本地完整检查;结果 validationScope=local,不核对现场条件",
299
303
  "type": "boolean"
300
304
  }
301
- }
305
+ },
306
+ "additionalProperties": false
302
307
  }
303
308
  ```
304
309
 
@@ -523,7 +528,8 @@ MCP 使用项目锁定的根包;在客户端配置下列 stdio 启动参数,
523
528
  "description": "持续跟踪原运行至结束,最多 15 分钟",
524
529
  "type": "boolean"
525
530
  }
526
- }
531
+ },
532
+ "additionalProperties": false
527
533
  }
528
534
  ```
529
535
 
@@ -92,8 +92,11 @@ export default defineOpenXiangdaApp({
92
92
 
93
93
  附件、图片、多选等多值字段使用数组,存储不允许 null;这不意味着用户必须填写。上例照片可选,
94
94
  可以省略或提交空数组,不放入 `requiredFields`。如果业务确实要求上传,资源字段声明 `required: true`,
95
- 公开策略也必须将它列入 `requiredFields`;提交时省略、null 或空数组都会被拒绝。策略可以提出更严格的
96
- 必填要求,但不能漏掉资源已有的必填业务字段。缺失覆盖的诊断会指出字段和 `fields`/`requiredFields` 路径。
95
+ 公开策略也必须将它列入 `requiredFields`;提交时省略、null 或空数组都会被拒绝。`requiredFields` 必须覆盖
96
+ 资源已有的必填业务字段;比模型必填**更严**(列入模型未声明 `required: true` 的字段)时编译器给出警告
97
+ `APP_CONFIG_ANONYMOUS_POLICY_REQUIRED_FIELDS_STRICTER_THAN_MODEL`:标准表单控件不为这些字段生成必填
98
+ 校验,空值提交会被服务端 `REQUIRED_FIELD_MISSING` 拒绝。需要前端必填校验时在模型字段上声明
99
+ `required: true`,使模型必填与策略对齐。缺失覆盖的诊断会指出字段和 `fields`/`requiredFields` 路径。
97
100
 
98
101
  操作按页面实际需要最小声明:
99
102
 
@@ -158,15 +161,22 @@ publicFilters: [{ field: 'enabled', operator: 'eq', value: true }]
158
161
 
159
162
  匿名创建需要平台生成的不可预测字段时,使用 `serverGeneratedFields`。这些字段不属于
160
163
  `fields`,调用方不能在草稿中写入;提交事务会由平台生成随机值并在提交回执的 `generated`
161
- 对象中返回。`random-token` 只适用于不承载身份信息的核验令牌等用途:
164
+ 对象中返回。目前仅支持 `random-token` 一种 kind,只适用于不承载身份信息的核验令牌等用途:
162
165
 
163
166
  ```ts
164
167
  serverGeneratedFields: [{ field: 'qrToken', kind: 'random-token' }]
165
168
  ```
166
169
 
167
- 需要跨资源复核预约窗口等业务不变量时,可声明 `schedule`,绑定两个只读公开策略和资源字段。
168
- 平台会在最终创建事务中重新读取启用校区与规则,校验星期、日期范围、提前小时数和离散时段;页面端
169
- 校验只能改善体验,不能替代这次服务端复核。
170
+ 需要跨资源复核预约窗口等业务不变量时,可声明 `schedule`。它把本策略资源上的
171
+ `campusField`(校区)、`dateField`(日期)和 `timeField`(时间)绑定到另外两个**只读公开
172
+ 策略**(`campusPolicyCode`/`rulePolicyCode`,必须是同 `publicAccess` 中已声明的策略 code),
173
+ 并在规则策略暴露的资源上指定读取哪些字段:`ruleCampusField`(规则所属校区)、
174
+ `ruleWeekdaysField`(开放星期)、`ruleOpenAtField`/`ruleCloseAtField`(每日开放窗口)、
175
+ `ruleSlotMinutesField`(离散时段分钟数)、`ruleAdvanceHoursField`/`ruleAdvanceDaysField`
176
+ (可提前预约的小时/天数上限),以及两个策略各自的启用开关
177
+ `campusEnabledField`/`ruleEnabledField`。十四个键全部必填,字段 code 必须真实存在于
178
+ 对应资源。平台会在最终创建事务中重新读取启用的校区与规则,校验星期、日期范围、提前量
179
+ 和离散时段;页面端校验只能改善体验,不能替代这次服务端复核。
170
180
 
171
181
  子表中的文件引用会自动带上受控的 `resourceCode` 与父字段绑定;应用如需为附件生成下载地址,使用
172
182
  `fileContentUrl(fileId, disposition, variant, resourceCode, parentFieldCode)`,不要自行拼接文件路径。
@@ -32,6 +32,8 @@ Web 默认保留开发服务回环访问检查。按需 Nest 使用 `tsx --test`
32
32
 
33
33
  应用入口或导航变更必须从平台应用列表实际点击进入,再验证应用根路径、登录返回和刷新后的深链接。使用后台的应用检查 `/admin` 进入首个有权菜单、自定义页只出现一个 Shell、菜单切换与未保存保护、无权限拒绝;纯用户应用验证其声明首页,不强制增加后台。直达开发者给出的 `/home` 或报表链接通过,不能替代平台实际入口验收。
34
34
 
35
+ 平台浏览器入口有两个:`/view/<应用标识>/...` 是正式环境入口,`/dev/<应用标识>/...` 是预发(测试环境)入口,路径中的应用标识即 `app.code`。测试部署通过后从 `/dev/<应用标识>` 做浏览器验收;生产晋级后用 `/view/<应用标识>` 复核。`/dev` 入口要求已登录平台账号(应用内数据和功能仍按应用角色权限控制),未部署的环境返回明确的 503 页面,响应头 `X-OpenXiangda-Environment` 标识当前环境。本地 `pnpm openxiangda dev` 的回环地址只用于开发迭代,不能替代这两个平台入口的验收。
36
+
35
37
  只改文案时验证受影响页面。复杂事务、并发和值转换使用聚焦测试。浏览器验收实际操作并检查错误,不能用模拟响应或空页面加载代替真实角色验收。
36
38
 
37
39
  匿名访问另验证续填、上传、校验、幂等提交、own.list/own.read;另一浏览器应无法获取前一浏览器的记录。工作流按已启用功能检查发起、处理、历史详情和消息跳转,不为未启用通道增加测试负担。
@@ -16,7 +16,7 @@
16
16
 
17
17
  共享校验使用 `configuration-compatibility/v2` 描述,包含校验实现摘要。切换到这一契约时,平台和项目工具链需按同一发布说明配套升级。随后每次发布在构建前核对实现摘要;不要反复修改业务代码来处理工具版本不配套。
18
18
 
19
- 引导式开发使用 workspace-context/v3 与 AppSpec context/v3。升级后按实际业务补齐总纲与关联变更;空模板不能正式测试发布,生产晋级需要原测试版本的验收计划和实际报告。旧候选缺少计划时建立新的测试候选并验收,不自动补写过去的确认或通过记录。普通 dev/check 仍可用于整理和验证尚未完成的项目。
19
+ 引导式开发使用 workspace-context/v3 与 AppSpec context/v4。升级后按实际业务补齐总纲与关联变更;空模板不能正式测试发布,生产晋级需要原测试版本的验收计划和实际报告。旧候选缺少计划时建立新的测试候选并验收,不自动补写过去的确认或通过记录。普通 dev/check 仍可用于整理和验证尚未完成的项目。
20
20
 
21
21
  ## 更新项目 {#upgrade}
22
22
 
@@ -58,6 +58,46 @@ events: {
58
58
  外部 Webhook 和自定义代码仍使用已有签名、回执、重试及接收端幂等协议,按至少
59
59
  一次投递处理。轻量操作历史仍通过已有审计 API 查询,不依赖是否订阅了事件。
60
60
 
61
+ ## 定时与日期触发
62
+
63
+ 除订阅平台数据事件外,`events` 还能声明两类自有时程触发器;两者都只负责在到期时
64
+ 发出应用事件,业务效果仍由订阅(含 `execution: native-data`)或应用后端处理。
65
+
66
+ `events.timers` 按 cron 周期发事件。`code` 为 kebab-case 且唯一;`eventType` 必须是
67
+ `events.schemas` 已声明的应用事件;`cronExpression` 为六段 cron(秒 分 时 日 月 周),
68
+ `timezone` 使用 IANA 名称(如 `Asia/Shanghai`),最短触发间隔为 60 秒;`misfirePolicy`
69
+ 目前仅支持 `coalesce_one`(错过合并为一次);`payload` 是普通对象,必须完整满足所引用
70
+ 事件的 JSON Schema 且不超过 64 KiB。定时声明属于环境中立的 AppVersion,不写
71
+ `environmentKey`;启用/暂停和下次触发时间由平台按环境管理。
72
+
73
+ ```ts
74
+ events: {
75
+ schemas: [{
76
+ eventType: 'app.report.digest.v1',
77
+ dataSchemaVersion: '1',
78
+ jsonSchema: {
79
+ type: 'object', additionalProperties: false,
80
+ required: ['kind'], properties: { kind: { type: 'string' } },
81
+ },
82
+ }],
83
+ timers: [{
84
+ code: 'daily-digest',
85
+ eventType: 'app.report.digest.v1',
86
+ cronExpression: '0 0 9 * * *',
87
+ timezone: 'Asia/Shanghai',
88
+ payload: { kind: 'daily' },
89
+ }],
90
+ },
91
+ ```
92
+
93
+ `events.dateTriggers` 相对记录的 date/datetime 字段发事件:字段值加 `offset`(ISO-8601
94
+ 时长,十年内,如 `-PT1H` 表示提前一小时)到达时触发。适合到期提醒、超期升级等场景;
95
+ 同一条记录的字段更新后按新值重算。`code` 唯一,`eventType` 同样引用已声明应用事件。
96
+
97
+ 两类触发器各最多 100 条。事件 Schema 用 `events.schemas` 声明
98
+ (`{ eventType, dataSchemaVersion, jsonSchema, sensitiveFields? }`),`eventType` 遵循
99
+ `xxx.yyy.v1` 版本后缀模式;触发器只发事件,不直接写数据或调用流程。
100
+
61
101
  ## 标准详情与当前用户入口
62
102
 
63
103
  普通记录、流程记录、任务和实例复用同一详情框架。流程详情提供申请内容、审批历史和变更记录三个标签页;管理员在当前抽屉或页面中切换到普通表单编辑,直接保存并自动留下变更记录,审批结果保持不变。PC 子表在表格内编辑,父表提交时统一校验。
@@ -113,6 +153,8 @@ Workflow 发起只接受 `{ resourceCode, id }` 形式的 `dataRef`,并要求
113
153
 
114
154
  首期只支持 `approval`、`condition`、`end`,以及 `single`、`any`、`all`、`sequence` 审批模式。标准操作为提交、同意、拒绝、退回、重新提交、转交、委托、前/后加签、撤回、管理员改派、管理员终止和不改变流程状态的催办。
115
155
 
156
+ 除命令集之外,平台为实例管理员提供两个维护动作:`admin_jump`(把处于运行或退回状态的实例跳转到指定节点)与 `admin_delete`(删除实例,可选同时删除表单数据、是否触发自动化)。两者走平台管理端点的预览/执行两步流程并要求同源浏览器请求,不属于应用声明的工作流命令,也不占用 `commandToken` 命令合同。
157
+
116
158
  复杂业务状态机继续放在应用领域服务。不要把任意 JavaScript、Service Task、BPMN、通用长事务或业务记录复制进 Workflow。
117
159
 
118
160
  所有页面和消息动作必须来自后端 Workflow Surface 的 `operations[]`。前端、模板和渠道 Adapter 不自行推断操作权限。