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.
Files changed (50) hide show
  1. package/README.md +6 -6
  2. package/dist/bin.js +0 -0
  3. package/dist/index.d.ts +4 -1
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +167 -62
  6. package/dist/index.js.map +1 -1
  7. package/dist/internal/skill-installer.d.ts +8 -0
  8. package/dist/internal/skill-installer.d.ts.map +1 -0
  9. package/dist/internal/skill-installer.js +51 -0
  10. package/dist/internal/skill-installer.js.map +1 -0
  11. package/package.json +3 -6
  12. package/skills/manifest.json +2 -32
  13. package/skills/openxiangda-v2/SKILL.md +57 -24
  14. package/skills/openxiangda-v2/agents/openai.yaml +1 -1
  15. package/skills/openxiangda-v2/references/appspec.md +67 -0
  16. package/skills/openxiangda-v2/references/architecture.md +9 -0
  17. package/skills/openxiangda-v2/references/backend.md +279 -0
  18. package/skills/openxiangda-v2/references/commands.md +21 -0
  19. package/skills/openxiangda-v2/references/data-authz.md +410 -0
  20. package/skills/openxiangda-v2/references/delivery.md +49 -0
  21. package/skills/openxiangda-v2/references/discovery.md +15 -0
  22. package/skills/openxiangda-v2/references/frontend.md +259 -0
  23. package/skills/openxiangda-v2/references/public-access.md +159 -0
  24. package/skills/openxiangda-v2/references/testing.md +74 -0
  25. package/skills/openxiangda-v2/references/workflow-events.md +279 -0
  26. package/skills/openxiangda-v2/references/workspace.md +48 -0
  27. package/docs/architecture/repository-and-release.md +0 -52
  28. package/docs/backend.md +0 -89
  29. package/docs/concepts.md +0 -34
  30. package/docs/data-authz.md +0 -100
  31. package/docs/delivery.md +0 -71
  32. package/docs/frontend.md +0 -47
  33. package/docs/getting-started.md +0 -120
  34. package/docs/index.md +0 -23
  35. package/docs/llms.txt +0 -12
  36. package/docs/reference/cli.md +0 -51
  37. package/docs/reference/mcp.md +0 -26
  38. package/docs/workflow-events.md +0 -63
  39. package/skills/openxiangda-v2-architecture/SKILL.md +0 -29
  40. package/skills/openxiangda-v2-architecture/agents/openai.yaml +0 -4
  41. package/skills/openxiangda-v2-backend/SKILL.md +0 -42
  42. package/skills/openxiangda-v2-backend/agents/openai.yaml +0 -4
  43. package/skills/openxiangda-v2-data-authz/SKILL.md +0 -44
  44. package/skills/openxiangda-v2-data-authz/agents/openai.yaml +0 -4
  45. package/skills/openxiangda-v2-delivery/SKILL.md +0 -63
  46. package/skills/openxiangda-v2-delivery/agents/openai.yaml +0 -4
  47. package/skills/openxiangda-v2-frontend/SKILL.md +0 -40
  48. package/skills/openxiangda-v2-frontend/agents/openai.yaml +0 -4
  49. package/skills/openxiangda-v2-workflow-events/SKILL.md +0 -39
  50. 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.