openxiangda-skill-kit 2.0.0-alpha.13 → 2.0.0-alpha.131
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.
- package/README.md +6 -6
- package/dist/bin.js +0 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +167 -62
- package/dist/index.js.map +1 -1
- package/dist/internal/skill-installer.d.ts +8 -0
- package/dist/internal/skill-installer.d.ts.map +1 -0
- package/dist/internal/skill-installer.js +51 -0
- package/dist/internal/skill-installer.js.map +1 -0
- package/package.json +3 -6
- package/skills/manifest.json +2 -32
- package/skills/openxiangda-v2/SKILL.md +57 -24
- package/skills/openxiangda-v2/agents/openai.yaml +1 -1
- package/skills/openxiangda-v2/references/appspec.md +67 -0
- package/skills/openxiangda-v2/references/architecture.md +9 -0
- package/skills/openxiangda-v2/references/backend.md +279 -0
- package/skills/openxiangda-v2/references/commands.md +21 -0
- package/skills/openxiangda-v2/references/data-authz.md +410 -0
- package/skills/openxiangda-v2/references/delivery.md +49 -0
- package/skills/openxiangda-v2/references/discovery.md +15 -0
- package/skills/openxiangda-v2/references/frontend.md +259 -0
- package/skills/openxiangda-v2/references/public-access.md +159 -0
- package/skills/openxiangda-v2/references/testing.md +74 -0
- package/skills/openxiangda-v2/references/workflow-events.md +279 -0
- package/skills/openxiangda-v2/references/workspace.md +48 -0
- package/docs/architecture/repository-and-release.md +0 -52
- package/docs/backend.md +0 -89
- package/docs/concepts.md +0 -34
- package/docs/data-authz.md +0 -100
- package/docs/delivery.md +0 -71
- package/docs/frontend.md +0 -47
- package/docs/getting-started.md +0 -120
- package/docs/index.md +0 -23
- package/docs/llms.txt +0 -12
- package/docs/reference/cli.md +0 -51
- package/docs/reference/mcp.md +0 -26
- package/docs/workflow-events.md +0 -63
- package/skills/openxiangda-v2-architecture/SKILL.md +0 -29
- package/skills/openxiangda-v2-architecture/agents/openai.yaml +0 -4
- package/skills/openxiangda-v2-backend/SKILL.md +0 -42
- package/skills/openxiangda-v2-backend/agents/openai.yaml +0 -4
- package/skills/openxiangda-v2-data-authz/SKILL.md +0 -44
- package/skills/openxiangda-v2-data-authz/agents/openai.yaml +0 -4
- package/skills/openxiangda-v2-delivery/SKILL.md +0 -63
- package/skills/openxiangda-v2-delivery/agents/openai.yaml +0 -4
- package/skills/openxiangda-v2-frontend/SKILL.md +0 -40
- package/skills/openxiangda-v2-frontend/agents/openai.yaml +0 -4
- package/skills/openxiangda-v2-workflow-events/SKILL.md +0 -39
- package/skills/openxiangda-v2-workflow-events/agents/openai.yaml +0 -4
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
# OpenXiangda 2.0 Data and Authorization
|
|
2
|
+
|
|
3
|
+
Declare a resource and every field exactly once in `openxiangda.config.ts`.
|
|
4
|
+
Do not write `schemaVersion`, `appCode`, `schema`, `capabilities`, `surface` or
|
|
5
|
+
resource-level `fieldPolicies`; those are compiler projections, not application
|
|
6
|
+
source. Do not create `platform/data` modules.
|
|
7
|
+
|
|
8
|
+
Anonymous external access is not an application role, current-user policy or
|
|
9
|
+
unrestricted row grant. When a no-account visitor must submit, resume, upload or
|
|
10
|
+
read their own submissions, read [Anonymous public access](public-access.md) and
|
|
11
|
+
declare the exact `frontend.publicAccess` policy. Never widen the ordinary Native
|
|
12
|
+
Data API or manufacture an internal user to serve a public page.
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import {
|
|
16
|
+
currentUserDataPolicy,
|
|
17
|
+
dataPolicyExpression,
|
|
18
|
+
defineOpenXiangdaApp,
|
|
19
|
+
resourceCapabilityCodes,
|
|
20
|
+
resourceReadPolicy,
|
|
21
|
+
} from 'openxiangda/config';
|
|
22
|
+
|
|
23
|
+
const APP_CODE = 'visitor-center';
|
|
24
|
+
const reservations = resourceCapabilityCodes(APP_CODE, 'visitor-reservations');
|
|
25
|
+
|
|
26
|
+
const visitorReservations = {
|
|
27
|
+
code: 'visitor-reservations',
|
|
28
|
+
name: '访客预约',
|
|
29
|
+
dataPolicyCode: 'reservation-host',
|
|
30
|
+
fields: [
|
|
31
|
+
{
|
|
32
|
+
code: 'visitorName', type: 'text.short', label: '访客姓名', required: true,
|
|
33
|
+
indexed: true, list: true, filter: true, searchable: true, sortable: true,
|
|
34
|
+
section: '访客信息',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
code: 'visitDate', type: 'date', label: '来访日期', required: true,
|
|
38
|
+
list: true, filter: true, sortable: true, section: '来访安排',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
code: 'hostUserId', type: 'user.single', label: '接待人', required: true,
|
|
42
|
+
list: true, filter: true,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
code: 'hostDepartmentId', type: 'department.single', label: '接待部门',
|
|
46
|
+
list: true, filter: true,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
code: 'attachments', type: 'file', label: '附件',
|
|
50
|
+
file: { maxCount: 5, maxSizeMb: 20, accept: ['image/*', '.pdf'] },
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
code: 'internalNote', type: 'text.long', label: '内部备注',
|
|
54
|
+
access: { read: ['app:visitor-center:internal-note:read'], update: false },
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
list: { defaultPageSize: 20, defaultSort: { field: 'visitDate', order: 'desc' } },
|
|
58
|
+
form: { layout: 'sections' }, detail: { layout: 'sections' },
|
|
59
|
+
mobile: { enabled: true },
|
|
60
|
+
};
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Put it in `data: { resources: [visitorReservations] }`. Resource CRUD
|
|
64
|
+
capabilities are derived by `resourceCapabilityCodes`. For a Native resource,
|
|
65
|
+
grant `read` to each role that may enter its generated page; the compiler also
|
|
66
|
+
grants that role every enabled generated create/update/delete operation. Remove
|
|
67
|
+
an operation only when the role must be restricted, using the corresponding
|
|
68
|
+
code in `deniedCapabilities`. The compiler validates the denial and seals only
|
|
69
|
+
the resulting allow list, so runtime authorization has no second deny model.
|
|
70
|
+
Non-Native mutation owners never receive this expansion. Directory-backed roles also require
|
|
71
|
+
`app:<app-code>:directory:read`. Explicit field `access` capability codes are
|
|
72
|
+
also granted only to the intended roles. They are owned and exported by the
|
|
73
|
+
generated field policy, so do not repeat them in `authz.capabilities`.
|
|
74
|
+
`authz.capabilities` contains only explicit `backend` or `ui` capabilities.
|
|
75
|
+
|
|
76
|
+
Declare mutation ownership on the same resource with
|
|
77
|
+
`mutationOwner: 'native' | 'action' | 'readonly' | 'workflow'`. Native defaults
|
|
78
|
+
to generated list/detail/create/update/delete. Other owners default to readable
|
|
79
|
+
list/detail only and must use their named action or Workflow boundary for
|
|
80
|
+
mutation. Use `generated: { list, detail, create, update, delete }` to narrow
|
|
81
|
+
the generated page/operation surface. The compiler rejects Native mutation
|
|
82
|
+
pages, AI operations or role grants on a non-Native owner, and rejects
|
|
83
|
+
create/update when no writable business field exists. Do not grant a resource
|
|
84
|
+
create/update/delete capability to make an action-owned record editable.
|
|
85
|
+
|
|
86
|
+
Declare optional work Perspectives once at the application root. A Perspective
|
|
87
|
+
references existing role codes; the compiler derives its readable capability
|
|
88
|
+
projection, so never hand-author `capabilityCodes` or duplicate row filters:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
export default defineOpenXiangdaApp({
|
|
92
|
+
// ...identity, authz and data...
|
|
93
|
+
perspectives: [
|
|
94
|
+
{
|
|
95
|
+
code: 'reception-desk',
|
|
96
|
+
name: '接待人员视角',
|
|
97
|
+
roleCodes: ['reception_staff'],
|
|
98
|
+
default: true,
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
code: 'visitor-admin',
|
|
102
|
+
name: '访客管理员视角',
|
|
103
|
+
roleCodes: ['visitor_admin'],
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The runtime offers only Perspectives whose `roleCodes` intersect the current
|
|
110
|
+
user's actual application-role union. Selecting one sends
|
|
111
|
+
`X-OpenXiangda-Perspective` on standard reads. Native Data intersects the
|
|
112
|
+
normal authorized role union with that Perspective before capability, field,
|
|
113
|
+
row-policy and RLS evaluation. Omit the header for the complete union. Never
|
|
114
|
+
use Perspective to guard writes, workflows or custom actions.
|
|
115
|
+
|
|
116
|
+
`required: true` is a logical application rule: generated forms show it as
|
|
117
|
+
required, and the Native Data API rejects omission on create or explicit null
|
|
118
|
+
on create/update. It does not create PostgreSQL `NOT NULL`; every authored
|
|
119
|
+
business column remains physically nullable so fields can be added or made
|
|
120
|
+
required without a historical-data backfill. Older rows may therefore contain
|
|
121
|
+
null when they predate the rule. A field without `access` inherits the resource
|
|
122
|
+
read/create/update capability. Each access array is all-of; `false` is explicit
|
|
123
|
+
deny. The same arrays drive the generated UI and platform field policies.
|
|
124
|
+
|
|
125
|
+
For `uuid` fields, omission never generates a value. Only the resource system
|
|
126
|
+
field `id` is platform-generated. An omitted optional UUID remains `null`; a
|
|
127
|
+
required UUID must be supplied by a current create request and cannot rely on
|
|
128
|
+
an implicit database default. This is Data API validation, not a physical
|
|
129
|
+
`NOT NULL` constraint.
|
|
130
|
+
|
|
131
|
+
Field types are semantic, not PostgreSQL storage aliases. Use the catalog in the generated `AGENTS.md`: for example `text.short`, `number.integer`, `user.single`, `department.multiple`, `resource-ref.single`, `file`, `address` and `subtable`. The compiler alone chooses storage columns and constraints. Reference fields store JSON display values. For `resource-ref.*`, `resourceCode`, `value`, `label`, optional `description` and optional `snapshot` are convenient historical display data only: the target resource remains authoritative, the platform does not create a foreign key or refresh/check the stored JSON, and business actions that need current target state must query it by `resourceCode` plus `value`. A resource source `labelField` must point to `text.short` or `text.long`; a `serial-number` field can be listed in `searchFields`, `descriptionFields` or `snapshotFields`, but it is not a display label. File limits exist only under `file`; `maxCount` owns the single/multiple bound and `maxSizeMb` owns the per-file size bound. There is no `file.multiple` key.
|
|
132
|
+
|
|
133
|
+
Generated list, detail, audit and preview surfaces render the stored canonical
|
|
134
|
+
`label` snapshots for `option.*`, `user.*`, `department.*` and
|
|
135
|
+
`resource-ref.*`, including multiple arrays and the first linked list column.
|
|
136
|
+
Do not add application formatters, directory re-queries or browser-side joins
|
|
137
|
+
for these standard fields.
|
|
138
|
+
|
|
139
|
+
Numeric bounds belong on the field declaration and are enforced by the
|
|
140
|
+
platform for every write path. They are inclusive, and only valid on
|
|
141
|
+
`number.integer` or `number.decimal`:
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
{ code: 'capacity', type: 'number.integer', label: '容量', required: true, min: 0, max: 10000 }
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Cross-field rules belong on the resource, not in a generated form callback.
|
|
148
|
+
Only bounded same-record field comparisons are supported; declare multiple
|
|
149
|
+
invariants when all must hold:
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
{
|
|
153
|
+
code: 'sessions',
|
|
154
|
+
name: '场次',
|
|
155
|
+
fields: [
|
|
156
|
+
{ code: 'startAt', type: 'datetime', label: '开始时间', required: true },
|
|
157
|
+
{ code: 'endAt', type: 'datetime', label: '结束时间', required: true },
|
|
158
|
+
{ code: 'capacity', type: 'number.integer', label: '容量', required: true, min: 0 },
|
|
159
|
+
{ code: 'occupied', type: 'number.integer', label: '已占用', required: true, min: 0 },
|
|
160
|
+
],
|
|
161
|
+
invariants: [
|
|
162
|
+
{ code: 'time-order', expression: { leftField: 'startAt', operator: 'lt', rightField: 'endAt' } },
|
|
163
|
+
{ code: 'capacity-not-exceeded', expression: { leftField: 'capacity', operator: 'gte', rightField: 'occupied' } },
|
|
164
|
+
],
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Every `date-range` and `datetime-range` field explicitly declares
|
|
169
|
+
`rangeBoundary: 'closed' | 'half-open'`. Values remain `{ start, end }`; callers
|
|
170
|
+
never send or override the boundary. Closed ranges allow `start <= end` and use
|
|
171
|
+
PostgreSQL `[]`; half-open ranges require `start < end` and use `[)`. Adjacent
|
|
172
|
+
half-open values such as `[10:00, 11:00)` and `[11:00, 12:00)` do not overlap.
|
|
173
|
+
Use the ordinary `overlaps` query operator or a `query-empty` transaction guard;
|
|
174
|
+
never add or subtract milliseconds at the boundary.
|
|
175
|
+
|
|
176
|
+
Do not author raw schema or storage words as field types. `string`, `text`,
|
|
177
|
+
`integer`, `decimal`, `boolean`, `date`, `datetime`, `uuid`, `json` and `file`
|
|
178
|
+
describe value or storage families only when the platform protocol says so;
|
|
179
|
+
their authored counterparts are the semantic catalog entries above (some names
|
|
180
|
+
such as `date`, `datetime`, `uuid`, `json` and `file` intentionally coincide).
|
|
181
|
+
In particular, `number` is not a field type: choose `number.integer` or
|
|
182
|
+
`number.decimal`.
|
|
183
|
+
|
|
184
|
+
Current-user row access has one spelling only:
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
currentUserDataPolicy({
|
|
188
|
+
code: 'reservation-host',
|
|
189
|
+
name: '接待人员仅查看本人预约',
|
|
190
|
+
resourceCode: 'visitor-reservations',
|
|
191
|
+
field: 'hostUserId',
|
|
192
|
+
roleCodes: ['reception_staff'],
|
|
193
|
+
unrestrictedRoleCodes: ['visitor_admin'],
|
|
194
|
+
})
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Put that value in `authz.dataPolicies`, declare both roles, and set the
|
|
198
|
+
resource `dataPolicyCode` to the same code. Do not invent `current_user: true`,
|
|
199
|
+
lowercase match modes, a dotted value path, or policy-level `roleCodes`. The
|
|
200
|
+
compiler and platform know that `user.*` fields compare their stable `value`;
|
|
201
|
+
`field` remains the declared field root code.
|
|
202
|
+
|
|
203
|
+
For portal visibility windows, use the typed SDK expression and keep it a
|
|
204
|
+
server-enforced read policy:
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
resourceReadPolicy({
|
|
208
|
+
code: 'published-articles',
|
|
209
|
+
name: '仅查看当前已发布内容',
|
|
210
|
+
resourceCode: 'articles',
|
|
211
|
+
matchMode: 'AND',
|
|
212
|
+
rules: [{ dimensionCode: 'organization', field: 'organizationId' }],
|
|
213
|
+
expression: dataPolicyExpression.allOf(
|
|
214
|
+
dataPolicyExpression.constant({
|
|
215
|
+
field: 'status', operator: 'eq', value: 'PUBLISHED',
|
|
216
|
+
}),
|
|
217
|
+
dataPolicyExpression.databaseNow({
|
|
218
|
+
field: 'publishAt', operator: 'lte',
|
|
219
|
+
}),
|
|
220
|
+
dataPolicyExpression.anyOf(
|
|
221
|
+
dataPolicyExpression.null({ field: 'expireAt', operator: 'is_null' }),
|
|
222
|
+
dataPolicyExpression.databaseNow({ field: 'expireAt', operator: 'gt' }),
|
|
223
|
+
),
|
|
224
|
+
),
|
|
225
|
+
})
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
`db_now` is platform-owned PostgreSQL statement time and only accepts a
|
|
229
|
+
`datetime` field. Missing/null values match only `is_null`; they do not match
|
|
230
|
+
negative constants or time comparisons. The same bounded expression can
|
|
231
|
+
combine `currentUser`, `dimension`, `relation`, `constant`, `null`, and
|
|
232
|
+
`databaseNow` leaves under `allOf`/`anyOf`. Never duplicate this expression in
|
|
233
|
+
a page `where` filter and call it authorization: UI filters are optional
|
|
234
|
+
display state and cannot weaken or replace Native RLS.
|
|
235
|
+
|
|
236
|
+
`readExpression` is always added on top of the base `matchMode`/`rules`; those
|
|
237
|
+
base rules continue to restrict create/update/delete. If writes truly need no
|
|
238
|
+
row scope beyond capability checks, say so explicitly with
|
|
239
|
+
`writeBoundary: 'capability_only'` instead of `matchMode`/`rules`. Never use an
|
|
240
|
+
empty base implicitly: `check` rejects it so an AI cannot accidentally remove
|
|
241
|
+
write authorization while adding a portal read window.
|
|
242
|
+
|
|
243
|
+
When a business membership resource is the durable source of a package role or
|
|
244
|
+
RelationshipGrant, declare the projection instead of calling authorization
|
|
245
|
+
management endpoints from application code:
|
|
246
|
+
|
|
247
|
+
For an application intended for every logged-in platform user, declare one
|
|
248
|
+
baseline package role directly on `authz`. The platform materializes a real
|
|
249
|
+
membership for that role on first access, so routes, Data RLS, Workflow and
|
|
250
|
+
business actions consume the same union. Omit the declaration for applications
|
|
251
|
+
that require explicit role assignment:
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
authz: {
|
|
255
|
+
authenticatedUserRoleCode: 'applicant',
|
|
256
|
+
capabilities: [/* explicit UI and backend capabilities */],
|
|
257
|
+
roles: [
|
|
258
|
+
{
|
|
259
|
+
code: 'applicant',
|
|
260
|
+
name: '普通申请人',
|
|
261
|
+
capabilities: [/* bounded baseline capabilities */],
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
The referenced role must exist and cannot also be the target of a
|
|
268
|
+
`roleMembershipSource`. Use projected roles such as `member` for approved
|
|
269
|
+
business membership and let the current user receive the union, for example
|
|
270
|
+
`applicant + member`.
|
|
271
|
+
|
|
272
|
+
```ts
|
|
273
|
+
authz: {
|
|
274
|
+
// ...capabilities, roles and policies...
|
|
275
|
+
roleMembershipSources: [{
|
|
276
|
+
code: 'venue-managers',
|
|
277
|
+
name: '场馆管理员角色成员',
|
|
278
|
+
resourceCode: 'venue-manager-relations',
|
|
279
|
+
userIdField: 'manager.value',
|
|
280
|
+
roleCode: 'venue_admin',
|
|
281
|
+
enabledField: 'enabled',
|
|
282
|
+
failureMode: 'strict',
|
|
283
|
+
}],
|
|
284
|
+
relationshipGrantSources: [{
|
|
285
|
+
code: 'venue-member-grants',
|
|
286
|
+
name: '场馆成员关系授权',
|
|
287
|
+
resourceCode: 'venue-manager-relations',
|
|
288
|
+
subject: { type: 'user', userIdField: 'manager.value' },
|
|
289
|
+
relationCode: 'member',
|
|
290
|
+
targetResourceCode: 'venues',
|
|
291
|
+
resourceIdField: 'venue.value',
|
|
292
|
+
operations: ['read', 'update'],
|
|
293
|
+
enabledField: 'enabled',
|
|
294
|
+
failureMode: 'strict',
|
|
295
|
+
}],
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
The user path must be `user.single.value`; the target path is `id` only when the
|
|
300
|
+
source resource is also the target resource, or a `resource-ref.single.value`
|
|
301
|
+
that points at the declared target resource.
|
|
302
|
+
Operations are a bounded constant list. Create/update/delete the relationship
|
|
303
|
+
resource through the standard Data API; the platform converges and revokes only
|
|
304
|
+
the facts owned by that source. Projection failure is always strict because a
|
|
305
|
+
last-known-good grant could defeat revocation.
|
|
306
|
+
|
|
307
|
+
Use the SDK projection health call after deployment. A platform operator with
|
|
308
|
+
the existing authorization-management capability may run rebuild for historical
|
|
309
|
+
rows or recover a dead-letter job. Rebuild/recovery are idempotent and accept an
|
|
310
|
+
`operationId`; they never accept a user token, user id override or impersonation
|
|
311
|
+
input. Do not write `sourceCode`, canonical membership rows or relationship
|
|
312
|
+
grant rows yourself.
|
|
313
|
+
|
|
314
|
+
## Custom role-management pages
|
|
315
|
+
|
|
316
|
+
Use the current-user browser SDK when a custom desktop or mobile page must
|
|
317
|
+
maintain this application's role members. Do not create an application role
|
|
318
|
+
table, call the platform `role` controller, forward a developer token, or add a
|
|
319
|
+
NestJS endpoint that accepts a user/tenant/role from the browser.
|
|
320
|
+
|
|
321
|
+
An application or platform super administrator initializes delegation by
|
|
322
|
+
attaching one role-management grant to a business role. `manageAllRoles: true`
|
|
323
|
+
means every current application role and therefore requires
|
|
324
|
+
`managedRoleCodes: []`; otherwise list the exact role codes. Every grant must
|
|
325
|
+
include `membership.read` and may add `membership.assign`,
|
|
326
|
+
`membership.update`, `membership.revoke`, and `management.delegate`:
|
|
327
|
+
|
|
328
|
+
```ts
|
|
329
|
+
import {
|
|
330
|
+
createRoleManagementGrant,
|
|
331
|
+
loadRoleManagementCatalog,
|
|
332
|
+
} from 'openxiangda/core';
|
|
333
|
+
|
|
334
|
+
const catalog = await loadRoleManagementCatalog();
|
|
335
|
+
const managerAuthority = catalog.roleManagement.roles.find(
|
|
336
|
+
item => item.roleCode === 'business_manager',
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
await createRoleManagementGrant({
|
|
340
|
+
operationId: crypto.randomUUID(),
|
|
341
|
+
reason: '允许业务管理员维护场馆操作员',
|
|
342
|
+
subjectRoleCode: 'business_manager',
|
|
343
|
+
manageAllRoles: false,
|
|
344
|
+
managedRoleCodes: ['venue_operator'],
|
|
345
|
+
actions: [
|
|
346
|
+
'membership.read',
|
|
347
|
+
'membership.assign',
|
|
348
|
+
'membership.update',
|
|
349
|
+
'membership.revoke',
|
|
350
|
+
'management.delegate',
|
|
351
|
+
],
|
|
352
|
+
});
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
The platform always evaluates the current logged-in user's complete
|
|
356
|
+
application-role union. A delegated manager can pass on only role/action pairs
|
|
357
|
+
already present in that union and needs `management.delegate` for both the
|
|
358
|
+
recipient role and every managed role. It cannot manufacture an all-role grant
|
|
359
|
+
from several selected-role grants.
|
|
360
|
+
|
|
361
|
+
Build the member page only from these SDK calls:
|
|
362
|
+
|
|
363
|
+
```ts
|
|
364
|
+
import {
|
|
365
|
+
createRoleMembership,
|
|
366
|
+
listRoleMemberships,
|
|
367
|
+
searchRoleManagementUsers,
|
|
368
|
+
updateRoleMembership,
|
|
369
|
+
revokeRoleMembership,
|
|
370
|
+
} from 'openxiangda/core';
|
|
371
|
+
|
|
372
|
+
const users = await searchRoleManagementUsers({ keyword: '张' });
|
|
373
|
+
const page = await listRoleMemberships({
|
|
374
|
+
roleCode: 'venue_operator',
|
|
375
|
+
status: 'active',
|
|
376
|
+
limit: 20,
|
|
377
|
+
offset: 0,
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
await createRoleMembership({
|
|
381
|
+
operationId: crypto.randomUUID(),
|
|
382
|
+
reason: '张老师负责场馆日常运营',
|
|
383
|
+
userId: users.items[0]!.id,
|
|
384
|
+
roleCode: 'venue_operator',
|
|
385
|
+
scopeGrants: [],
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
const membership = page.items[0]!;
|
|
389
|
+
if (membership.maintainable) {
|
|
390
|
+
await updateRoleMembership(membership.id, {
|
|
391
|
+
operationId: crypto.randomUUID(),
|
|
392
|
+
reason: '调整角色有效期',
|
|
393
|
+
expectedRevision: membership.revision,
|
|
394
|
+
scopeGrants: membership.scopeGrants,
|
|
395
|
+
validFrom: null,
|
|
396
|
+
validTo: '2027-01-01T00:00:00.000Z',
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
Use `listRoleManagementGrants`, `updateRoleManagementGrant` and
|
|
402
|
+
`revokeRoleManagementGrant` for the delegation page. All updates/revocations
|
|
403
|
+
require the row's latest `revision`. On HTTP 409, reload catalog and rows; never
|
|
404
|
+
retry with a guessed revision. Every successful mutation returns an immutable
|
|
405
|
+
`receipt`; `loadAuthorizationMutationReceipt(operationId)` reads it again for
|
|
406
|
+
the same actor. Show `immutableReason` for an authenticated-user or projection
|
|
407
|
+
membership and never try to mutate it. A hidden button is only UX—the server
|
|
408
|
+
returns 403 for every undelegated role/action.
|
|
409
|
+
|
|
410
|
+
Run `pnpm openxiangda check` after every declaration or permission change.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# OpenXiangda 2.0 Delivery
|
|
2
|
+
|
|
3
|
+
Run `pnpm openxiangda check --json`; it defaults to the test target, compiles
|
|
4
|
+
the complete configuration and contract bundles, and asks the target
|
|
5
|
+
platform's advertised `configurationCompatibility` endpoint to run the same
|
|
6
|
+
Native compiler validation used during deployment preparation. This read-only
|
|
7
|
+
preflight happens before workspace checks, tests, builds, Buildx or uploads.
|
|
8
|
+
Use `--environment production` only when explicitly checking that target. A
|
|
9
|
+
failure preserves `pointer`, client contract/schema versions, platform
|
|
10
|
+
version/capability and the required/supported application-contract tuples.
|
|
11
|
+
The capability also publishes the existing 4 MiB configuration, 8 MiB
|
|
12
|
+
contract and 10 MiB total request bounds so oversized input fails before the
|
|
13
|
+
transport layer.
|
|
14
|
+
Never remove generated fields, reduce versions, raise limits, add app-code
|
|
15
|
+
exceptions or introduce 1.x compatibility to make it pass. The platform
|
|
16
|
+
revalidates authoritatively during deployment preparation.
|
|
17
|
+
|
|
18
|
+
After compatibility succeeds, check owns static checks, tests and production
|
|
19
|
+
builds but deliberately does not seal an AppPackage. Its
|
|
20
|
+
`data.sealedArtifact` object and `.openxiangda/build/seal-status.json` make that
|
|
21
|
+
state explicit even when an older `app-package.json` remains on disk. Deploy to
|
|
22
|
+
test with the returned `openxiangda deploy` next command. Deploy owns the
|
|
23
|
+
official application Dockerfile and platform-provided repository target,
|
|
24
|
+
builds and pushes the Nest image, then writes a `sealed` status tied to the new
|
|
25
|
+
immutable package digest. Never ask the developer for an image tag, digest,
|
|
26
|
+
registry password, Docker configuration, or another public build command. If
|
|
27
|
+
Docker, Buildx, repository configuration, or registry login is missing,
|
|
28
|
+
preserve the stable machine error and retry with the same
|
|
29
|
+
`pnpm openxiangda deploy` command after fixing that prerequisite.
|
|
30
|
+
|
|
31
|
+
`pnpm openxiangda accept --plan <file>` is an optional manual preproduction
|
|
32
|
+
test helper. It prepares expiring real identities but does not deploy, seal,
|
|
33
|
+
promote or satisfy any release gate. Use it only when the requested acceptance
|
|
34
|
+
needs real role membership and browser login.
|
|
35
|
+
|
|
36
|
+
Use `pnpm openxiangda status` and `pnpm openxiangda logs` without an ID for the most recent run, or pass an explicit run ID.
|
|
37
|
+
Treat DeploymentRun recovery as platform-authoritative. Inspect `rootFailure`,
|
|
38
|
+
`latestFailure`, `candidate`, `recovery` and the append-only `attempts` ledger.
|
|
39
|
+
Run only the published `recovery.nextCommand`; offer cancellation only when
|
|
40
|
+
`recovery.cancelAllowed` is true. Never infer retryability or cancellation from
|
|
41
|
+
status, the latest failure, or locally interpreted checkpoints.
|
|
42
|
+
|
|
43
|
+
For the AI-native MCP entrypoint, `build_app` and `deployment_plan` are explicitly unsealed previews. After the user authorizes deployment, call `deploy_app` without any image coordinate; it owns the same automatic Buildx, push, digest and sealing path as the CLI.
|
|
44
|
+
|
|
45
|
+
Applications with Native Data Resources automatically require `data.native-golden-crud`. If deployment returns `OPENXIANGDA_REQUIRED_CAPABILITY_UNAVAILABLE`, preserve the remediation to upgrade the platform and retry the same deploy command. Never remove the requirement, edit the AppPackage, construct a second identity path, or create Function-based CRUD.
|
|
46
|
+
|
|
47
|
+
All AppPackage platform requirements are compiler-derived structured entries with `code`, exact `contractVersion` and a deterministic declaration-only `usageDigest`. The target platform feature must be `available` at exactly that contract version. Applications cannot declare `platform.requiredCapabilities`, pass additional requirements to the package compiler or use the deleted string `requiredCapabilities` shape.
|
|
48
|
+
|
|
49
|
+
After a successful test run, deploy the exact same version with `pnpm openxiangda deploy --environment production --from <test-deployment-id>`. Roll back with `pnpm openxiangda rollback --environment production --to <app-version-id>`. The platform owns durable deployment state.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Requirement Discovery
|
|
2
|
+
|
|
3
|
+
Before editing, write a compact decision record covering:
|
|
4
|
+
|
|
5
|
+
- business objective and measurable outcome;
|
|
6
|
+
- actors and positive/negative scenarios;
|
|
7
|
+
- resources, field meanings, required values and lifecycle states;
|
|
8
|
+
- role memberships, operation capabilities, field restrictions and row scope;
|
|
9
|
+
- external systems, side effects, concurrency and idempotency;
|
|
10
|
+
- environment, security, volume and latency bounds;
|
|
11
|
+
- acceptance evidence for UI, API, PostgreSQL/RLS and delivery.
|
|
12
|
+
|
|
13
|
+
Separate known facts, assumptions and unresolved choices. Inspect the workspace protocol and existing declarations before inventing a contract. If a choice changes data meaning, authorization, external writes or release scope, resolve it before implementation. Ordinary naming or layout details may use a documented reasonable assumption.
|
|
14
|
+
|
|
15
|
+
The output is complete only when every requirement maps to an owner and a falsifiable acceptance check. Do not begin with source patches and reconstruct intent afterward.
|