openxiangda-skill-kit 2.0.0-alpha.98 → 2.0.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 (37) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2 -7
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +9 -19
  6. package/dist/index.js.map +1 -1
  7. package/dist/workspace-guidance.d.ts +13 -0
  8. package/dist/workspace-guidance.d.ts.map +1 -0
  9. package/dist/workspace-guidance.js +67 -0
  10. package/dist/workspace-guidance.js.map +1 -0
  11. package/package.json +12 -3
  12. package/skills/manifest.json +2 -2
  13. package/skills/openxiangda-v2/SKILL.md +63 -46
  14. package/skills/openxiangda-v2/agents/openai.yaml +2 -2
  15. package/skills/openxiangda-v2/references/administration.md +27 -0
  16. package/skills/openxiangda-v2/references/application-foundation.md +162 -0
  17. package/skills/openxiangda-v2/references/appspec.md +132 -47
  18. package/skills/openxiangda-v2/references/backend.md +101 -237
  19. package/skills/openxiangda-v2/references/cli.md +27 -0
  20. package/skills/openxiangda-v2/references/concepts.md +61 -0
  21. package/skills/openxiangda-v2/references/data-authz.md +36 -239
  22. package/skills/openxiangda-v2/references/delivery.md +110 -42
  23. package/skills/openxiangda-v2/references/development.md +32 -0
  24. package/skills/openxiangda-v2/references/field-components.md +236 -0
  25. package/skills/openxiangda-v2/references/frontend.md +269 -101
  26. package/skills/openxiangda-v2/references/getting-started.md +66 -0
  27. package/skills/openxiangda-v2/references/interaction-patterns.md +56 -0
  28. package/skills/openxiangda-v2/references/mcp.md +649 -0
  29. package/skills/openxiangda-v2/references/product-design.md +142 -0
  30. package/skills/openxiangda-v2/references/public-access.md +167 -0
  31. package/skills/openxiangda-v2/references/testing.md +45 -48
  32. package/skills/openxiangda-v2/references/upgrading.md +39 -0
  33. package/skills/openxiangda-v2/references/workflow-events.md +152 -138
  34. package/skills/openxiangda-v2/references/architecture.md +0 -7
  35. package/skills/openxiangda-v2/references/commands.md +0 -21
  36. package/skills/openxiangda-v2/references/discovery.md +0 -15
  37. package/skills/openxiangda-v2/references/workspace.md +0 -25
@@ -1,268 +1,132 @@
1
- # OpenXiangda 2.0 Backend
2
-
3
- Read [Architecture](architecture.md) and [Data and authorization](data-authz.md)
4
- before adding a backend. Finish with [Testing](testing.md).
5
-
6
- Keep normal record CRUD in the platform Data API. Use `apps/server` only for typed business actions that need server-side logic. For an interactive request, the platform gateway supplies the verified current user, complete application-role union and capabilities. Controllers bind operations and capabilities through `openxiangda/nest`; workers continue to use their explicit application identity.
7
-
8
- Every interactive business action uses `@OpenXiangdaOperation(operation)`. The
9
- guard checks `operation.requiredCapability` once at App API ingress. Inside that
10
- declared action, inject `OpenXiangdaBusinessDataApiService`,
11
- `OpenXiangdaBusinessNotificationService`, or use
12
- `OpenXiangdaStandardOperations`: Native Data and Notification Hub then run as the trusted backend
13
- for this exact application, environment, version and operation. Do not forward
14
- the caller's resource, row or field permissions into that internal Data call,
15
- and do not author an `authorizationJSON` permission mirror. The platform still
16
- records the initiating user and action code in records, files, events and audit.
17
- The platform models this internal hop as the request-bounded
18
- `native-business-action` principal. It is neither an OAuth client nor a Native
19
- runtime credential: PostgreSQL accepts it only after the platform has verified
20
- the signed Gateway invocation, exact target/Head, published
21
- `code + requiredCapability`, application data scope and initiating user. Never
22
- copy an invocation token into application data, construct the action headers
23
- yourself, or fall back to `OpenXiangdaApplicationDataApiService` when this
24
- channel is rejected; a rejection means the platform/package combination is not
25
- compatible or the invocation is stale.
26
-
27
- For an informational business notification, inject
28
- `OpenXiangdaBusinessNotificationService` in the same declared action. The SDK
29
- uses the short-lived gateway invocation, never the caller's original login
30
- token. The platform rechecks the operation and capability, promotes the call to
31
- the exact application/environment principal, and records the initiating user
32
- and action. Do not grant ordinary users `app:notification2:send` and do not use
33
- the privileged `OpenXiangdaNotificationService` as an application workaround.
1
+ # NestJS 后端
34
2
 
35
- ```ts
36
- import { Inject } from '@nestjs/common';
37
- import {
38
- OPENXIANGDA_NOTIFICATION_BUSINESS_SEND_V2,
39
- OPENXIANGDA_NOTIFICATION_EVENT_SEND_V2,
40
- OpenXiangdaBusinessNotificationService,
41
- } from 'openxiangda/nest';
3
+ 默认模板只包含 Web 和共享契约。只有需要执行服务端业务动作时才增加 NestJS。记录列表、详情、新增、编辑和删除直接由浏览器调用平台 Data API,不在 controller 中重写一遍。
42
4
 
43
- constructor(
44
- @Inject(OpenXiangdaBusinessNotificationService)
45
- private readonly notifications: OpenXiangdaBusinessNotificationService,
46
- ) {}
47
-
48
- await this.notifications.send({
49
- schemaVersion: OPENXIANGDA_NOTIFICATION_BUSINESS_SEND_V2,
50
- eventId: body.eventId,
51
- correlationId: `proposal:${proposal.id}`,
52
- messageKey: `proposal:${proposal.id}:invite:${member.id}`,
53
- sourceSequence: proposal.revision,
54
- recipients: [{ userId: member.userId }],
55
- title: '待您附议',
56
- summary: proposal.title,
57
- navigationTarget: {
58
- kind: 'APP_ROUTE',
59
- appCode: 'proposal_app',
60
- routeCodes: {
61
- desktop: 'proposal.detail',
62
- mobile: 'proposal.detail-mobile',
63
- },
64
- pathParams: { id: proposal.id },
65
- access: 'AUTHENTICATED',
66
- },
67
- idempotencyKey: `proposal:${proposal.id}:invite:${member.id}:v${proposal.revision}`,
68
- });
5
+ 平台网关验证当前用户完整的应用角色并集,并把经平台重验的角色、capability 与可选 Perspective 交给 Nest SDK。业务 controller 使用生成的 operation 合同和 capability 装饰器;平台仍是身份与授权的唯一所有者。请求作用域 `OpenXiangdaDataApiService` 自动继承 Perspective 读取投影;绕过 Data API 的自定义读取才使用 `@CurrentPerspective()` 显式投影。应用代码不替换身份、不保存平台凭据,也不建立第二套用户或权限状态。
69
6
 
7
+ ```bash
8
+ pnpm openxiangda dev
9
+ pnpm openxiangda check
70
10
  ```
71
11
 
72
- The fixed template accepts only `title` and optional `summary`. Reuse the same
73
- `eventId` or `idempotencyKey` for retries. Reuse `messageKey` with increasing
74
- `sourceSequence` for state convergence; do not generate a new logical message
75
- for each retry.
12
+ `openxiangda.config.ts` 声明 `backend: { enabled: true }`,或添加需要执行应用代码的
13
+ operation、事件消费者、人员提供器,然后运行 `pnpm openxiangda check` `pnpm dev`。
14
+ 工具从当前版本的内置模板初始化后端源码并安装依赖;后续不会覆盖业务代码。
15
+ 标准表单、流程定义/激活和平台待办通知使用平台运行时,不会隐式启用 Nest。
76
16
 
77
- The generated
78
- `notificationTemplateCodes.applicationInformational` constant documents the
79
- selected platform template; `send()` chooses it automatically and does not
80
- accept a caller-supplied template code.
17
+ 依赖安装失败会保留新源码并报告 `OPENXIANGDA_BACKEND_INSTALL_FAILED`;重试相同命令
18
+ 即可继续。关闭 backend 不自动删除用户源码。仅删除已经确认不用的后端目录和其依赖。
19
+ 本地 `/api` 经过 connected proxy 进入 Nest;发布态由同源应用网关转发。
81
20
 
82
- Inside a signed Application Events/date-trigger handler, call
83
- `sendFromEvent()` on the same service. The SDK obtains the application
84
- credential and forwards the current eventId, deliveryId and subscription code.
85
- Declare only dot paths beneath the immutable event `data`; Notification Hub
86
- resolves the actual Native user IDs and presentation text. Do not copy values
87
- from the event into a caller-supplied `recipients` array and never manufacture a
88
- user token.
21
+ | 需求 | 使用的 SDK | 权威边界 |
22
+ | --- | --- | --- |
23
+ | 当前用户与角色并集 | `@CurrentUser()` | 网关验证结果,业务不管理凭据 |
24
+ | 当前用户范围的数据 | `OpenXiangdaDataApiService` | 平台行/字段权限和读取 Perspective |
25
+ | 已授权业务动作的跨模型读写 | `OpenXiangdaBusinessDataApiService` | 声明的 operation;平台保留发起人审计 |
26
+ | 原子写入与幂等回执 | `.transaction(idempotentTransaction(...))` | 同一平台事务,不读后再写 |
27
+ | 分派对象必须具有指定角色 | 事务 `role-member` 条件 | 具名动作声明允许核对的角色;平台核对有效成员与并发 |
28
+ | 托管文件 | Data SDK 的 `initiateFileUpload` / `completeFileUpload` / `copyManagedFile` | 平台文件归属和操作授权 |
29
+ | 标准流程 | `OpenXiangdaWorkflowService`;带业务提交用 `OpenXiangdaBusinessProcessService` | 平台命令 token、版本和回执 |
30
+ | 当前用户待办 | `OpenXiangdaTodoService` | 平台投影,不另建待办表 |
31
+ | 通知 | `OpenXiangdaBusinessNotificationService` | 平台收件人、通道、投递和幂等 |
32
+ | 域事件 | 事务 `emitEvent` / `@OpenXiangdaEventHandler` | 平台 outbox 与消费回执 |
33
+ | 请求和动作日志 | `OpenXiangdaLoggerService`、`OpenXiangdaPlatformError.request` | Nest 日志输出和平台请求关联 |
89
34
 
90
- ```ts
91
- await this.notifications.sendFromEvent({
92
- schemaVersion: OPENXIANGDA_NOTIFICATION_EVENT_SEND_V2,
93
- correlationId: `proposal:${event.data.proposalId}`,
94
- messageKey: `proposal:${event.data.proposalId}:reminder`,
95
- sourceSequence: event.data.revision,
96
- recipientPaths: ['assigneeUserIds'],
97
- titlePath: 'notification.title',
98
- summaryPath: 'notification.summary',
99
- navigationTarget: {
100
- kind: 'APP_ROUTE',
101
- appCode: 'proposal_app',
102
- routeCodes: {
103
- desktop: 'proposal.detail',
104
- mobile: 'proposal.detail-mobile',
105
- },
106
- pathParams: { id: event.data.proposalId },
107
- access: 'AUTHENTICATED',
108
- },
109
- idempotencyKey: event.id,
110
- });
111
- ```
35
+ 下方代码是接入片段,模型与角色需要在应用中显式声明。独立的完整示例由工具链维护者在新建应用中做打包验收。
36
+
37
+ 自定义 operation 的 capability 必须先在 `authz.capabilities` 以
38
+ `kind: 'backend'` 声明,再由 operation 和允许调用它的角色共同引用。普通资源 CRUD
39
+ 能力仍由编译器生成,不写入显式 capability catalog。
40
+
41
+ 访客重复预约统一使用 `OpenXiangdaStandardOperations.createVisitorReservation`。
42
+ `duplicateMatch` 是字段代码到本次提交值的非空对象,不是字段名数组;它与 `data`
43
+ 必须来自同一个不可变请求,并连同 `idempotencyKey` 一次提交给平台事务。应用不先查
44
+ 重、不自行加锁、不在重试时重新生成业务时间。
112
45
 
113
- `OpenXiangdaDataApiService` deliberately retains ordinary current-user Data
114
- authorization and automatically inherits the request Perspective for reads.
115
- Its writes still authorize against the complete role union. Do not use it to
116
- implement a declared business action. The
117
- business facade also rejects routes without immutable `OpenXiangdaOperation`
118
- metadata, so a controller cannot silently gain trusted Data access.
46
+ 只读前置条件使用 `record-exists` `record-match`,它们不要求同记录 mutation,
47
+ 但仍执行 read capability、字段权限与行级授权。需要与数据库当前时间比较时使用
48
+ `databaseNowAssertion('publishAt', 'lte')`;平台用一次 PostgreSQL transaction time
49
+ 完成所有断言,并把该时间作为 `evaluatedAt` 存入幂等回执。相同幂等键重放不会重新
50
+ 读取当前时间。不得把 `Date.now()`、SQL 表达式、时区偏移或调用方时钟塞入断言。
119
51
 
120
- If a custom controller bypasses the standard Data API and reads Nest-owned SQL,
121
- an external service or another datastore, inject `@CurrentPerspective()` and
122
- apply the declared role-based read projection explicitly. Perspective is not a
123
- credential and must never change mutation authorization. Prefer moving ordinary
124
- reads back to `OpenXiangdaDataApiService`, where the platform owns the contract.
125
52
 
126
- Do not duplicate resource persistence in controllers. Keep actions bounded and side-effect behavior explicit. Run `pnpm openxiangda dev` for the connected loop and `pnpm openxiangda check` before delivery.
53
+ ## 业务动作与普通查询 {#business-action}
127
54
 
128
- Keep the generated `apps/server/src/main.ts` on
129
- `bootstrapOpenXiangdaApplication(AppModule)`. Do not replace it with an
130
- application-owned `NestFactory` or `FastifyAdapter`: the SDK bootstrap owns the
131
- exact raw request bytes required by gateway signatures, proxy trust, shutdown
132
- hooks, and the runtime listen contract.
55
+ `OpenXiangdaDataApiService` 按当前用户的普通资源、行和字段权限执行。具名业务动作使用 `OpenXiangdaBusinessDataApiService`:入口先检查该动作 capability,平台在精确应用和环境内以受信任后端执行,并保留发起人与动作审计。业务动作不能接受任意模型/字段/用户 ID 后不做业务校验;应用负责该动作的输入约束和业务不变量。
133
56
 
134
- Connected development and release builds deliberately use different compilers,
135
- so application Nest classes must never depend on emitted constructor metadata.
136
- Every constructor dependency in a `@Controller` or `@Injectable` class uses an
137
- explicit token:
57
+ 同一业务变更用一次受限事务表达。断言、派生计数、写入和事件保持原子性;遇到可重试响应时复用不可变 payload idempotencyKey,不先读取可变状态再决定写入。
58
+
59
+ ## 在分派事务中核对目标角色 {#role-member}
60
+
61
+ 维修派单、指定审核人等规则不能只依赖页面筛选或先查成员再写入。先在对应
62
+ `backend.operations[]` 的 `platformAccess` 声明允许核对的应用角色:
138
63
 
139
64
  ```ts
140
- import { Controller, Inject, Post } from '@nestjs/common';
141
- import { appOperations } from '@app/contracts';
142
- import {
143
- OpenXiangdaBusinessDataApiService,
144
- OpenXiangdaOperation,
145
- } from 'openxiangda/nest';
65
+ platformAccess: { roleAssertions: { roleCodes: ['technician'] } }
66
+ ```
67
+
68
+ `technician` 必须存在于本应用 `authz.roles`,最多声明 20 个角色。应用管理员身份
69
+ 不自动代表维修角色。人员候选可以使用已有且已委托管理范围的成员查询;这项声明
70
+ 本身不授予成员管理或人员目录权限。
71
+
72
+ 在 `OpenXiangdaBusinessDataApiService` 的同一次事务中表达业务状态与目标角色:
146
73
 
147
- @Controller()
148
- export class VisitorReservationsController {
149
- constructor(
150
- @Inject(OpenXiangdaBusinessDataApiService)
151
- private readonly data: OpenXiangdaBusinessDataApiService,
152
- ) {}
153
-
154
- @Post('/api/reservations/enroll')
155
- @OpenXiangdaOperation(appOperations.reservationEnroll)
156
- async enroll() {
157
- return await this.data.create('visitor-reservations', { status: 'pending' });
158
- }
159
- }
74
+ ```ts
75
+ await businessData.transaction({
76
+ schemaVersion: 'openxiangda.data-transaction-request/v2',
77
+ idempotencyKey: input.idempotencyKey,
78
+ guards: [
79
+ { kind: 'role-member', userId: input.technicianId, roleCode: 'technician',
80
+ errorCode: 'OPENXIANGDA_ASSIGNEE_INVALID' },
81
+ { kind: 'record-assert', resourceCode: 'service-orders', id: input.id,
82
+ lockKey: `service-order:${input.id}`, errorCode: 'OPENXIANGDA_ORDER_STATE_INVALID',
83
+ assertions: [{ kind: 'value', field: 'status', operator: 'eq', value: 'approved' }] },
84
+ ],
85
+ operations: [{ operation: 'update', resourceCode: 'service-orders', id: input.id,
86
+ expectedRevision: input.revision, data: { assignedTo: input.technicianId, status: 'assigned' } }],
87
+ });
160
88
  ```
161
89
 
162
- `openxiangda check` and `openxiangda dev` reject implicit constructor injection.
163
- Do not silence that diagnostic or replace the connected-development compiler.
90
+ 以上是接入片段;模型、字段、角色、动作 capability 和请求输入仍需在应用中声明。
91
+ 角色条件只有 `kind/userId/roleCode/errorCode`,不接受环境、成员快照、调用者锁名或
92
+ 调用者时间。所有 guard 合计最多 20 项。带业务写入的流程提交同样可以使用这一条件。
93
+ 普通用户 Data SDK、应用凭据和事件处理器不能使用;伪造操作请求头不能获得授权。
94
+
95
+ 平台在同一事务内核对当前租户、应用、环境和版本,以数据库取得的统一时间检查
96
+ 显式成员是否生效、过期或已撤销,并核对授权投影就绪。条件不成立返回指定失败码,
97
+ 无业务写入;没有声明返回 `OPENXIANGDA_ROLE_ASSERTION_NOT_DECLARED`。并发角色撤销、
98
+ 投影或环境切换返回 `OPENXIANGDA_ROLE_ASSERTION_CONFLICT`,锁等待最多 1 秒,整笔回滚。
99
+ 保留原请求和幂等键,根据当前业务状态决定是否重试。成功请求重放只返回已有结果;
100
+ 同一幂等请求绑定原发起人和业务动作,换人或换动作不能复用该回执。
164
101
 
165
- Backend routes are private to the application unless their operation declaration contains an explicit `ai` block. Never infer AI exposure or risk from the HTTP method. A write action declares its touched resources and non-empty side effects, for example:
102
+ 分派已接受后撤销角色,不会自动撤销历史分派;后续处理动作必须重新验证当前权限,
103
+ 由管理员重新分派。此规则应写入 AppSpec,并实测撤销先发生和分派先发生两种顺序。
166
104
 
167
- Build operation request and response schemas through
168
- `resourceRecordSchema`, `schemaRef` and `composeAppOperationSchemas` from
169
- `openxiangda/config`. These helpers project canonical field value protocols
170
- from the same Resource declaration and prevent repeated user, department,
171
- resource-reference or managed-file shapes. Do not import a physical toolchain
172
- package or copy the JSON protocol by hand.
105
+ ## 启动与依赖注入 {#bootstrap}
173
106
 
174
107
  ```ts
175
- {
176
- code: 'reservation.enroll',
177
- method: 'POST',
178
- path: '/api/reservations/enroll',
179
- capability: 'app:visitor-app:reservation:enroll',
180
- ...composeAppOperationSchemas({
181
- request: resourceRecordSchema(visitorReservations, {
182
- fields: ['visitorName', 'visitDate', 'startTime'],
183
- }),
184
- response: schemaRef('ReservationRecord'),
185
- definitions: {
186
- ReservationRecord: resourceRecordSchema(visitorReservations),
187
- },
188
- }),
189
- ai: {
190
- name: '预约访客',
191
- description: '校验预约规则并创建访客预约',
192
- risk: 'write',
193
- resources: ['reservations'],
194
- sideEffects: ['reservations:create'],
195
- concurrency: 'none',
196
- timeoutMs: 10000,
197
- },
198
- }
108
+ import 'reflect-metadata';
109
+ import { bootstrapOpenXiangdaApplication } from 'openxiangda/nest';
110
+ import { AppModule } from './app.module.js';
111
+ await bootstrapOpenXiangdaApplication(AppModule);
199
112
  ```
200
113
 
201
- Every custom operation capability is declared once in
202
- `authz.capabilities` with `kind: 'backend'`, then referenced by the operation
203
- and granted to every role allowed to call it. A capability string written only
204
- on an operation or role is invalid. Resource CRUD capabilities remain generated
205
- and must not be copied into this catalog.
114
+ 使用标准启动器保留原始请求体校验、代理信任和关闭处理。可注入依赖使用明确的 Nest 注入 token/装饰器,遵循生成后端的现有模式;不另建网关身份验证或自行转发授权 JSON。请求关联使用平台传入的 request ID。
206
115
 
207
- Generated CRUD and custom actions use the same platform Catalog, current-user authorization, Preview/Confirm boundary and idempotent execution path. Do not create an application-owned MCP server, preview store, AI role or database client.
116
+ ## 通知与事件 {#notifications}
208
117
 
209
- For visitor duplicates, meeting-time conflicts, and course capacity, use
210
- `OpenXiangdaStandardOperations`. These helpers submit the whole rule as one
211
- platform transaction. Do not query mutable rows before calling them and do not
212
- calculate replacement counters in application code. Course selection is a
213
- locked `record-assert` plus a bounded `increment`; replay must reach the
214
- platform idempotency receipt even after the course becomes full.
118
+ 具名用户动作发送通知使用 `OpenXiangdaBusinessNotificationService.send()`,携带稳定 eventId、messageKey、sourceSequence idempotencyKey。重放同一事件返回已有消息;同一 messageKey 的更高序列用于收敛状态。通知目标使用声明的 PC/移动路由代码及参数,不拼接环境域名或身份凭据。
215
119
 
216
- The visitor helper has one exact command shape. `duplicateMatch` is the actual
217
- field-to-submitted-value object used for equality matching; it is never a list
218
- of field names:
120
+ 签名事件处理器使用 `sendFromEvent()`,在声明中指定事件 data 内的收件人与文案路径,由平台验证不可变事件后解析。普通业务动作不需要平台通知管理权限。需要高级钉钉卡片时才使用已授权的管理服务和已启用通道,不能把它设为普通审批的默认依赖。
121
+
122
+ 通知协议常量也从同一个公开入口导入:
219
123
 
220
124
  ```ts
221
- return this.operations.createVisitorReservation({
222
- reservationResourceCode: 'visitor-reservations',
223
- duplicateMatch: {
224
- visitorIdentityNumber: body.visitorIdentityNumber,
225
- visitDate: body.visitDate,
226
- startTime: body.startTime,
227
- },
228
- data: {
229
- visitorIdentityNumber: body.visitorIdentityNumber,
230
- visitDate: body.visitDate,
231
- startTime: body.startTime,
232
- status: body.status,
233
- },
234
- idempotencyKey: body.idempotencyKey,
235
- activeStatusField: 'status',
236
- activeStatusValues: ['pending', 'approved'],
237
- });
125
+ import {
126
+ OpenXiangdaBusinessNotificationService,
127
+ OPENXIANGDA_NOTIFICATION_BUSINESS_SEND_V2,
128
+ OPENXIANGDA_NOTIFICATION_EVENT_SEND_V2,
129
+ } from 'openxiangda/nest';
238
130
  ```
239
131
 
240
- Use only declared field codes in `duplicateMatch`. Its values and `data` come
241
- from the same immutable request body.
242
-
243
- An idempotency key and the complete command payload are one immutable pair.
244
- Never put `new Date()`, `Date.now()`, `randomUUID()` or another changing value
245
- inside a controller while constructing the transaction. If a business time is
246
- part of the record, declare it as a required operation request field; the
247
- caller creates it once together with the idempotency key and reuses both on
248
- every retry. Platform-created record timestamps remain the source of truth
249
- when no separate business time is required.
250
-
251
- When declaring a lower-level transaction directly, use only the current
252
- contract:
253
-
254
- - every guard has a stable `OPENXIANGDA_*` `errorCode`;
255
- - `query-empty` protects a business-key uniqueness rule;
256
- - `record-exists` requires one visible record without forcing a mutation of it;
257
- - `record-match` requires one visible record plus typed value/field assertions,
258
- and may protect a mutation of another resource;
259
- - `record-assert` locks one exact record and checks declared field values or
260
- declared field-to-field comparisons;
261
- - `databaseNowAssertion('publishAt', 'lte')` compares a declared `datetime`
262
- field with the single PostgreSQL transaction time. Do not pass `Date.now()`,
263
- an offset, a SQL function or a fallback timestamp; a successful idempotent
264
- replay returns the original `evaluatedAt` without re-evaluating the guard;
265
- - `increment` targets one declared integer field and must have a matching
266
- same-resource, same-record `record-assert` guard;
267
- - never send SQL, table names, expressions, old guards without `errorCode`, or
268
- a pre-read/absolute-update fallback.
132
+ 用户动作 send schemaVersion 使用 OPENXIANGDA_NOTIFICATION_BUSINESS_SEND_V2;事件处理 sendFromEvent 使用 OPENXIANGDA_NOTIFICATION_EVENT_SEND_V2。二者的调用上下文和收件人来源不同,不能混用。
@@ -0,0 +1,27 @@
1
+ # CLI 命令参考
2
+
3
+ > 从实际命令注册表生成。参数与示例使用 `pnpm openxiangda <命令> --help` 查看;修改注册表后重新生成本页。
4
+
5
+ | 命令 | 影响 | 用途 |
6
+ | --- | --- | --- |
7
+ | `pnpm openxiangda auth` | 只读 | 只读核验指定平台授权,不登录或刷新会话 |
8
+ | `pnpm openxiangda context` | 只读 | 只读查看工作区、版本与平台绑定 |
9
+ | `pnpm openxiangda docs` | 只读 | 按主题和章节读取当前版本中文资料 |
10
+ | `pnpm openxiangda admin` | 只读 | 只读查看应用管理能力和流程节点运行配置 |
11
+ | `pnpm openxiangda create` | 远端变更 | 创建、绑定并初始化应用 |
12
+ | `pnpm openxiangda dev` | 本地写入 | 连接平台测试数据启动本地 Web,按需启动 Nest |
13
+ | `pnpm openxiangda check` | 本地写入 | 生成契约并在目标平台预检后执行检查、测试和构建 |
14
+ | `pnpm openxiangda accept` | 远端变更 | 按计划准备可选的真实预发验收身份 |
15
+ | `pnpm openxiangda deploy` | 远端变更 | 部署测试环境或显式复用测试版本部署生产 |
16
+ | `pnpm openxiangda status` | 只读 | 查询最近或指定部署状态 |
17
+ | `pnpm openxiangda logs` | 只读 | 查询最近或指定部署日志 |
18
+ | `pnpm openxiangda cancel` | 远端变更 | 幂等取消尚未提交激活的部署 |
19
+ | `pnpm openxiangda retry` | 远端变更 | 显式重试可恢复的失败部署 |
20
+ | `pnpm openxiangda start` | 远端变更 | 从当前不可变版本启动应用环境 |
21
+ | `pnpm openxiangda stop` | 远端变更 | 将应用环境缩容为零并保留数据 |
22
+ | `pnpm openxiangda rollback` | 远端变更 | 回滚测试或生产环境 |
23
+ | `pnpm openxiangda login` | 本地写入 | 通过平台浏览器授权登录 |
24
+ | `pnpm openxiangda skill` | 本地写入 | 安装当前版本的 AI Skill |
25
+ | `pnpm openxiangda spec` | 本地写入 | 维护需求、设计、变更与业务验收记录 |
26
+
27
+ 只验证时运行 check;部署测试环境时直接运行 deploy,它已包含检查、测试和构建。生产使用 deploy --environment production --from <测试运行ID>;加 --dry-run 只读预览。登录、创建和长期 dev 进程由 CLI 管理。
@@ -0,0 +1,61 @@
1
+ # 核心架构
2
+
3
+ ```mermaid
4
+ flowchart LR
5
+ Repo["应用 Git 仓库"] --> CI["项目 CLI / MCP"]
6
+ CI --> Package["不可变 AppPackage"]
7
+ Package --> Control["平台控制面"]
8
+ Control --> Deploy["DeploymentRun"]
9
+ Deploy --> Web["前端静态包"]
10
+ Deploy --> Backend["按需启用的 NestJS 容器"]
11
+ Deploy --> Config["Data/AuthZ/Workflow/Event 配置版本"]
12
+ Backend --> Data["统一 Data API"]
13
+ Backend --> Kernel["Workflow Kernel v2"]
14
+ Backend --> Events["事件投递服务"]
15
+ ```
16
+
17
+ ## 工程边界
18
+
19
+ - Git 仓库是应用源码与声明的事实来源。
20
+ - AppPackage 是交付边界,包含前端摘要、后端镜像摘要、配置包摘要和契约版本。
21
+ - 平台是运行状态的事实来源,持久保存应用版本、部署运行、检查点与环境激活状态。
22
+ - AI、CLI、MCP 都是控制面客户端,不负责持有发布状态。
23
+
24
+ ## 运行档位
25
+
26
+ 纯 CRUD 和标准审批无需应用后端。需要后端时使用平台可控的运行方式:每应用独立容器,共享 Kubernetes 集群、节点池、网关和可观测基础设施。后续可以用资源配额形成共享档与独享档,但不建设多应用共用 Node 进程。
27
+
28
+ ## 数据边界
29
+
30
+ 首期不为应用创建独立数据库。业务后端通过统一 Data API 访问平台数据;Data API 提供资源化查询、字段策略、行级授权、并发修订和受限事务批处理。这样保留统一治理,又不限制应用后端表达业务逻辑。
31
+
32
+ 应用后端声明具名 Operation 时,不应重新手写用户、部门、资源引用和文件字段协议。`openxiangda/config` 提供 `resourceRecordSchema`、`schemaRef`、`composeJsonSchema` 和 `composeAppOperationSchemas`:它们从同一 Resource declaration 和公共 `FIELD_VALUE_SCHEMAS` 投影请求/响应 JSON Schema,只允许有界的本地 `$defs`/`$ref`,不会改变 Data API、权限或业务事务 owner。
33
+
34
+ ```ts
35
+ import {
36
+ composeAppOperationSchemas,
37
+ resourceRecordSchema,
38
+ schemaRef,
39
+ } from 'openxiangda/config';
40
+
41
+ const schemas = composeAppOperationSchemas({
42
+ request: {
43
+ type: 'object',
44
+ additionalProperties: false,
45
+ required: ['record'],
46
+ properties: { record: schemaRef('InstrumentRecord') },
47
+ },
48
+ response: schemaRef('InstrumentRecord'),
49
+ definitions: {
50
+ InstrumentRecord: resourceRecordSchema(instruments, {
51
+ fields: ['name', 'owner'],
52
+ }),
53
+ },
54
+ });
55
+ ```
56
+
57
+ ## 版本列车
58
+
59
+ OpenXiangda 2.0 使用兼容版本列车,而不是要求所有 npm 包共享同一个版本号。各包按职责独立递增;应用只直接安装精确版本的 `openxiangda` 根包并提交锁文件,内部物理依赖由根包确定,不能使用范围或 `latest`。AppPackage 记录 AppPackage、configuration bundle、contract bundle 和 compiler contract 的原子兼容四元组;平台通过唯一的 `capabilities.configurationCompatibility` 契约声明完整可接受组合、验证端点与 validator 能力版本。CLI 必须把真实生成的 configuration/contract bundle 交给目标平台只读预检,并在应用生产构建、后端镜像构建和制品上传前拒绝不兼容组合;平台部署准备阶段继续权威复验,不允许删字段或向下协商。
60
+
61
+ AppPackage 的 `compatibility.requiredPlatformCapabilities` 也是 compiler 输出:每项固定包含 `code`、`contractVersion` 和只覆盖该能力相关规范化声明的 `usageDigest`。平台 `features[code]` 必须处于 `available` 且 `contractVersion` 精确相等;`preview`、缺失或版本不同都不能部署。应用配置没有 `platform.requiredCapabilities`,构建 API 也没有追加入口;不得手改 AppPackage 或用字符串能力名绕过 compiler。摘要不包含运行期业务数据或 secret 值,平台部署准备仍须根据原始 config/contract bytes 权威重算。CLI、MCP、Skills 和文档随相关包变更发布,不因无关包升级而强制全量重发。