openxiangda 2.33.0 → 2.34.0

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/dist/browser/ManagedCommand.d.ts +19 -0
  2. package/dist/browser/ManagedCommand.d.ts.map +1 -1
  3. package/dist/browser/ManagedCommand.js +8 -0
  4. package/dist/browser/ManagedCommand.js.map +1 -1
  5. package/dist/browser/components/resource/useResourceFormDrafts.d.ts.map +1 -1
  6. package/dist/browser/durable-command.d.ts +51 -0
  7. package/dist/browser/durable-command.d.ts.map +1 -0
  8. package/dist/browser/durable-command.js +266 -0
  9. package/dist/browser/durable-command.js.map +1 -0
  10. package/dist/browser/managed-command.d.ts +7 -1
  11. package/dist/browser/managed-command.d.ts.map +1 -1
  12. package/dist/browser/managed-command.js.map +1 -1
  13. package/dist/browser/platform-client.d.ts +2 -0
  14. package/dist/browser/platform-client.d.ts.map +1 -1
  15. package/dist/browser/platform-client.js +7 -0
  16. package/dist/browser/platform-client.js.map +1 -1
  17. package/dist/nest.d.ts +3 -2
  18. package/dist/nest.d.ts.map +1 -1
  19. package/dist/nest.js +1 -1
  20. package/dist/nest.js.map +1 -1
  21. package/documentation/getting-started.md +7 -7
  22. package/documentation/managed-concurrency-frontend.md +23 -4
  23. package/documentation/managed-concurrency.md +53 -2
  24. package/documentation/manifest.json +4 -4
  25. package/package.json +25 -20
  26. package/releases/2.34.0.json +38 -0
  27. package/skills/manifest.json +1 -1
  28. package/skills/openxiangda-v2/SKILL.md +4 -4
  29. package/skills/openxiangda-v2/references/getting-started.md +7 -7
  30. package/skills/openxiangda-v2/references/managed-concurrency-frontend.md +23 -4
  31. package/skills/openxiangda-v2/references/managed-concurrency.md +53 -2
@@ -15,7 +15,7 @@
15
15
  | `commands` | 当前用户的能力、按资源分队列、最长等待、处理截止、Native 守卫和原子写入 |
16
16
  | `quotas` | 容量来源、业务记录上的分配 ID、可选的预占到期时间 |
17
17
 
18
- 参数限于 UUID、有限字符串枚举和有限整数范围;绑定只接受 `input`、可信 `actor`、字面量和平台生成的 `allocation`。每个命令最多 8 个操作、8 个记录守卫。首版不执行应用任意 JS/SQL,也不在锁内调用外部系统。通知和外部副作用接已有事务后事件。
18
+ 默认 permit 模式的参数限于 UUID、有限字符串枚举和有限整数范围;绑定只接受 `input`、可信 `actor`、字面量和平台生成的 `allocation`。permit 命令最多 8 个操作、8 个记录守卫。permit 模式不执行应用任意 JS/SQL,也不在锁内调用外部系统。通知和外部副作用接已有事务后事件。
19
19
 
20
20
  配额来源模型的容量字段必须是整数;分配记录模型的引用字段必须是必填 UUID,模型设置 `mutationOwner: 'queued-command'`。角色仍须具备来源读取、分配模型创建和命令能力。普通 CRUD、导入、应用凭据均不能绕过命令所有权。用户身份由 `actor` 绑定,不能使用用户填写的人员编号决定名额归属。
21
21
 
@@ -75,7 +75,7 @@ export function Claim({ offerId }: { offerId: string }) {
75
75
 
76
76
  立即分配用 `quota.action: 'allocate', mode: 'committed'`;需要预占时声明 `reservationSeconds` 并用 `mode: 'reserved'`。另声明 `confirm`、`release` 命令,绑定原 `allocation`,该命令的 `operations` 为空。只有原主体可确认/释放;到期与确认竞争使用数据库时间及固定锁顺序。
77
77
 
78
- 原命令回执不可变。预占之后到期或释放,应调用 `client.allocation(allocationId)` 获取当前分配状态,不能用旧成功回执证明仍持有名额。首版同一池/主体保留永久去重事实,释放后不会自动重新报名;应用若需要新一轮,应使用新的权威资源/配额池,不能删除历史分配。
78
+ 原命令回执不可变。预占之后到期或释放,应调用 `client.allocation(allocationId)` 获取当前分配状态,不能用旧成功回执证明仍持有名额。内置 quota 模式同一池/主体保留永久去重事实,释放后不会自动重新报名;应用若需要新一轮,应使用新的权威资源/配额池,不能删除历史分配。
79
79
 
80
80
  容量减少不能低于已预占加已确认数量。存在配额历史时,禁止删除来源、改写分配引用或发布移除配额映射的版本。应用激活先排空所有非终态命令;不会让旧任务在新 Head 上执行。连接开发消费已激活测试版本的并发声明,未激活的本地模型覆盖层不能作为持久命令契约。
81
81
 
@@ -98,3 +98,54 @@ RabbitMQ 的消息只是唤醒提示;队列丢失或发布失败由数据库
98
98
  暂停/恢复按同一个环境锁串行传播;Redis 不可用时控制接口明确报错。控制值是绝对值,可以使用相同 `paused` 重试并核对状态。数据库提交确认丢失时不猜测控制结果;受理始终复查数据库的暂停状态。
99
99
 
100
100
  验证应包括内容 SQL 与授权 SQL 占比、每秒完成数、P95/P99、最老等待、连接数、锁等待和恢复时间。仓库故障测试证明协议边界,不代表任何客户环境的生产吞吐。部署者仍需使用目标硬件、真实权限和活动规模做容量验收。
101
+
102
+
103
+ ## 持久排队的应用业务计划(durable)
104
+
105
+ 需要「受理后关闭浏览器仍继续」或取消后再次申请的业务,声明 `mode: 'durable'` 和 `execution.kind: 'backend-plan'`,额外要求 `data.managed-concurrency.durable@1.0.0`。默认省略 mode 仍走原 permit 协议,不能混用 wait/accept 和 enqueue。持久受理先提交数据库;MQ 只唤醒,Redis 只保存可重建的预算和位置投影。受理成功不是报名成功。
106
+
107
+ ```ts
108
+ {
109
+ code: 'registration-enroll', mode: 'durable', capability: 'app:arts:registration:submit',
110
+ parameters: { activity: {type:'uuid'}, channel: {type:'uuid'},
111
+ agreed: {type:'boolean'}, phone: {type:'string',maxLength:32} },
112
+ resourceKey: {from:'input',key:'activity'}, deadlineSeconds:3600,
113
+ intake: {perSecond:100,burst:100,maxInFlight:50},
114
+ admission: {perSecond:2,burst:2,maxInFlight:2,maxQueue:5000,maxWaitSeconds:3600},
115
+ execution: {kind:'backend-plan',handlerCode:'registration-enroll',timeoutMs:10000,
116
+ resources:[{resourceCode:'registrations',readFields:['id','activityId','person'],
117
+ writeOperations:['create'],writeFields:['activityId','person']}],
118
+ directory:{mode:'current-initiator',fields:['displayName','employeeNumber','primaryDepartment','departments']}}
119
+ }
120
+ ```
121
+
122
+ 这是声明片段,资源/字段、角色能力和应用总 admission 必须与实际模型一致。示例预算不是吞吐量承诺。durable 参数最多 8 个、编码最多 8 KiB;自由字符串 maxLength 最大 256,boolean 只允许 durable。intake 的 perSecond/burst/maxInFlight 各为 1..1000,独立限制新受理;admission 三项限制执行,不能超过应用共享预算。maxQueue 最大 10000,maxWaitSeconds 最大 3600,deadlineSeconds 为 10..3600,处理期限从最初数据库 acceptedAt 起算,重试不延长。
123
+
124
+ resources 最多 32 个,每项 readFields/writeFields 各最多 64 字段,writeOperations 仅 create/update/increment;不声明即不可访问。directory 仅允许当前发起人及四种现有字段,不接受 userId。不得同时声明静态 operations/guards/quota。普通业务模型不必改为 queued-command 所有权;管理员取消等正常业务动作仍使用原权限。已有内置 quota 的独占分配模型不允许通过 backend-plan 改写。
125
+
126
+ ### 后端只读计划
127
+
128
+ 从应用 generated 导入 `managedCommandHandlerManifest`,传给 `OpenXiangdaModule.forApplication({managedCommandHandlerManifest})`。使用 `OpenXiangdaManagedCommandHandler({commandCode,handlerCode})` 注册实现 `plan(input, context)` 的 Nest provider。声明会启用后端;显式禁用 backend 会编译失败。SDK 生成固定 `POST /__platform/managed-commands/:handlerCode/plan`,不可自定义 URL。
129
+
130
+ context 提供 commandId/commandCode/generation、actor.userId、acceptedAt/deadlineAt、data.get/query 和 directory.currentInitiator()。get 无记录抛 404;query 返回标准 DataPage。handler 强制 request scope,构造函数和依赖只在网关签名与在线执行证明核实之后解析。执行证明保存在 SDK 私有 ALS,普通 SDK 事务、写入、通知和 OAuth 调用在该上下文内拒绝。应用只返回:
131
+
132
+ ```ts
133
+ return {
134
+ schemaVersion:'openxiangda.managed-command-plan/v1',
135
+ guards:[{kind:'record-match',resourceCode:'channels',id:channel.id,
136
+ lockKey:`channel:${channel.id}`,errorCode:'OPENXIANGDA_REGISTRATION_CLOSED',
137
+ assertions:[{kind:'command-accepted-at',field:'closesAt',operator:'gte'}]}],
138
+ operations:[{operation:'create',resourceCode:'registrations',data:{activityId,person:context.actor.userId}}],
139
+ result:{registrationId:{operationIndex:0,field:'id'}}
140
+ };
141
+ ```
142
+
143
+ 对应读取和写入字段必须列入 declaration。guard 比较方向为「字段 operator acceptedAt」,不使用客户端时间,也不改变普通 database-now 含义。最多 99 操作、20 守卫、64 KiB 完整响应;仅 create/update/increment。result 中的 `{operationIndex,field:'id'}` 必须指向本计划的 create,由平台提交后替换真实 ID。禁止事务 key、任意副作用或调用回调。应用仍需实时容量、活动开关与活动周期去重守卫,不能把受理时间当作名额保证。
144
+
145
+ 平台在短事务中复核 generation/租约/head/期限/当前命令资格,执行 Native 计划并原子落终态。冲突重新运行只读 handler;网络重试不会重复业务效果。通知以业务事务中的待发送事实或已有事务事件触发,不能在 plan 中直接发送。
146
+
147
+ ### 本人结果与恢复
148
+
149
+ `client.enqueue(code,input,requestKey)` 返回持久回执;`client.mine(code,{resourceKey,cursor?,limit})` 只查询当前主体,最多 20 条;`client.result` 延续原 ID/原 key。回执包含 resourceKey、updatedAt,可选 queue `{position:number|null,observedAt}`,位置是近似投影,缺失不代表请求丢失。结果终态只说明那次业务事务;管理员取消后仍应读当前业务记录,用户明确操作才发起新周期。
150
+
151
+ 浏览器使用 `useDurableCommand`,详见[前端接入](./managed-concurrency-frontend.md#durable)。部署先发布新 SDK/编译器并确认平台能力;回退前暂停新受理、排空或核对所有原命令,不让旧镜像消费未知执行模式。不能用 SDK 单测替代真实多用户并发、关闭浏览器、跨设备结果和原子事务验收。
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "schemaVersion": "openxiangda.documentation/v1",
3
- "version": "2.33.0",
3
+ "version": "2.34.0",
4
4
  "topics": [
5
5
  {
6
6
  "id": "getting-started",
7
7
  "title": "安装与开始开发",
8
8
  "file": "getting-started.md",
9
- "sha256": "52308efef04d2f8d84393b208ae096ae74ef1b518d2e964c1da3800b3bccf603"
9
+ "sha256": "d48a6b16a667910c296d2d6225065488919f847c916e5a25affa6e3c777f30ab"
10
10
  },
11
11
  {
12
12
  "id": "product-design",
@@ -108,13 +108,13 @@
108
108
  "id": "managed-concurrency",
109
109
  "title": "缓存、排队与整数配额",
110
110
  "file": "managed-concurrency.md",
111
- "sha256": "21d7e1e6ce3f230f88013cd6522ce6195bd4079d55af6d0b74a525c209fd5601"
111
+ "sha256": "248e26e1774e0d2b231fe588884f473a63866e0d2b1885e05225cd242f476ee7"
112
112
  },
113
113
  {
114
114
  "id": "managed-concurrency-frontend",
115
115
  "title": "并发能力的前端接入",
116
116
  "file": "managed-concurrency-frontend.md",
117
- "sha256": "294fa5655a9f0673ec871d204ad53a15bf8dfc8a9a91a2975d8eafe58f402469"
117
+ "sha256": "0529a873ee763cae078b67f7baef5d690695e290384ce1fe398a37344b8de7ff"
118
118
  },
119
119
  {
120
120
  "id": "administration",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openxiangda",
3
- "version": "2.33.0",
3
+ "version": "2.34.0",
4
4
  "description": "OpenXiangda 2.0 的统一命令、应用 SDK、MCP 与中文 AI 技能资料。",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -64,13 +64,13 @@
64
64
  "antd-mobile": "5.42.3",
65
65
  "dayjs": "1.11.18",
66
66
  "docx-preview": "0.3.7",
67
- "openxiangda-cli": "2.6.13",
68
- "openxiangda-contracts": "2.33.0",
69
- "openxiangda-devkit-core": "2.34.5",
67
+ "openxiangda-cli": "2.6.14",
68
+ "openxiangda-contracts": "2.34.0",
69
+ "openxiangda-devkit-core": "2.35.0",
70
70
  "openxiangda-legacy": "npm:openxiangda@1.0.269",
71
- "openxiangda-mcp": "2.0.61",
72
- "openxiangda-nest": "2.10.0",
73
- "openxiangda-skill-kit": "2.3.36",
71
+ "openxiangda-mcp": "2.0.62",
72
+ "openxiangda-nest": "2.11.0",
73
+ "openxiangda-skill-kit": "2.3.37",
74
74
  "xlsx": "https://github.com/1377385356/openxiangda/releases/download/vendor-mirror/xlsx-0.20.3.tgz"
75
75
  },
76
76
  "peerDependencies": {
@@ -136,36 +136,41 @@
136
136
  },
137
137
  "openxiangdaRelease": {
138
138
  "schemaVersion": "openxiangda.release-notes/v1",
139
- "version": "2.33.0",
139
+ "version": "2.34.0",
140
140
  "status": "reviewed",
141
- "title": "OpenXiangda 2.33.0:事务构建与部署前置诊断",
142
- "summary": "在提交前定位事务组合错误,在构建前发现平台依赖故障。",
141
+ "title": "OpenXiangda 2.34.0:持久排队业务计划与结果恢复",
142
+ "summary": "新增由平台持久受理并自动处理的业务命令,支持关闭页面后继续执行与本人跨设备恢复。",
143
143
  "newFeatures": [
144
- "openxiangda/nest 提供 createDataTransaction、diagnoseDataTransaction 和结构化构建错误。"
144
+ "新增 durable 受管命令、独立能力门禁、有界参数及生成的 backend-plan handler 清单。",
145
+ "openxiangda/nest 提供受执行证明约束的只读计划 handler,平台负责最终原子提交。",
146
+ "openxiangda/react 与 mobile 提供 useDurableCommand,支持显式提交、原键恢复、本人结果和退避轮询。"
145
147
  ],
146
148
  "fixes": [
147
- "支持平台实时前置检查,发现内核、源码托管及镜像仓故障时停止后续构建。"
149
+ "严格区分内置 permit 配额所有权与应用 durable 计划,普通业务模型无需迁移到命令独占所有权。",
150
+ "执行上下文拒绝普通 SDK 写入、事务、通知和 OAuth,读取响应验证标准记录结构。"
148
151
  ],
149
152
  "affectedUsers": [
150
- "编写后端事务或向私有平台部署应用的 V2 开发者。"
153
+ "构建活动报名、预约、抢票及其他需要持久排队事务的 V2 应用开发者。"
151
154
  ],
152
155
  "upgradeSteps": [
153
- "升级 openxiangda 到 2.33.0 并重新安装依赖。",
154
- "平台部署对应后端后自动启用前置诊断;事务构建器按文档显式接入。"
156
+ "升级 openxiangda 到 2.34.0,重新安装依赖并生成应用契约。",
157
+ "先部署支持 data.managed-concurrency.durable@1.0.0 的平台,再声明 durable 命令、handler manifest 和前端恢复流程。",
158
+ "在隔离测试数据上验证关闭浏览器、原请求恢复、取消后重报和业务原子结果,再分级测量实际容量。"
155
159
  ],
156
160
  "knownLimitations": [
157
- "只读预检不保证后续写入或容量;原有部署和权限检查继续执行。",
158
- "事务构建不自动合并重复写入,不替代服务端权限和并发判断。"
161
+ "应用只返回受声明约束的事务计划,不能在 handler 内发送通知或调用普通写入接口。",
162
+ "受理成功不等于业务成功;队列位置为近似投影,不能推导精确等待时间或服务器容量。",
163
+ "旧 permit 模式保留,未升级平台会拒绝 durable 包;回滚前须停止新受理并核对已有请求。"
159
164
  ],
160
165
  "issues": [],
161
166
  "compatibility": {
162
167
  "node": ">=24",
163
168
  "workspaceGenerations": "v2",
164
- "platform": "前置检查按平台 capability 启用,旧站保持原部署流程。",
169
+ "platform": "durable 必须具备 data.managed-concurrency.durable@1.0.0,旧 permit 契约继续支持。",
165
170
  "v1": "V1 引擎与业务不变。"
166
171
  },
167
- "sha256": "6738fcde83daed062691a30f4c4161d5f59b8024303ba2c4c7fd377c6886de70",
168
- "url": "https://github.com/1377385356/openxiangda/releases/tag/v2.33.0"
172
+ "sha256": "929abb0ea8cdbb32e38c39131540cd64a248c4e1a68dbde9ff187f35438f8da0",
173
+ "url": "https://github.com/1377385356/openxiangda/releases/tag/v2.34.0"
169
174
  },
170
175
  "scripts": {
171
176
  "build": "node ../../scripts/prune-package-dist.mjs && tsc -p tsconfig.json && node scripts/copy-assets.mjs",
@@ -0,0 +1,38 @@
1
+ {
2
+ "schemaVersion": "openxiangda.release-notes/v1",
3
+ "version": "2.34.0",
4
+ "status": "reviewed",
5
+ "title": "OpenXiangda 2.34.0:持久排队业务计划与结果恢复",
6
+ "summary": "新增由平台持久受理并自动处理的业务命令,支持关闭页面后继续执行与本人跨设备恢复。",
7
+ "newFeatures": [
8
+ "新增 durable 受管命令、独立能力门禁、有界参数及生成的 backend-plan handler 清单。",
9
+ "openxiangda/nest 提供受执行证明约束的只读计划 handler,平台负责最终原子提交。",
10
+ "openxiangda/react 与 mobile 提供 useDurableCommand,支持显式提交、原键恢复、本人结果和退避轮询。"
11
+ ],
12
+ "fixes": [
13
+ "严格区分内置 permit 配额所有权与应用 durable 计划,普通业务模型无需迁移到命令独占所有权。",
14
+ "执行上下文拒绝普通 SDK 写入、事务、通知和 OAuth,读取响应验证标准记录结构。"
15
+ ],
16
+ "affectedUsers": [
17
+ "构建活动报名、预约、抢票及其他需要持久排队事务的 V2 应用开发者。"
18
+ ],
19
+ "upgradeSteps": [
20
+ "升级 openxiangda 到 2.34.0,重新安装依赖并生成应用契约。",
21
+ "先部署支持 data.managed-concurrency.durable@1.0.0 的平台,再声明 durable 命令、handler manifest 和前端恢复流程。",
22
+ "在隔离测试数据上验证关闭浏览器、原请求恢复、取消后重报和业务原子结果,再分级测量实际容量。"
23
+ ],
24
+ "knownLimitations": [
25
+ "应用只返回受声明约束的事务计划,不能在 handler 内发送通知或调用普通写入接口。",
26
+ "受理成功不等于业务成功;队列位置为近似投影,不能推导精确等待时间或服务器容量。",
27
+ "旧 permit 模式保留,未升级平台会拒绝 durable 包;回滚前须停止新受理并核对已有请求。"
28
+ ],
29
+ "issues": [],
30
+ "compatibility": {
31
+ "node": ">=24",
32
+ "workspaceGenerations": "v2",
33
+ "platform": "durable 必须具备 data.managed-concurrency.durable@1.0.0,旧 permit 契约继续支持。",
34
+ "v1": "V1 引擎与业务不变。"
35
+ },
36
+ "sha256": "929abb0ea8cdbb32e38c39131540cd64a248c4e1a68dbde9ff187f35438f8da0",
37
+ "url": "https://github.com/1377385356/openxiangda/releases/tag/v2.34.0"
38
+ }
@@ -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": "5ddba6fd144539bbffac61c20b83c004ad24cfefbf29cf112be3d75e1fba2ca4"
7
+ "sha256": "9df75c0d1a91f5729dbcca42c5b04f99cc3efc5f6d453ea02dbf571bb6c1f4a7"
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.33.0 auth status --cwd <应用目录> --base-url <平台地址> --json
44
- pnpm dlx openxiangda@2.33.0 login --cwd <应用目录> --base-url <平台地址>
45
- pnpm dlx openxiangda@2.33.0 create <应用目录> --base-url <同一平台地址>
46
- pnpm dlx openxiangda@2.33.0 skill install --force
43
+ pnpm dlx openxiangda@2.34.0 auth status --cwd <应用目录> --base-url <平台地址> --json
44
+ pnpm dlx openxiangda@2.34.0 login --cwd <应用目录> --base-url <平台地址>
45
+ pnpm dlx openxiangda@2.34.0 create <应用目录> --base-url <同一平台地址>
46
+ pnpm dlx openxiangda@2.34.0 skill install --force
47
47
  ```
48
48
 
49
49
  创建前把产品要求的目标平台明确带入命令,不从旧登录态推断站点。已有工作区从原绑定恢复,平台不一致时先解决登录与目标,不改 link 文件跨站创建。
@@ -68,10 +68,10 @@ MCP 服务随项目根包一起安装,AI 客户端的 stdio 连接仍需配置
68
68
  以下命令的版本占位符由随包资料替换为该根包的精确版本。网站源码阅读者应先确认要使用的发行版本。
69
69
 
70
70
  ```bash
71
- pnpm dlx openxiangda@2.33.0 skill install --force
72
- pnpm dlx openxiangda@2.33.0 auth status --base-url <平台地址> --json
73
- pnpm dlx openxiangda@2.33.0 login --cwd my-app --base-url https://platform.example.com
74
- pnpm dlx openxiangda@2.33.0 create my-app --base-url https://platform.example.com
71
+ pnpm dlx openxiangda@2.34.0 skill install --force
72
+ pnpm dlx openxiangda@2.34.0 auth status --base-url <平台地址> --json
73
+ pnpm dlx openxiangda@2.34.0 login --cwd my-app --base-url https://platform.example.com
74
+ pnpm dlx openxiangda@2.34.0 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
@@ -174,9 +174,9 @@ MCP 的 `docs_read` 可以读取本说明,当前没有独立的源码操作 MC
174
174
  无需本地工作区,使用本 Skill 随包精确版本或已安装的对应 CLI:
175
175
 
176
176
  ```bash
177
- pnpm dlx openxiangda@2.33.0 auth status --base-url <平台> --json
178
- pnpm dlx openxiangda@2.33.0 source resolve <仓库URL> --base-url <平台> --json
179
- pnpm dlx openxiangda@2.33.0 source clone <仓库URL> <新目录> --base-url <平台> --json
177
+ pnpm dlx openxiangda@2.34.0 auth status --base-url <平台> --json
178
+ pnpm dlx openxiangda@2.34.0 source resolve <仓库URL> --base-url <平台> --json
179
+ pnpm dlx openxiangda@2.34.0 source clone <仓库URL> <新目录> --base-url <平台> --json
180
180
  ```
181
181
 
182
182
  登录缺失或站点不匹配时,先按该平台执行 login。resolve 根据平台已经登记的绑定返回
@@ -1,6 +1,6 @@
1
1
  # 并发能力的前端接入
2
2
 
3
- 适用于活动报名、限量申领、抢票和预约。本文使用 `openxiangda@2.31.0` 引入的公开 API;目标平台必须支持 `data.managed-concurrency@1.0.0`。先按[缓存、排队与整数配额](managed-concurrency.md)声明读取、命令、配额和权限,再选择页面接入形式。
3
+ 适用于活动报名、限量申领、抢票和预约。permit API 从 `openxiangda@2.31.0` 引入;目标平台必须支持 `data.managed-concurrency@1.0.0`。先按[缓存、排队与整数配额](managed-concurrency.md)声明读取、命令、配额和权限,再选择页面接入形式。
4
4
 
5
5
  推荐体验是正常浏览内容,在用户明确提交时按需等待,并持续核对同一次申请的结果。低负载时可以很快完成,不需要人为添加等待页。
6
6
 
@@ -8,6 +8,7 @@
8
8
 
9
9
  | 形式 | 适用场景 | 用户流程 | 当前接入方式 |
10
10
  | --- | --- | --- | --- |
11
+ | 持久受理后可离开 | 后台自动完成、跨设备恢复 | 填写 → 明确提交 → 持久受理 → 查询结果 | `useDurableCommand`,需 durable 独立能力 |
11
12
  | 热点内容直接浏览 | 详情、场次、活动说明 | 浏览 → 按需刷新 | `client.read`,应用处理读取状态 |
12
13
  | 按钮发起、原地等待 | 一键报名、限量申领 | 点击 → 排队 → 处理 → 结果 | `useManagedCommand` + `ManagedCommandStatus` |
13
14
  | 先填写、再排队提交 | 有选项的预约和申请 | 填写校验 → 确认内容 → 排队提交 | 有界参数组合现有 hook;复杂资料需要额外冻结契约 |
@@ -167,17 +168,17 @@ export function ClaimAction({ offerId }: { offerId: string }) {
167
168
 
168
169
  默认恢复信息保存在 `sessionStorage`,按应用、环境、主体、命令和 `storageKey` 隔离。同标签页刷新可以恢复;`storageKey` 必须稳定,不能在每次渲染时随机生成。存储不可用时明确停止或处理,不能悄悄改用易丢失的内存继续提交。
169
170
 
170
- 已知 `operationId` 或原 `requestKey` 时,可以用 `client.result` 查询原结果。已成功业务记录可通过本人权限下的普通数据查询展示。当前没有“列出我的所有进行中命令”的接口;关闭标签后找回、跨设备继续和完整处理中列表需要额外的受管操作索引,不能把浏览器存储当作全平台事实。
171
+ 已知 `operationId` 或原 `requestKey` 时,可以用 `client.result` 查询原结果。已成功业务记录可通过本人权限下的普通数据查询展示。durable 命令另有 client.mine 和 useDurableCommand 用于本人跨设备恢复,见下文;原 permit 等待票据仍依赖本地保存,不能把浏览器存储当作全平台事实。
171
172
 
172
173
  不要把超时解释为提交失败,也不要在结果未知时 `clear()`。重试、恢复与返回入口都应该指向原意图。配额永久去重与界面请求键是不同边界:清除一个已终结的界面状态不代表可以再次获得同一池名额。
173
174
 
174
175
  ## 当前组件与后续封装边界 {#available-components}
175
176
 
176
- 当前可用:`createManagedConcurrencyClient`、`useManagedCommand`、`ManagedCommandStatus`、`ManagedCommandGate`,以及客户端的读取、结果、取消和分配状态方法。
177
+ 当前可用:`createManagedConcurrencyClient`、`useManagedCommand`、`useDurableCommand`、`ManagedCommandStatus`、`ManagedCommandGate`,以及客户端的读取、结果、取消和分配状态方法。
177
178
 
178
179
  `useManagedRead`、`ManagedCommandButton`、`ManagedCommandPanel`、`useManagedFormSubmission`、`useManagedAllocation`、`AllocationStatus` 是建议的后续封装名称,当前不是公开导出。不要照这些名称编写 import。应用现在可以使用已有 hook 与平台普通 UI 组件组合界面,后续标准封装仍应复用同一状态机和传输。
179
180
 
180
- 复杂资料冻结、跨设备命令索引、独立页面准入及精确倒计时需要相应的平台契约,不能仅通过前端外观补齐。SDK 版本、平台部署和实际业务容量需要分别确认。
181
+ 复杂资料冻结、跨所有命令的全局索引、独立页面准入及精确倒计时需要相应的平台契约,不能仅通过前端外观补齐。SDK 版本、平台部署和实际业务容量需要分别确认。
181
182
 
182
183
  ## 接入验收 {#acceptance}
183
184
 
@@ -194,3 +195,21 @@ export function ClaimAction({ offerId }: { offerId: string }) {
194
195
  | 键盘、屏幕阅读器、窄屏 | 状态可读,焦点可控,关闭与取消语义清楚 |
195
196
 
196
197
  这些检查与目标环境的吞吐、P95/P99、授权查询成本和故障恢复验证一起完成;页面演示和 SDK 单元测试不能代替真实业务验收。
198
+
199
+
200
+ ## 持久受理与跨设备恢复 {#durable}
201
+
202
+ ```tsx
203
+ const client = useMemo(() => createManagedConcurrencyClient(), []);
204
+ const command = useDurableCommand({client,command:'registration-enroll',resourceKey:activityId});
205
+ // 仅真实点击提交;不是 useEffect 或页面 mount 回调。
206
+ const submit = () => command.submit({activity:activityId,channel:channelId,agreed:true,phone});
207
+ ```
208
+
209
+ hook 从 `openxiangda/react` 和 `openxiangda/mobile` 导出。state、initialized、isObserving、requestKey、receipt、errorCode 用于统一状态区;submit(input)、resume()、refresh()、stop() 分别为明确提交、用冻结 input 重试原请求、恢复查询和停止观察。挂载只查本地原 key 或服务端 mine,不自动 enqueue。未知应答保留原 key 与 input;用户恢复后先查原结果,不能换 key 重试。浏览器存储失败在提交前明确失败。未知应答后的恢复按钮调用 resume(),不传当前可能已经修改的表单。snapshot.input 可恢复本地冻结表单;跨设备只有回执时不展示推测的原输入。一次明确 submit/resume 期间,429、暂时5xx或网络错误会按原 key/input 自动退避恢复,先查原结果再重试 enqueue,最多6次、最长120秒,遵守更长的 Retry-After;尚无 receipt 时只能提示“正在确认受理”,不能承诺可关闭页面。用完预算保留原意图,明确点击 resume() 继续。如果刷新时原请求尚未被平台受理,挂载只查询,明确点击 resume() 才重新启动有界受理重试。
210
+
211
+ 默认每次至少间隔 5 秒,遵守更长的服务端 retryAfterMs,再加随机抖动;暂时失败指数退避,终态停止。一个业务区域只挂载一个观察者。离开或关闭页面不撤销已受理请求,平台自动继续;新设备通过 mine 找到本人的原请求。position 为空时显示「已受理,稍后可查看」,不要显示虚假的精确人数或预计秒数。
212
+
213
+ refresh 会优先恢复 mine 返回的新进行中周期,避免本地历史 succeeded 遮蔽另一个设备的新申请。不要在 mount 发现历史 succeeded 时自动跳成功页或永久禁用提交。它可能已经被管理员取消,需结合当前业务记录展示。只有用户明确再次点击 submit,且原请求已有终态,SDK 才创建新的 requestKey;活跃请求或未知应答始终恢复原 key。平台明确返回未受理的参数错误(400 + CONCURRENCY_INPUT_INVALID 等约定错误)时,SDK 才清除被拒输入,允许修正后再提交;未知 400、409、429、5xx 和网络错误仍保留原意图。成功提示以 receipt.state==='succeeded' 和 receipt.result 为准,accepted/executing 只显示「已登记,处理中」。
214
+
215
+ permit 的 ManagedCommandGate 仍只用于 admitted 短确认,不用于 durable。durable 不能套任意前端 onSubmit 冒充后台事务,真正业务必须由已声明的 backend-plan handler 返回受管计划。
@@ -15,7 +15,7 @@
15
15
  | `commands` | 当前用户的能力、按资源分队列、最长等待、处理截止、Native 守卫和原子写入 |
16
16
  | `quotas` | 容量来源、业务记录上的分配 ID、可选的预占到期时间 |
17
17
 
18
- 参数限于 UUID、有限字符串枚举和有限整数范围;绑定只接受 `input`、可信 `actor`、字面量和平台生成的 `allocation`。每个命令最多 8 个操作、8 个记录守卫。首版不执行应用任意 JS/SQL,也不在锁内调用外部系统。通知和外部副作用接已有事务后事件。
18
+ 默认 permit 模式的参数限于 UUID、有限字符串枚举和有限整数范围;绑定只接受 `input`、可信 `actor`、字面量和平台生成的 `allocation`。permit 命令最多 8 个操作、8 个记录守卫。permit 模式不执行应用任意 JS/SQL,也不在锁内调用外部系统。通知和外部副作用接已有事务后事件。
19
19
 
20
20
  配额来源模型的容量字段必须是整数;分配记录模型的引用字段必须是必填 UUID,模型设置 `mutationOwner: 'queued-command'`。角色仍须具备来源读取、分配模型创建和命令能力。普通 CRUD、导入、应用凭据均不能绕过命令所有权。用户身份由 `actor` 绑定,不能使用用户填写的人员编号决定名额归属。
21
21
 
@@ -75,7 +75,7 @@ export function Claim({ offerId }: { offerId: string }) {
75
75
 
76
76
  立即分配用 `quota.action: 'allocate', mode: 'committed'`;需要预占时声明 `reservationSeconds` 并用 `mode: 'reserved'`。另声明 `confirm`、`release` 命令,绑定原 `allocation`,该命令的 `operations` 为空。只有原主体可确认/释放;到期与确认竞争使用数据库时间及固定锁顺序。
77
77
 
78
- 原命令回执不可变。预占之后到期或释放,应调用 `client.allocation(allocationId)` 获取当前分配状态,不能用旧成功回执证明仍持有名额。首版同一池/主体保留永久去重事实,释放后不会自动重新报名;应用若需要新一轮,应使用新的权威资源/配额池,不能删除历史分配。
78
+ 原命令回执不可变。预占之后到期或释放,应调用 `client.allocation(allocationId)` 获取当前分配状态,不能用旧成功回执证明仍持有名额。内置 quota 模式同一池/主体保留永久去重事实,释放后不会自动重新报名;应用若需要新一轮,应使用新的权威资源/配额池,不能删除历史分配。
79
79
 
80
80
  容量减少不能低于已预占加已确认数量。存在配额历史时,禁止删除来源、改写分配引用或发布移除配额映射的版本。应用激活先排空所有非终态命令;不会让旧任务在新 Head 上执行。连接开发消费已激活测试版本的并发声明,未激活的本地模型覆盖层不能作为持久命令契约。
81
81
 
@@ -98,3 +98,54 @@ RabbitMQ 的消息只是唤醒提示;队列丢失或发布失败由数据库
98
98
  暂停/恢复按同一个环境锁串行传播;Redis 不可用时控制接口明确报错。控制值是绝对值,可以使用相同 `paused` 重试并核对状态。数据库提交确认丢失时不猜测控制结果;受理始终复查数据库的暂停状态。
99
99
 
100
100
  验证应包括内容 SQL 与授权 SQL 占比、每秒完成数、P95/P99、最老等待、连接数、锁等待和恢复时间。仓库故障测试证明协议边界,不代表任何客户环境的生产吞吐。部署者仍需使用目标硬件、真实权限和活动规模做容量验收。
101
+
102
+
103
+ ## 持久排队的应用业务计划(durable)
104
+
105
+ 需要「受理后关闭浏览器仍继续」或取消后再次申请的业务,声明 `mode: 'durable'` 和 `execution.kind: 'backend-plan'`,额外要求 `data.managed-concurrency.durable@1.0.0`。默认省略 mode 仍走原 permit 协议,不能混用 wait/accept 和 enqueue。持久受理先提交数据库;MQ 只唤醒,Redis 只保存可重建的预算和位置投影。受理成功不是报名成功。
106
+
107
+ ```ts
108
+ {
109
+ code: 'registration-enroll', mode: 'durable', capability: 'app:arts:registration:submit',
110
+ parameters: { activity: {type:'uuid'}, channel: {type:'uuid'},
111
+ agreed: {type:'boolean'}, phone: {type:'string',maxLength:32} },
112
+ resourceKey: {from:'input',key:'activity'}, deadlineSeconds:3600,
113
+ intake: {perSecond:100,burst:100,maxInFlight:50},
114
+ admission: {perSecond:2,burst:2,maxInFlight:2,maxQueue:5000,maxWaitSeconds:3600},
115
+ execution: {kind:'backend-plan',handlerCode:'registration-enroll',timeoutMs:10000,
116
+ resources:[{resourceCode:'registrations',readFields:['id','activityId','person'],
117
+ writeOperations:['create'],writeFields:['activityId','person']}],
118
+ directory:{mode:'current-initiator',fields:['displayName','employeeNumber','primaryDepartment','departments']}}
119
+ }
120
+ ```
121
+
122
+ 这是声明片段,资源/字段、角色能力和应用总 admission 必须与实际模型一致。示例预算不是吞吐量承诺。durable 参数最多 8 个、编码最多 8 KiB;自由字符串 maxLength 最大 256,boolean 只允许 durable。intake 的 perSecond/burst/maxInFlight 各为 1..1000,独立限制新受理;admission 三项限制执行,不能超过应用共享预算。maxQueue 最大 10000,maxWaitSeconds 最大 3600,deadlineSeconds 为 10..3600,处理期限从最初数据库 acceptedAt 起算,重试不延长。
123
+
124
+ resources 最多 32 个,每项 readFields/writeFields 各最多 64 字段,writeOperations 仅 create/update/increment;不声明即不可访问。directory 仅允许当前发起人及四种现有字段,不接受 userId。不得同时声明静态 operations/guards/quota。普通业务模型不必改为 queued-command 所有权;管理员取消等正常业务动作仍使用原权限。已有内置 quota 的独占分配模型不允许通过 backend-plan 改写。
125
+
126
+ ### 后端只读计划
127
+
128
+ 从应用 generated 导入 `managedCommandHandlerManifest`,传给 `OpenXiangdaModule.forApplication({managedCommandHandlerManifest})`。使用 `OpenXiangdaManagedCommandHandler({commandCode,handlerCode})` 注册实现 `plan(input, context)` 的 Nest provider。声明会启用后端;显式禁用 backend 会编译失败。SDK 生成固定 `POST /__platform/managed-commands/:handlerCode/plan`,不可自定义 URL。
129
+
130
+ context 提供 commandId/commandCode/generation、actor.userId、acceptedAt/deadlineAt、data.get/query 和 directory.currentInitiator()。get 无记录抛 404;query 返回标准 DataPage。handler 强制 request scope,构造函数和依赖只在网关签名与在线执行证明核实之后解析。执行证明保存在 SDK 私有 ALS,普通 SDK 事务、写入、通知和 OAuth 调用在该上下文内拒绝。应用只返回:
131
+
132
+ ```ts
133
+ return {
134
+ schemaVersion:'openxiangda.managed-command-plan/v1',
135
+ guards:[{kind:'record-match',resourceCode:'channels',id:channel.id,
136
+ lockKey:`channel:${channel.id}`,errorCode:'OPENXIANGDA_REGISTRATION_CLOSED',
137
+ assertions:[{kind:'command-accepted-at',field:'closesAt',operator:'gte'}]}],
138
+ operations:[{operation:'create',resourceCode:'registrations',data:{activityId,person:context.actor.userId}}],
139
+ result:{registrationId:{operationIndex:0,field:'id'}}
140
+ };
141
+ ```
142
+
143
+ 对应读取和写入字段必须列入 declaration。guard 比较方向为「字段 operator acceptedAt」,不使用客户端时间,也不改变普通 database-now 含义。最多 99 操作、20 守卫、64 KiB 完整响应;仅 create/update/increment。result 中的 `{operationIndex,field:'id'}` 必须指向本计划的 create,由平台提交后替换真实 ID。禁止事务 key、任意副作用或调用回调。应用仍需实时容量、活动开关与活动周期去重守卫,不能把受理时间当作名额保证。
144
+
145
+ 平台在短事务中复核 generation/租约/head/期限/当前命令资格,执行 Native 计划并原子落终态。冲突重新运行只读 handler;网络重试不会重复业务效果。通知以业务事务中的待发送事实或已有事务事件触发,不能在 plan 中直接发送。
146
+
147
+ ### 本人结果与恢复
148
+
149
+ `client.enqueue(code,input,requestKey)` 返回持久回执;`client.mine(code,{resourceKey,cursor?,limit})` 只查询当前主体,最多 20 条;`client.result` 延续原 ID/原 key。回执包含 resourceKey、updatedAt,可选 queue `{position:number|null,observedAt}`,位置是近似投影,缺失不代表请求丢失。结果终态只说明那次业务事务;管理员取消后仍应读当前业务记录,用户明确操作才发起新周期。
150
+
151
+ 浏览器使用 `useDurableCommand`,详见[前端接入](managed-concurrency-frontend.md#durable)。部署先发布新 SDK/编译器并确认平台能力;回退前暂停新受理、排空或核对所有原命令,不让旧镜像消费未知执行模式。不能用 SDK 单测替代真实多用户并发、关闭浏览器、跨设备结果和原子事务验收。