openxiangda-skill-kit 2.0.0-alpha.13 → 2.0.0-alpha.130

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,259 @@
1
+ # OpenXiangda 2.0 Frontend
2
+
3
+ Read [Data and authorization](data-authz.md) for every resource or permission
4
+ change and keep the generated [workspace contract](workspace.md) authoritative.
5
+
6
+ Use the official Vite + React Router + Refine Core + Ant Design template. Import the platform-owned runtime from `openxiangda/react`, field controls from `openxiangda/field-kit` and clients/types from `openxiangda/core`. Do not copy these implementations into application source and do not add per-resource function wrappers.
7
+
8
+ Generated desktop lists already own row selection, atomic batch update/delete,
9
+ CSV/XLS/XLSX preview import, export, filters, column settings and density. Keep
10
+ imports at 100 rows per transaction, use exact declared labels or field codes,
11
+ and leave attachment values to the platform managed-file component.
12
+
13
+ Declare searchable and filterable fields on the resource and let the standard
14
+ CRUD renderer compose the search area. Keyword search spans the declared
15
+ searchable fields; the first two filters remain inline and additional filters
16
+ open in the platform's large modal. The renderer also preserves declaration
17
+ order for form fields and removes authorization/debug explanations from the
18
+ business UI. Do not create application-owned search toolbars, field ordering
19
+ maps, permission tips or per-resource CRUD pages for these standard behaviors.
20
+
21
+ Read the current user, complete application-role union and capabilities from the platform runtime authorization endpoint. There is no application-selected active role or identity switch. The standard Shell owns the optional Perspective selector. Use `hasReadCapability` for navigation, list/detail visibility and readable fields; continue to use `hasCapability` for create/update/delete, workflows and custom actions. The standard client sends the selected Perspective automatically, and the server repeats the projection for rows, fields and RLS. Never implement resource-specific frontend filters for it. Strip unauthorized fields from create and update payloads and never treat a disabled input as server authorization. Verify with `pnpm openxiangda check`.
22
+
23
+ Use the standard `Shell` unchanged. It owns the authenticated user's display
24
+ name, account identifier, safe avatar upload, localized application-role names,
25
+ theme controls, Perspective selection and navigation scroll restoration. It
26
+ also owns directory field requests under the current logged-in user's role
27
+ union. Never fetch identity for the header, display role codes, add a second
28
+ identity selector, or copy Shell and
29
+ directory-client implementations into application source.
30
+
31
+ For a custom application role-management page, use only the typed functions in
32
+ `openxiangda/core`: `loadRoleManagementCatalog`, `listRoleMemberships`,
33
+ `searchRoleManagementUsers`, the membership mutations, and the
34
+ role-management-grant mutations. The catalog's `roleManagement` projection
35
+ drives button visibility, while the platform repeats the check for every
36
+ request. The “Custom role-management pages” section in
37
+ [Data and authorization](data-authz.md) defines the delegation and CAS
38
+ contract. Do not use the developer control-plane client in browser code.
39
+
40
+ The application owns the editable admin information architecture through the
41
+ typed `defineAdminNavigation`, `adminNavigationGroup`, `adminResourcePage`,
42
+ and `adminOperationPage` helpers in `openxiangda/config`. The compiler emits
43
+ all reachable `adminPages`, but the Shell renders only pages explicitly
44
+ referenced by generated `adminNavigation`, then applies current-user access as
45
+ a final filter. Page existence, route reachability and menu visibility are
46
+ separate: detail/new/edit/dynamic/handoff pages never become menu entries by
47
+ discovery, and permission logic cannot create entries. Read
48
+ `openxiangda://workspace/contracts` or call `contract_describe`, then copy
49
+ `data.adminNavigationAuthoring.suggestion.expression` once into
50
+ `frontend.admin.navigation` with the listed `openxiangda/config` imports. The
51
+ proposal is deterministic and editable; the compiler/runtime never invokes it
52
+ or appends newly added resources later.
53
+
54
+ Generated desktop resource CRUD routes use the compiler-owned
55
+ `/admin/resources/<resourceCode>...` namespace and always render inside the
56
+ platform's one Shell. Their independent mobile admin surface is projected from
57
+ the same generated route catalog under `/m/admin/resources/<resourceCode>...`.
58
+ Generated pages consume that catalog for every list/create/detail/edit/back
59
+ navigation; do not reconstruct root resource paths in application code. Root
60
+ and ordinary `/m/...` paths remain available to explicit user routes. The
61
+ compiler rejects an explicit route whose canonical path shape conflicts with
62
+ any explicit or platform-generated route, including dynamic routes that differ
63
+ only by parameter name. Do not add aliases, redirects or route-order branches
64
+ for earlier alpha paths.
65
+
66
+ Declare the application-wide admin boundary at `frontend.admin.access` with
67
+ `allOf` and/or `anyOf` capability arrays. Import generated `adminAccess` and
68
+ pass it to `OpenXiangdaApplication`; the runtime evaluates that same immutable
69
+ expression before `/admin/**`, `/m/admin/**`, the Shell, generated resources or
70
+ admin contribution pages mount. Portal shortcuts may use the exported pure
71
+ `isAdminAccessAllowed` predicate, while Data/App/Workflow APIs remain the
72
+ server-side authority.
73
+
74
+ Declare custom routes in `openxiangda.config.ts`, import the generated
75
+ `appRoutes`, and bind every route key to exactly one local React page with
76
+ `defineApplicationContributions` from `openxiangda/react`. Pass the result to
77
+ `OpenXiangdaApplication`. The runtime registers `surface: 'admin'` routes inside
78
+ the one platform Shell; do not wrap them in another Shell. It renders
79
+ `surface: 'user'` routes without the admin Shell so mobile/user experiences can
80
+ own their page layout without creating another router, identity provider or
81
+ permission store. Only static admin routes explicitly referenced by
82
+ `frontend.admin.navigation` enter the menu. Hidden routes retain the same
83
+ `capability` or `access.allOf/anyOf`, including ancestor constraints. Keep admin
84
+ operation pages under `/admin/operations`; parameterized routes remain
85
+ reachable but cannot be menu references. `defineAdminContributions` remains an
86
+ admin-only helper and intentionally rejects `user` routes.
87
+
88
+ The platform also owns admin appearance. Wrap bespoke admin content in
89
+ `OpenXiangdaAdminPage`. Ant Design components inherit the existing
90
+ `ConfigProvider`; do not create another theme provider. Use
91
+ `useOpenXiangdaTheme()` for TypeScript-rendered values such as chart colors and
92
+ the public `--oxa-color-*`, `--oxa-shadow-surface`, and
93
+ `--oxa-radius-surface` variables for custom CSS. Never hard-code white, black,
94
+ or neutral greys as admin canvas, card, border, or text colors. Application
95
+ brand and categorical colors may remain explicit only when they are not used as
96
+ structural theme colors. Verify every custom admin page in light, dark, and
97
+ follow-system modes.
98
+
99
+ Declare application-owned login visuals with optional
100
+ `frontend.authentication`. The only supported account contract is
101
+ `existing-platform-users-only` with `registration.mode: 'reject'`; use exact
102
+ desktop `/login` and mobile `/m/login`, each pointing to a static protected user
103
+ default route. Import generated `authenticationSurfaces` and bind an exact
104
+ desktop/mobile renderer map alongside `appRoutes`:
105
+
106
+ ```tsx
107
+ const contributions = defineApplicationContributions(
108
+ { routes: appRoutes, authenticationSurfaces },
109
+ {
110
+ pages,
111
+ authentication: {
112
+ applicationLogin: DesktopLogin,
113
+ applicationLoginMobile: MobileLogin,
114
+ },
115
+ },
116
+ );
117
+ ```
118
+
119
+ Renderers receive only `ApplicationLoginSurfaceProps`: safe method descriptors,
120
+ state/error metadata, normalized `returnTo`, and platform callbacks. They never
121
+ receive credentials, provider configuration, OAuth state, tokens, roles, or
122
+ authorization facts. The platform owns the one Router and authentication
123
+ boundary, tokenless v2 facade, Secure HttpOnly session/refresh family and
124
+ logout. Do not put login in `appRoutes`, call v1 auth APIs, persist tokens, or
125
+ translate network/5xx and authenticated 403 states into login. Generated
126
+ `platformAuthManifest` is the independent login QA denominator and does not
127
+ change protected user route counts.
128
+
129
+ For a deliberately anonymous user page, first read
130
+ [Anonymous public access](public-access.md), declare one static `surface: 'user'`
131
+ route and bind it through `frontend.publicAccess`. The policy must name one
132
+ resource, its writable/returnable fields and only the bounded operations needed
133
+ by the page (`draft.read`, `draft.update`, `validate`, `create`, `own.list`,
134
+ `own.read`). A public route must not also declare `capability` or `access`.
135
+ Pass generated `anonymousPublicAccess` to
136
+ `<OpenXiangdaApplication publicAccess={anonymousPublicAccess}>`, then use
137
+ `createAnonymousPublicClient({ routeCode })` from `openxiangda/react` inside
138
+ the page. This client owns bootstrap, current-draft CAS, named validation,
139
+ managed upload, idempotent submission and owner-scoped list/detail calls. Do
140
+ not call the general Native Data API, store an identity in local storage, or
141
+ derive ownership from IP, user-agent or a browser fingerprint. The HttpOnly
142
+ browser credential identifies only this browser profile; clearing it or using
143
+ another browser loses access by design.
144
+
145
+ This is also the whole-application composition contract: pass the resulting
146
+ `contributions` to `OpenXiangdaApplication` and let that component remain the
147
+ only owner of `BrowserRouter`, `RuntimeBoundary`, Refine, generated resource and
148
+ Workflow routes, and the admin Shell. A user page may render an independent PC
149
+ or mobile layout, but it must not add a nested router/Shell or copy generated
150
+ routes. Route parameters continue to come from React Router, and generated
151
+ capability plus ancestor access guards run before the component renders.
152
+
153
+ To expose the standard application-level todo center, declare
154
+ `frontend.user.applicationTodoCenter: true`. The compiler generates `/todos`;
155
+ the same runtime exposes the independent mobile `/m/todos` page. The page reads only the authenticated user's
156
+ Notification Hub recipient projection and deep-links to the platform-resolved
157
+ target. Do not query Notification Hub management endpoints or recreate a local
158
+ message state store.
159
+
160
+ When an application needs branded user-end composition, keep those generated
161
+ routes and contribute renderers from the application entry through the public
162
+ `openxiangda/react` contract:
163
+
164
+ ```tsx
165
+ import {
166
+ defineApplicationContributions,
167
+ type StandardApplicationTodoCenterProps,
168
+ type StandardUserPageFrameProps,
169
+ type StandardUserSurfaceContributions,
170
+ } from 'openxiangda/react';
171
+
172
+ const standardUserSurfaces = {
173
+ frame: {
174
+ desktop: DesktopUserFrame,
175
+ mobile: MobileUserFrame,
176
+ },
177
+ applicationTodoCenter: {
178
+ desktop: DesktopTodoCenter,
179
+ mobile: MobileTodoCenter,
180
+ },
181
+ } satisfies StandardUserSurfaceContributions;
182
+
183
+ export const applicationContributions = defineApplicationContributions(
184
+ { routes: appRoutes, authenticationSurfaces },
185
+ { pages, authentication, standardUserSurfaces },
186
+ );
187
+ ```
188
+
189
+ Both desktop/mobile members and both groups are mandatory once
190
+ `standardUserSurfaces` is present; partial families fail at compile time and
191
+ again at runtime. Omit the property to retain the platform defaults.
192
+
193
+ `StandardUserPageFrameProps` contains `pageKind`, `device`, `mobile`, rendered
194
+ `children`, safe `route` metadata, `canGoBack` and the platform-owned `back()`
195
+ callback. The frame wraps Todo and standard Workflow work-center, launch, task
196
+ and instance pages. It must not create a Router, resolve identity, redirect a
197
+ standard path or reinterpret route metadata as authorization.
198
+
199
+ `StandardApplicationTodoCenterProps` contains only the authenticated current
200
+ user's `items`, aggregate `counts`, `total`, `loading`, `loadingMore`, `error`,
201
+ immutable `query`, `hasMore`, and the bounded callbacks `setQuery`, `refresh`,
202
+ `loadMore`, `recordInteraction` and `openItem`. `setQuery` owns view, keyword,
203
+ unread and paged-offset changes; `loadMore` appends and de-duplicates the next
204
+ platform page; `openItem` records a click best-effort and uses the
205
+ platform-resolved desktop/mobile target. Render only these props. Never import
206
+ the private platform client, call Notification Hub endpoints, persist a Todo
207
+ copy, accept a user/token parameter, or navigate from an item object not
208
+ supplied by the current render. A renderer exception is contained by the
209
+ platform error boundary without replacing `RuntimeBoundary` or the Router.
210
+
211
+ The compiler also emits the required generated `routeManifest`. This is the
212
+ single desktop/mobile paired catalog for standard Workflow and Todo pages. Each
213
+ entry carries stable route codes, path parameters, access metadata and a
214
+ `requiresAuthentication: true` marker; the top-level digest binds the complete
215
+ catalog. Pass it directly to `OpenXiangdaApplication`. The runtime validates
216
+ user Surface pairs, parameter names and shared access metadata before registering
217
+ the Router. Applications must not recreate standard `/todos`, `/m/todos`, or
218
+ Workflow paths, add aliases/redirects, or maintain a second route state. The
219
+ compiler/platform preflight is the digest authority; the browser validates the
220
+ digest shape and fails closed on malformed or inconsistent entries.
221
+
222
+ For standard process submission, consume the typed browser helpers
223
+ `loadBusinessProcessReceipt(commandId)` and
224
+ `pollBusinessProcessCommand(commandId, afterRevision)` (or the matching Nest
225
+ `receipt`/`poll` methods). A first response may be `accepted`; follow
226
+ `nextPoll` until `terminal` instead of treating it as failure or issuing a
227
+ generic request to the platform endpoint.
228
+
229
+ For a small business action on a generated resource page, use the typed
230
+ `resources[resourceCode].toolbar`, `.row` or `.detail` slots accepted by
231
+ `defineApplicationContributions`. Declare a stable action code, label and
232
+ capability/access expression. The render context contains only the current
233
+ resource, already-authorized record or selection, and a bounded `refresh()`;
234
+ it does not own CRUD, revisions, fields or authorization. Invoke a guarded Nest
235
+ App Operation for cross-resource transactions, invariants or side effects and
236
+ leave ordinary CRUD on the Native Data API. Do not copy a generated page just
237
+ to insert a button.
238
+
239
+ Generated contracts intentionally emit each resource Surface once in
240
+ `resourceSurfaces`; `resourceDefinitions[code].surface` references that shared
241
+ object. Import the generated runtime definitions normally. Do not serialize,
242
+ inline or duplicate Surface literals in application code.
243
+
244
+ The standard Workflow detail renderers consume only the authoritative Surface:
245
+ `presentation.businessDetail`, `presentation.summary`, the typed timeline and
246
+ operation descriptors. Continue to use `SurfaceFieldValue` for complete field
247
+ semantics and the Workflow-scoped file client for attachments, rich-text images
248
+ and signatures. Keep desktop and mobile renderers structurally independent;
249
+ place decision actions in the fixed primary group, all low-frequency actions in
250
+ the single more-actions entry, and collect comments in the action confirmation
251
+ layer. The canonical desktop paths are `/tasks/:taskId` and
252
+ `/workflows/:instanceId`; they render as standalone full-screen pages outside
253
+ the admin Shell, while the platform alone replace-redirects historical admin
254
+ detail URLs. Filter `system: true` and internal fields, omit empty sections,
255
+ merge operation actor/action/reason/time into its vertical node, and never show
256
+ UUIDs or revision/event diagnostics. Treat `stale` as metadata and show the
257
+ friendly refresh prompt only after a real command token/CAS conflict. Do not
258
+ copy the standard page into an application merely to show full business fields
259
+ or rearrange platform-owned operations.
@@ -0,0 +1,159 @@
1
+ # OpenXiangda 2.0 Anonymous Public Access
2
+
3
+ Read this reference whenever the requirement includes an external person without
4
+ a platform account, anonymous or guest access, a public form/page, resumable
5
+ submission, duplicate checks, public attachment upload, or reading the visitor's
6
+ own submitted records.
7
+
8
+ ## Decide the boundary first
9
+
10
+ Record these choices before editing:
11
+
12
+ - the one static public route and one Native resource it serves;
13
+ - the exact fields the visitor may write and receive;
14
+ - whether drafts are needed and their bounded inactivity lifetime;
15
+ - whether the page needs `own.list`, `own.read`, or both;
16
+ - every named duplicate validation and its exact field tuple;
17
+ - file fields and their normal resource-level type, count, size and MIME limits;
18
+ - same-browser-only acceptance and the negative browser/device cases.
19
+
20
+ This contract identifies possession of one browser profile, not a natural person.
21
+ Clearing the platform HttpOnly cookie, private browsing, another browser or another
22
+ device creates a new anonymous visitor. Do not promise recovery, merge or
23
+ cross-device continuity. WeChat, DingTalk, SMS/email verification and automatic
24
+ platform-account creation require later, separate identity contracts.
25
+
26
+ ## Declare the one public policy
27
+
28
+ Declare the resource normally, then add one static `surface: 'user'` route and one
29
+ `frontend.publicAccess` policy in `openxiangda.config.ts`:
30
+
31
+ ```ts
32
+ export default defineOpenXiangdaApp({
33
+ // ...app, authz and data declarations...
34
+ frontend: {
35
+ root: 'apps/web',
36
+ routes: [
37
+ {
38
+ code: 'visitor-apply',
39
+ path: '/visitor/apply',
40
+ label: '访客预约',
41
+ surface: 'user',
42
+ },
43
+ ],
44
+ publicAccess: {
45
+ policies: [
46
+ {
47
+ code: 'visitor-apply-public',
48
+ routeCode: 'visitor-apply',
49
+ mode: 'anonymous',
50
+ resourceCode: 'visitor-requests',
51
+ operations: [
52
+ 'draft.read',
53
+ 'draft.update',
54
+ 'validate',
55
+ 'create',
56
+ 'own.list',
57
+ 'own.read',
58
+ ],
59
+ fields: ['visitorName', 'phone', 'visitDate', 'photo'],
60
+ requiredFields: ['visitorName', 'phone', 'visitDate'],
61
+ ownRecordFields: ['visitorName', 'phone', 'visitDate', 'photo'],
62
+ draft: {
63
+ enabled: true,
64
+ inactivityTtlSeconds: 2_592_000,
65
+ maxBytes: 262_144,
66
+ },
67
+ validations: [
68
+ {
69
+ code: 'phone-unused',
70
+ kind: 'duplicate',
71
+ fields: ['phone'],
72
+ result: 'availability',
73
+ },
74
+ ],
75
+ },
76
+ ],
77
+ },
78
+ },
79
+ });
80
+ ```
81
+
82
+ The route must be static and must not also declare `capability` or `access`.
83
+ `fields`, `requiredFields`, `ownRecordFields` and validation fields must reference
84
+ declared fields on the same resource. Public create must cover every writable
85
+ required business field on that Native resource; `check` rejects an incomplete or
86
+ widened declaration.
87
+
88
+ Request only the operations the page uses:
89
+
90
+ - `draft.read` and `draft.update` resume the current browser's active draft;
91
+ - `validate` runs only named availability checks and never returns matching rows;
92
+ - `create` performs the final idempotent submission;
93
+ - `own.list` returns a bounded, server-ordered page of this browser's submitted
94
+ records;
95
+ - `own.read` returns one submitted record only after the server repeats the owner
96
+ and submitted-draft receipt checks.
97
+
98
+ ## Use the generated browser client
99
+
100
+ The template already passes generated `anonymousPublicAccess` to
101
+ `OpenXiangdaApplication`. Bind the route through the normal generated `appRoutes`
102
+ contribution, then use only the dedicated client in that page:
103
+
104
+ ```tsx
105
+ import { createAnonymousPublicClient } from 'openxiangda/react';
106
+
107
+ const publicClient = createAnonymousPublicClient({ routeCode: 'visitor-apply' });
108
+
109
+ const session = await publicClient.bootstrap();
110
+ const draft = session.draft ?? await publicClient.currentDraft();
111
+ const saved = await publicClient.saveDraft(draft.revision, {
112
+ visitorName,
113
+ phone,
114
+ visitDate,
115
+ });
116
+
117
+ const availability = await publicClient.validate('phone-unused', { phone });
118
+ const photo = await publicClient.upload('photo', file);
119
+ const withPhoto = await publicClient.saveDraft(saved.revision, { photo });
120
+ const receipt = await publicClient.submit(
121
+ withPhoto.revision,
122
+ crypto.randomUUID(),
123
+ );
124
+
125
+ const page = await publicClient.listOwn({ pageSize: 20 });
126
+ const detail = await publicClient.getOwn(receipt.recordId);
127
+ ```
128
+
129
+ Call `bootstrap()` before other methods and reuse the same idempotency key when
130
+ retrying one uncertain submission. Draft writes use the returned revision as CAS;
131
+ on conflict, reload the current draft instead of overwriting it. Treat duplicate
132
+ preflight as UI feedback only: the platform repeats validation atomically during
133
+ submission.
134
+
135
+ Never call the general Native Data API from this page, construct `created_by`,
136
+ accept a caller-selected draft id, query arbitrary filters/order/count, expose an
137
+ object-store credential, or use a NestJS action as anonymous CRUD. Never store an
138
+ identity in local storage or derive ownership from IP, user-agent or a browser
139
+ fingerprint. IP may participate only in platform rate limiting.
140
+
141
+ ## Acceptance
142
+
143
+ After `pnpm openxiangda check --json`, verify the exact preproduction route in real
144
+ browsers:
145
+
146
+ 1. a fresh browser can bootstrap, save, reload and resume one draft;
147
+ 2. declared file upload/preview and final submission succeed;
148
+ 3. named validation returns only `available` or `duplicate`, and concurrent final
149
+ duplicate submissions cannot both succeed;
150
+ 4. the same browser can list and open its submitted records;
151
+ 5. another browser gets an empty list and the same record id returns 404;
152
+ 6. clearing the browser credential loses draft/history access as documented;
153
+ 7. undeclared routes, fields, operations, validations and ordinary Data API calls
154
+ remain denied;
155
+ 8. production uses HTTPS and no public object-storage bucket or anonymous upload
156
+ whitelist was introduced.
157
+
158
+ Read [Data and authorization](data-authz.md), [Frontend](frontend.md) and
159
+ [Testing](testing.md) for the surrounding resource, page and delivery checks.
@@ -0,0 +1,74 @@
1
+ # Testing and Acceptance
2
+
3
+ Run `pnpm openxiangda check --json` after declaration or code changes. It owns
4
+ generation plus a read-only Native compatibility preflight against the test
5
+ platform before static validation, unit tests or production builds. Use
6
+ `--environment production` only for an explicit production-target check.
7
+ Preserve the stable diagnostic and JSON pointer instead of bypassing a failing
8
+ stage. Read
9
+ `data.sealedArtifact` in the machine result. A successful check always reports
10
+ `state: "check-did-not-seal"`, `sealed: false` and
11
+ `usableForDeploy: false`; it may also describe an older package as
12
+ `previousArtifact`. Never treat an existing `.openxiangda/build/app-package.json`
13
+ as output from the current check. Follow the exact `nextCommand`: rerun check
14
+ when diagnostics fail, or run `openxiangda deploy` to build, seal and deploy.
15
+
16
+ For each changed resource, verify the real chain:
17
+
18
+ 1. declaration compiles into one resource, Surface and capability set;
19
+ 2. desktop and mobile render the declared field semantics;
20
+ 3. create, read, update, delete, filtering, export and revision conflict use Native Data API;
21
+ 4. an allowed role succeeds and a denied role fails for operation, field and row paths;
22
+ 5. stored values and audit records match the declared shape;
23
+ 6. PostgreSQL/RLS remains authoritative, including current-user and multi-role-union cases.
24
+
25
+ For `frontend.publicAccess`, add a separate anonymous-browser matrix. Verify draft
26
+ resume, declared file upload, named availability validation, idempotent submission,
27
+ and same-browser `own.list`/`own.read`. In a second browser verify an empty list and
28
+ 404 for the first browser's record id. Clear the first browser credential and
29
+ confirm access is lost as documented. Also prove undeclared routes, fields,
30
+ operations, validations and ordinary Data API calls remain denied. A logged-in
31
+ admin path or a mocked client does not close anonymous public acceptance.
32
+
33
+ For platform generator changes, add a representative multi-resource fixture
34
+ (the compatibility corpus fixes 43 resources, complete list/form/detail/mobile
35
+ surfaces, Perspective/AuthZ/Workflow/Event and exactly 161 producers), assert
36
+ the real toolchain generator output remains byte-identical, and pass the same
37
+ fixture through the platform's exported Native validator. Then run the unchanged
38
+ Web dist budget gate. A budget failure is a
39
+ generator/runtime regression to fix; never raise the application budget or copy
40
+ generated contracts into a smaller application-local format. Browser acceptance
41
+ must also cover the large more-filters modal, declaration-order forms, hidden
42
+ developer-only authorization copy, real account/role labels, avatar update and
43
+ left-menu scroll preservation across route changes.
44
+
45
+ Mock and unit tests are useful but do not close remote acceptance. When real
46
+ identity acceptance is needed, create a short-lived local plan and run:
47
+
48
+ ```bash
49
+ pnpm openxiangda accept --plan .openxiangda/acceptance-plan.json --json
50
+ ```
51
+
52
+ The plan is explicit and preproduction-only:
53
+
54
+ ```json
55
+ {
56
+ "schemaVersion": "openxiangda.preproduction-acceptance-plan/v2",
57
+ "environmentKey": "preproduction",
58
+ "expiresInMinutes": 240,
59
+ "actors": [
60
+ { "key": "allowed", "roleCodes": ["resource_admin"] },
61
+ { "key": "denied", "roleCodes": ["resource_viewer"] }
62
+ ]
63
+ }
64
+ ```
65
+
66
+ The result returns real temporary users and one-time login URLs. Treat those
67
+ URLs as secrets, do not commit the plan/result, and let them expire. `accept`
68
+ is an additional manual test facility: it is never called by `check` or
69
+ `deploy`, never becomes a release gate, and a skipped run must not block a
70
+ release. When it is used, capture the exact AppVersion, environment Head,
71
+ positive/negative result, browser behavior and request identifiers. Production
72
+ promotion reuses the exact successful version; it is not another build.
73
+
74
+ For a new toolchain release, also create a completely fresh workspace using only the packed or published `openxiangda` package. Do not use repository-relative imports, unpublished workspace links or machine-installed legacy Skills.