openxiangda 1.0.270 → 1.0.271

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.
@@ -170,10 +170,14 @@ function commandFromArgs(args) {
170
170
  }
171
171
 
172
172
  function createStep(id, args, options = {}) {
173
+ const env = options.env && Object.keys(options.env).length > 0
174
+ ? { env: { ...options.env } }
175
+ : {};
173
176
  return {
174
177
  id,
175
178
  args,
176
179
  command: commandFromArgs(args),
180
+ ...env,
177
181
  writes: options.writes !== false,
178
182
  stagedKind: options.stagedKind || null,
179
183
  resumeMode: options.resumeMode || 'skip',
@@ -329,8 +333,7 @@ function buildWorkspaceReleaseSteps(
329
333
  targets.resourceSelectors?.formPermissionGroups || []
330
334
  );
331
335
  const stagesFormRelease =
332
- runtimeMode === 'react-spa' &&
333
- (targets.forms.length > 0 || formPermissionGroups.length > 0);
336
+ targets.forms.length > 0 || formPermissionGroups.length > 0;
334
337
  const deferredDataViewCodes = stagesFormRelease
335
338
  ? uniqueSorted(targets.resourceSelectors?.dataViews || [])
336
339
  : [];
@@ -347,58 +350,59 @@ function buildWorkspaceReleaseSteps(
347
350
  excludeTypes: stagesFormRelease ? ['formPermissionGroups'] : [],
348
351
  }
349
352
  );
350
- if (runtimeMode === 'react-spa') {
351
- const ensuredForms = uniqueSorted([
352
- ...targets.forms,
353
- ...targets.formDependencies,
354
- ]);
355
- if (ensuredForms.length > 0) {
356
- steps.push(
357
- createStep(
358
- 'form-ensure',
359
- appendProfileAndChange(
360
- ['form', 'ensure', '--only', ensuredForms.join(',')],
361
- profileArg,
362
- changeId
363
- ),
364
- { resumeMode: 'replay-local' }
365
- )
366
- );
367
- }
368
- if (stagesFormRelease) {
369
- const resourceTypes = [
370
- ...(targets.forms.length > 0 ? ['form-setting'] : []),
371
- ...(formPermissionGroups.length > 0
372
- ? ['form-permission-group']
373
- : []),
374
- ];
375
- const qualify = resourceTypes.length > 1;
376
- const selectors = [
377
- ...targets.forms.map(code =>
378
- qualify ? `form-setting:${code}` : code
353
+ const ensuredForms = uniqueSorted([
354
+ ...targets.forms,
355
+ ...targets.formDependencies,
356
+ ]);
357
+ if (ensuredForms.length > 0) {
358
+ steps.push(
359
+ createStep(
360
+ 'form-ensure',
361
+ appendProfileAndChange(
362
+ ['form', 'ensure', '--only', ensuredForms.join(',')],
363
+ profileArg,
364
+ changeId
379
365
  ),
380
- ...formPermissionGroups.map(code =>
381
- qualify ? `form-permission-group:${code}` : code
366
+ { resumeMode: 'replay-local' }
367
+ )
368
+ );
369
+ }
370
+ if (stagesFormRelease) {
371
+ const resourceTypes = [
372
+ ...(targets.forms.length > 0 ? ['form-setting'] : []),
373
+ ...(formPermissionGroups.length > 0
374
+ ? ['form-permission-group']
375
+ : []),
376
+ ];
377
+ const qualify = resourceTypes.length > 1;
378
+ const selectors = [
379
+ ...targets.forms.map(code =>
380
+ qualify ? `form-setting:${code}` : code
381
+ ),
382
+ ...formPermissionGroups.map(code =>
383
+ qualify ? `form-permission-group:${code}` : code
384
+ ),
385
+ ];
386
+ steps.push(
387
+ createStep(
388
+ 'form-stage',
389
+ appendProfileAndChange(
390
+ [
391
+ 'resource',
392
+ 'publish',
393
+ resourceTypes.join(','),
394
+ '--only',
395
+ selectors.join(','),
396
+ ],
397
+ profileArg,
398
+ changeId
382
399
  ),
383
- ];
384
- steps.push(
385
- createStep(
386
- 'form-stage',
387
- appendProfileAndChange(
388
- [
389
- 'resource',
390
- 'publish',
391
- resourceTypes.join(','),
392
- '--only',
393
- selectors.join(','),
394
- ],
395
- profileArg,
396
- changeId
397
- ),
398
- { stagedKind: 'FormRelease' }
399
- )
400
- );
401
- }
400
+ { stagedKind: 'FormRelease' }
401
+ )
402
+ );
403
+ }
404
+
405
+ if (runtimeMode === 'react-spa') {
402
406
  steps.push(...directConfigurationSteps);
403
407
  if (hasResourceTargets) {
404
408
  steps.push(
@@ -466,10 +470,13 @@ function buildWorkspaceReleaseSteps(
466
470
  return steps;
467
471
  }
468
472
 
469
- const onlyTargets = [
470
- ...targets.forms.map(code => `forms/${code}`),
471
- ...targets.pages.map(code => `pages/${code}`),
472
- ];
473
+ if (hasResourceTargets) {
474
+ steps.push(...directConfigurationSteps);
475
+ steps.push(
476
+ ...buildTargetedResourceReleaseSteps(targets, profileArg, changeId)
477
+ );
478
+ }
479
+ const onlyTargets = targets.pages.map(code => `pages/${code}`);
473
480
  if (onlyTargets.length > 0) {
474
481
  steps.push(
475
482
  createStep(
@@ -484,17 +491,17 @@ function buildWorkspaceReleaseSteps(
484
491
  ],
485
492
  profileArg,
486
493
  changeId
487
- )
494
+ ),
495
+ {
496
+ stagedKind: 'PageRelease',
497
+ env: { OPENXIANGDA_PAGE_STAGE_ONLY: '1' },
498
+ }
488
499
  )
489
500
  );
490
501
  }
491
- if (hasResourceTargets) {
492
- steps.push(...directConfigurationSteps);
493
- steps.push(
494
- ...buildTargetedResourceReleaseSteps(targets, profileArg, changeId)
495
- );
496
- }
497
502
  if (
503
+ stagesFormRelease ||
504
+ targets.pages.length > 0 ||
498
505
  targets.functions.length > 0 ||
499
506
  targets.automations.length > 0 ||
500
507
  targets.workflows.length > 0
@@ -508,6 +515,12 @@ function buildWorkspaceReleaseSteps(
508
515
  'app-finalize',
509
516
  '--staged-resources-json',
510
517
  `.openxiangda/releases/${changeId || 'change'}/staged-resources.json`,
518
+ ...(deferredDataViewCodes.length > 0
519
+ ? [
520
+ '--finalize-data-views',
521
+ deferredDataViewCodes.join(','),
522
+ ]
523
+ : []),
511
524
  '--wait',
512
525
  ],
513
526
  profileArg,
@@ -167,7 +167,7 @@ When the sealed Runtime source intentionally does not descend from the active Ru
167
167
 
168
168
  `resource plan` and publish dry-runs are strictly GET/HEAD-only. `READ_ONLY_AUTH_REQUIRED` means the access token expired; run `openxiangda auth refresh --profile <name>` or log in again before retrying. Never add an automatic refresh POST inside a plan.
169
169
 
170
- `release publish` is the default promotion entrypoint only for legacy unmanaged workspaces. It verifies without rewriting reviewed `change.json`/`release.json`, waits for the app lease, freezes the App capture after ownership is acquired, executes deterministic exact staged steps, resumes from `.openxiangda/releases/<change>/execution.json`, atomically finalizes, verifies mainline integration, and releases the lease. When the same audited historical-lineage condition applies, legacy `release publish` accepts `--adopt-online-baseline --adoption-reason "..."` and forwards the pair only to exact `resource publish --only/--code` stages; missing reasons or plans without an exact resource stage fail before lease acquisition. Environment-managed applications use the two-phase `release ship`; candidate/deploy/test/fail/promote, `release begin`, and child commands remain recovery/diagnostic primitives.
170
+ `release publish` is the default promotion entrypoint only for legacy unmanaged workspaces. It verifies without rewriting reviewed `change.json`/`release.json`, waits for the app lease, freezes the App capture after ownership is acquired, executes deterministic exact staged steps, resumes from `.openxiangda/releases/<change>/execution.json`, atomically finalizes, verifies mainline integration, and releases the lease. Legacy Page steps receive `OPENXIANGDA_PAGE_STAGE_ONLY=1`; the step environment and staged kind are hashed and journaled, and missing PageRelease evidence blocks Root finalize. Custom page publishers must honor that environment and call the current package CLI so `page publish` records the staged child. When the same audited historical-lineage condition applies, legacy `release publish` accepts `--adopt-online-baseline --adoption-reason "..."` and forwards the pair only to exact `resource publish --only/--code` stages. An intentional manifest replacement may add `--replace-manifest --reason "..."`; the inseparable pair reaches only the unique exact Backend stage. Missing reasons or compatible exact scopes fail before lease acquisition. Environment-managed applications use the two-phase `release ship`; candidate/deploy/test/fail/promote, `release begin`, and child commands remain recovery/diagnostic primitives.
171
171
 
172
172
  Reviewed bundle commands may retain `<profile>` as a template. The explicit real `release publish --profile <name>` value is bound to actual child argv without rewriting tracked SDD. React SPA page codes are logical coverage targets and activate through one Runtime child; they do not require PageRelease. `release app-head` and `runtime releases` are compact by default; use `--full` only when the complete manifest is required.
173
173
 
@@ -1,8 +1,8 @@
1
1
  # Notification Resources
2
2
 
3
- Use notification resources when a page, workflow, or automation needs reusable message templates.
3
+ Use notification resources when an App Function, workflow, or automation needs reusable message templates.
4
4
 
5
- AI generation rule: declare notification resources first, then call `sdk.notification` in code pages or `ctx.notification` in JS_CODE. Do not hardcode `/api/notification-config/*` or store channel credentials in source.
5
+ AI generation rule: declare notification resources first, then send only from trusted runtime code through `ctx.notification`. Code pages call a named App Function with a business identifier through `sdk.function.invoke`; they never choose effective recipients, rendered content, or channels. Do not hardcode `/api/notification-config/*` or store channel credentials in source.
6
6
 
7
7
  ## Resource Files
8
8
 
@@ -135,21 +135,35 @@ Standard card payload variables include `title`, `lastMessage`, `content`, `cont
135
135
 
136
136
  ## Runtime Calls
137
137
 
138
- Code pages:
138
+ Code pages invoke a named business action. Pass business identifiers, not an
139
+ effective recipient, rendered message, or channel list:
139
140
 
140
141
  ```ts
141
- await sdk.notification.sendByType({
142
- notificationType: "reservation_reminder",
143
- recipientId: userId,
144
- payload: {
145
- title: "预约提醒",
146
- instrumentName,
147
- startTime,
148
- },
142
+ await sdk.function.invoke("send_reservation_reminder", {
143
+ input: { reservationId },
149
144
  });
150
145
  ```
151
146
 
152
- DingTalk-specific helpers are available when the caller wants to inspect or force the DingTalk channel:
147
+ The App Function checks the operator and current reservation state, resolves the
148
+ recipient and template variables from authoritative data, then sends through the
149
+ trusted runtime bridge:
150
+
151
+ ```ts
152
+ export default async function sendReservationReminder(ctx) {
153
+ const reservation = await loadAuthorizedReservation(ctx.input.reservationId, ctx);
154
+ await ctx.notification.sendByType({
155
+ notificationType: "reservation_reminder",
156
+ recipientId: reservation.ownerUserId,
157
+ payload: {
158
+ title: "预约提醒",
159
+ instrumentName: reservation.instrumentName,
160
+ startTime: reservation.startTime,
161
+ },
162
+ });
163
+ }
164
+ ```
165
+
166
+ DingTalk-specific read and preview helpers remain available to pages:
153
167
 
154
168
  ```ts
155
169
  const capabilities = await sdk.notification.capabilities();
@@ -161,13 +175,6 @@ const preview = await sdk.notification.previewDingTalk({
161
175
  },
162
176
  });
163
177
 
164
- await sdk.notification.sendDingTalk({
165
- notificationType: "reservation_reminder",
166
- recipientId: userId,
167
- payload: {
168
- title: "预约提醒",
169
- },
170
- });
171
178
  ```
172
179
 
173
180
  User-facing in-app message centers should read the platform inbox instead of
@@ -198,12 +205,10 @@ openxiangda notification preview reservation_reminder --body-json '{"payload":{"
198
205
  openxiangda notification capabilities --json
199
206
  openxiangda notification dingding-preview reservation_reminder --body-json '{"payload":{"title":"测试"}}'
200
207
  openxiangda notification dingding-preview --template-code reservation_reminder --body-json '{"payload":{"title":"测试"}}'
201
- openxiangda notification dingding-send reservation_reminder --body-json '{"recipientId":"USER_ID","payload":{"title":"测试"}}' --force
202
- openxiangda notification send reservation_reminder --body-json '{"recipientId":"USER_ID","payload":{"title":"测试"}}' --force
203
- openxiangda notification batch-send reservation_reminder --body-json '{"recipients":[{"recipientId":"USER_ID","payload":{"title":"测试"}}]}' --force
208
+ openxiangda function invoke send_reservation_reminder --body-json '{"input":{"reservationId":"RESERVATION_ID"}}'
204
209
  ```
205
210
 
206
- Automation or workflow JS_CODE:
211
+ App Function, Automation, or workflow JS_CODE:
207
212
 
208
213
  ```ts
209
214
  export default async function notify(ctx) {
@@ -344,38 +344,16 @@ Requires Bearer token. Updates menu sorting and parent relationships in batch.
344
344
 
345
345
  ### POST `/apps/:appType/notifications/send-by-type`
346
346
 
347
- Requires Bearer token. Sends a notification in the current app scope.
348
- Each returned message can include `deliveryMeta` with provider message id, outTrackId, card template id, fallback usage, and provider error summary.
349
-
350
- ```json
351
- {
352
- "notificationType": "reservation_reminder",
353
- "recipientId": "user-id",
354
- "payload": {
355
- "title": "预约提醒"
356
- },
357
- "channels": ["inapp"]
358
- }
359
- ```
347
+ Removed. Always returns HTTP/envelope `410` with
348
+ `DIRECT_NOTIFICATION_SEND_REMOVED`. Pages must invoke a named App Function;
349
+ trusted runtime code sends through `ctx.notification` after business
350
+ authorization and recipient resolution.
360
351
 
361
352
  ### POST `/apps/:appType/notifications/batch-send-by-type`
362
353
 
363
- Requires Bearer token. Sends one notification type to multiple recipients.
364
-
365
- ```json
366
- {
367
- "notificationType": "reservation_reminder",
368
- "recipients": [
369
- {
370
- "recipientId": "user-id",
371
- "payload": {
372
- "title": "预约提醒"
373
- },
374
- "channels": ["inapp"]
375
- }
376
- ]
377
- }
378
- ```
354
+ Removed. Always returns HTTP/envelope `410` with
355
+ `DIRECT_NOTIFICATION_SEND_REMOVED`. Batch recipient selection belongs in a
356
+ bounded, authorized App Function, Workflow, Automation, or trusted JS_CODE.
379
357
 
380
358
  ### GET `/apps/:appType/notifications/templates`
381
359
 
@@ -426,7 +404,9 @@ or a direct DingTalk config:
426
404
 
427
405
  ### POST `/apps/:appType/notifications/dingtalk/send`
428
406
 
429
- Requires Bearer token. Sends the resolved `notificationType` through the DingTalk channel only. The CLI command is `openxiangda notification dingding-send <notificationType> --body-json '{"recipientId":"USER_ID","payload":{"title":"测试"}}' --force`.
407
+ Removed. Always returns HTTP/envelope `410` with
408
+ `DIRECT_NOTIFICATION_SEND_REMOVED`. The CLI command is also removed; use
409
+ `openxiangda function invoke <functionCode>` for an authorized business action.
430
410
 
431
411
  ### GET `/apps/:appType/notifications/type-configs`
432
412
 
@@ -15,7 +15,7 @@ Guidelines:
15
15
  - Use `sdk.dataSource.run()` with a page data source descriptor when the page config should own the data view code, default fields, or default filters.
16
16
  - Use `sdk.function.invoke(code, { input })` for reusable backend business logic declared under `src/resources/functions/` and `src/functions/`. Do not implement multi-form orchestration, connector fan-out, notification orchestration, or permission-sensitive backend rules directly in a page component.
17
17
  - Use `sdk.connector.invoke`, `sdk.connector.call("connector.api")`, or `sdk.connector.download` for external services. The SDK calls the platform runtime connector endpoint; it must not call third-party domains directly.
18
- - Use `sdk.notification.sendByType` and `batchSendByType` for reusable business messages. Custom notification types must be declared in `src/resources/notifications/` and published with `openxiangda resource publish`.
18
+ - Pages must not send notifications directly. Use `sdk.function.invoke(code, { input })` with business identifiers; the named App Function performs authorization, resolves recipients and variables from authoritative data, and sends with trusted `ctx.notification`. Custom notification types must still be declared in `src/resources/notifications/` and published with `openxiangda resource publish`.
19
19
  - Use `sdk.export.create` and `sdk.export.get` for asynchronous XLSX exports. Simple pages may send a declarative workbook definition. Complex or reusable exports should send only a published App Function `definitionCode`, the current query snapshot, export scope, and stable selected row IDs. The server executes `structured_export_provider_v1` with fresh user permissions; never upload executable rendering code from the browser. See `docs/structured-export-v1.md`.
20
20
  - Use `AttachmentPreviewList`, `ImagePreviewGrid`, or `useFilePreview` from `openxiangda/runtime/react` for in-page attachment previews in a custom React SPA page. They use the current PageSdk context and enforce the platform capability and ticket contracts. Use `sdk.createFileAccessTicket(bucketName, objectName, fileName, "preview", { appType })` only when the page needs a shareable or new-window preview link. `appType` defaults to the current page context. Open `response.result.previewPageUrl`; do not use `previewUrl` or `/service/file/preview-by-ticket/:ticket` as the page entry.
21
21
  - Use `sdk.organization.departments.*` and `sdk.organization.accounts.*` only for intentional organization pages. Read-only list/detail pages need `app:organization:read` or `app:organization:manage`; writes and password operations need `app:organization:manage`. Do not call legacy `/user` or `/department` endpoints from pages.
@@ -552,19 +552,17 @@ const stats = await sdk.dataView.stats("ticket_stats_by_customer", {
552
552
  }
553
553
  ```
554
554
 
555
- 页面调用:
555
+ 页面调用具名 App Function,只传业务标识:
556
556
 
557
557
  ```ts
558
- await sdk.notification.sendByType({
559
- notificationType: "reservation_reminder",
560
- recipientId: userId,
561
- payload: { title: "预约提醒", instrumentName, startTime },
558
+ await sdk.function.invoke("send_reservation_reminder", {
559
+ input: { reservationId },
562
560
  });
563
561
  ```
564
562
 
565
- JS_CODE 调用:`ctx.notification.sendByType({ ... })`。允许的 channels:`inapp` / `email` / `dingding` / `wechat` / `thirdparty_todo`。完整规则见 [`notifications.md`](notifications.md)。
563
+ App Function / Workflow / Automation / JS_CODE 在完成业务鉴权并解析接收人后调用:`ctx.notification.sendByType({ ... })`。允许的 channels:`inapp` / `email` / `dingding` / `wechat` / `thirdparty_todo`。完整规则见 [`notifications.md`](notifications.md)。
566
564
 
567
- 钉钉卡片预览/发送:`sdk.notification.previewDingTalk({ notificationType, payload })`、`sdk.notification.sendDingTalk({ notificationType, recipientId, payload })`;CLI 为 `openxiangda notification dingding-preview` 和 `openxiangda notification dingding-send --force`。
565
+ 页面可做钉钉卡片预览:`sdk.notification.previewDingTalk({ notificationType, payload })`;CLI 为 `openxiangda notification dingding-preview`。实际发送必须位于具名 App Function 等可信运行时中。
568
566
 
569
567
  ## 4. App Function — `src/resources/functions/<functionCode>.json` + `src/functions/<functionCode>/index.ts`
570
568
 
@@ -157,7 +157,7 @@ An environment-managed release may intentionally replace complete Function/Autom
157
157
 
158
158
  For an audited Runtime source rollback, managed `release ship`, recovery `release deploy`, and `release promote` accept `--allow-runtime-rollback --reason "..."` with a reason of at least 8 characters. This pair is scoped only to `runtime-stage` and never reaches resource stages or `app-finalize`. Ship freezes it in `ship.json`; production confirmation inherits it automatically. The default remains fail closed.
159
159
 
160
- `release publish` is the normal whole-app entrypoint for legacy unmanaged workspaces: it verifies SDD without mutating reviewed files, waits for the promotion lease, freezes one App capture, stages the exact Form/Backend/Runtime children, resumes from a private execution journal, finalizes once, and releases the lease. For an approved historical-lineage catch-up, add `--adopt-online-baseline --adoption-reason "..."`; the pair reaches only exact `resource publish --only/--code` stages, never Form ensure, Runtime, or App finalize, and invalid or empty scopes fail before lease acquisition. Managed applications use two-phase `release ship` and its deployment-scoped journal. Individual candidate/deploy/test/fail/promote commands are recovery/diagnostic primitives. `release fail --deployment <id> --message <text> [--code <code>] [--details-json <JSON|file>] --environment preproduction` records an audited UAT failure only after verifying the deployment belongs to the selected preproduction environment; production targets and mismatched deployment identities fail before the write.
160
+ `release publish` is the normal whole-app entrypoint for legacy unmanaged workspaces: it verifies SDD without mutating reviewed files, waits for the promotion lease, freezes one App capture, stages the exact Form/Backend/Page children, resumes from a private execution journal, finalizes once, and releases the lease. A legacy Page step runs with `OPENXIANGDA_PAGE_STAGE_ONLY=1`; its environment and staged kind are part of the plan hash, and missing PageRelease evidence blocks Root finalize. Custom page publishers must honor that environment and invoke the current package CLI. For an approved historical-lineage catch-up, add `--adopt-online-baseline --adoption-reason "..."`; the pair reaches only exact `resource publish --only/--code` stages, never Form ensure, Page, or App finalize. A deliberate Backend manifest replacement may add `--replace-manifest --reason "..."`; the pair reaches only the unique exact Backend stage. Invalid reasons or empty/incompatible scopes fail before lease acquisition. Managed applications use two-phase `release ship` and its deployment-scoped journal. Individual candidate/deploy/test/fail/promote commands are recovery/diagnostic primitives. `release fail --deployment <id> --message <text> [--code <code>] [--details-json <JSON|file>] --environment preproduction` records an audited UAT failure only after verifying the deployment belongs to the selected preproduction environment; production targets and mismatched deployment identities fail before the write.
161
161
 
162
162
  Reviewed bundle commands may retain `<profile>` as a template. The explicit real `release publish --profile <name>` value is bound to actual child argv without rewriting tracked SDD. React SPA page codes remain logical coverage targets and activate through the single Runtime child; they do not require PageRelease. If local lease state disappears, `release end --change <id>` reconciles a self-owned remote lease from the private execution journal and never reports inactive while a remote lease is active.
163
163
 
@@ -44,7 +44,7 @@ openxiangda workspace publish --profile <name> --page <pageCode>
44
44
  - ❌ Raw native form controls in AI-authored page code (`<input>`, `<select>`, `<textarea>`, file inputs, hand-written pickers/uploaders). Use platform components or `antd` / `antd-mobile`.
45
45
  - ❌ Embed a single `FormProvider` field component temporarily; navigate to a full standard form page or render a complete `StandardFormPage` instead.
46
46
  - ❌ Reuse one form UI for both PC and mobile without verifying overlay / picker / bottom-sheet behavior on both viewports.
47
- - ❌ Hardcode notification or platform API URLs; use `openxiangda/runtime` and `src/resources/notifications/` declarations.
47
+ - ❌ Send notifications directly from a page or hardcode notification/platform API URLs. Pages invoke a named App Function with business identifiers; trusted runtime code uses `ctx.notification` and `src/resources/notifications/` declarations.
48
48
 
49
49
  ## CLI Flow
50
50
 
@@ -133,7 +133,7 @@ Read these references only when editing page code:
133
133
  - Store live `pageId`, `routeKey`, and `legacyFormUuid` under the current profile only.
134
134
  - Use `openxiangda/runtime` for platform data access instead of hardcoding backend URLs in page code.
135
135
  - 家校关系必须通过 `sdk.organization.schoolContact` / `ctx.organization.schoolContact` 读取;班主任班级使用 `teachers.list` 的 `isHeadTeacher`,不要用全局角色推断具体班级,也不要直连钉钉、查询系统表或用同班成员推断亲属关系。
136
- - For reminders, alerts, and business messages, declare `src/resources/notifications/` first and call `sdk.notification`; do not hardcode notification API URLs.
136
+ - For reminders, alerts, and business messages, declare `src/resources/notifications/` first. Pages call a named App Function through `sdk.function.invoke`; the function authorizes the business action, derives recipients and variables, and sends through trusted `ctx.notification`. Do not hardcode notification API URLs or pass effective recipients/content/channels from the page.
137
137
  - For backend business logic shared by pages, automations, or workflows, declare an App Function and call `sdk.function.invoke`; do not duplicate the same multi-form query/connector/notification orchestration in page code.
138
138
  - Before hand-writing mature UI behavior, consult `references/component-guide.md` and use established libraries: platform components for platform data fields, antd/antd-mobile for controls and overlays, ECharts for charts, GSAP for complex animation timelines, and maintained packages such as dnd-kit for drag/drop. Do not rebuild mature controls with raw DOM/native inputs.
139
139
  - Named imports from `@ant-design/icons` are supported by the `openxiangda` workspace build proxy, which enumerates icon module exports at runtime.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openxiangda",
3
- "version": "1.0.270",
3
+ "version": "1.0.271",
4
4
  "description": "OpenXiangda CLI, workspace build tools, runtime SDK, and form components.",
5
5
  "private": false,
6
6
  "bin": {
@@ -233,43 +233,42 @@
233
233
  },
234
234
  "openxiangdaRelease": {
235
235
  "schemaVersion": "openxiangda.release-notes/v1",
236
- "version": "1.0.270",
237
- "title": "OpenXiangda V1 Inbound Webhook",
236
+ "version": "1.0.271",
237
+ "title": "OpenXiangda V1 Legacy Atomic Mixed Releases",
238
238
  "status": "reviewed",
239
- "summary": "V1 应用可以声明公开 Inbound Webhook,由平台可靠接入并转发给固定 App Function,供应商验签和业务处理仍由应用负责。",
240
- "newFeatures": [
241
- "新增 src/resources/webhooks/*.json 资源声明、校验、计划、发布、拉取和停用能力。",
242
- "新增 webhook list/get/create/update/upsert/disable/deliveries/delivery 命令,用于配置和投递诊断。",
243
- "Webhook Function 获得原始 Body、原始 Query、重复参数数组、安全 Headers、投递 ID 和幂等键。"
244
- ],
239
+ "summary": "Legacy PageRelease 应用现在可以在一次受审发布中同时暂存 FormRelease、BackendRelease 和 PageRelease,并通过一个 Root AppRelease 原子激活。",
240
+ "newFeatures": [],
245
241
  "fixes": [
246
- "Function 资源在 Webhook 之前发布,prune 对遗失的 Webhook 执行停用而不是删除审计数据。",
247
- "模板、Skill、命令发现和 API 参考统一说明 rawBody 验签、secretRefs 和业务幂等要求。"
242
+ "Legacy 发布计划不再把 Form 交给 workspace publish 直接处理,而是生成独立的不可变 FormRelease stage。",
243
+ "BackendRelease 在依赖的 staged Form contract 就绪后执行,legacy Page 继续使用 PageRelease,最后只执行一次 app-finalize。",
244
+ "SDD bundle 和 prepublish 校验对 legacy Form、Backend、Page 混合发布使用同一组确定性命令。",
245
+ "Legacy Page step 自动注入 stage-only 环境并记录 PageRelease staged evidence;缺少证据时禁止 Root finalize。",
246
+ "Legacy release publish 的 manifest replacement 权限只透传给唯一的精确 Backend stage。"
248
247
  ],
249
248
  "affectedUsers": [
250
- "需要接收门禁、支付、IoT 或其他第三方主动回调的 OpenXiangda V1 应用开发者。"
249
+ "使用 legacy PageRelease 且同一变更同时包含 Form、Function/Automation 或 Page 的 OpenXiangda V1 应用。"
251
250
  ],
252
251
  "compatibility": {
253
252
  "node": ">=18;全局 V2 统一入口需要 >=24",
254
253
  "workspaceGenerations": [
255
254
  "v1"
256
255
  ],
257
- "workspacePolicy": "现有 V1 资源保持兼容;只有声明 webhooks 的应用会创建公开回调端点。",
258
- "platformPolicy": "必须部署包含 V1 Webhook 管理/公开入口、SQL 迁移和网关规则的平台版本。App Function Secret 必须按部署规范启用。"
256
+ "workspacePolicy": "React SPA 发布计划保持兼容;legacy 混合发布会改用 FormRelease + PageRelease + Root AppRelease 的原子编排。",
257
+ "platformPolicy": "目标平台需已支持 FormRelease、PageRelease、BackendRelease 和 atomic staged children;无需数据库迁移。"
259
258
  },
260
259
  "upgradeSteps": [
261
- "将 V1 项目依赖锁定到 openxiangda@1.0.270 或运行同代 update install。",
262
- "执行 skill install --force 和 skill bootstrap --force,阅读 references/webhooks.md。",
263
- "先发布声明 secretRefs 的 trusted_node_v2 Function,再声明并发布 Webhook。",
264
- "从 .openxiangda/state.json 读取 callbackUrl,配置到供应商并用投递查询命令完成验签、重试和幂等验收。"
260
+ "将 V1 项目依赖锁定到 openxiangda@1.0.271 或运行同代 update install。",
261
+ "对待发布变更重新生成 mainline release bundle,或至少重新执行 sdd verify --changed --stage prepublish。",
262
+ "确认 legacy 计划中的 workspace publish 只包含 pages/*,Form 使用 resource publish form-setting 单独暂存。",
263
+ "自定义 legacy 页面发布脚本必须遵守 OPENXIANGDA_PAGE_STAGE_ONLY=1,并通过当前包内 CLI 登记 staged PageRelease。",
264
+ "继续通过 release publish 执行一次性 Root finalize,不要顺序激活 Form、Backend 或 Page。"
265
265
  ],
266
266
  "knownLimitations": [
267
- "首版公开入口只接受 application/json 的 HTTP POST,单端点 Body 上限不超过 1 MiB。",
268
- "平台不内置供应商签名算法;Function 必须按照供应商正式协议使用 rawBody 验签。",
269
- "投递语义为 at-least-once,应用必须用 input.idempotencyKey 原子保护业务副作用。"
267
+ "1.0.270 生成的错误计划不会被原地信任;必须使用 1.0.271 重新计算并通过 SDD gate。",
268
+ "该补丁不改变网络链路;独立的 ECONNRESET 仍需按请求时间和 ingress 日志排查。"
270
269
  ],
271
270
  "issues": [],
272
- "sha256": "7ec9e6eebb0b509833283274bde2fdfba48a099c76cea862f77a08441a4ef1dd",
273
- "url": "https://github.com/1377385356/openxiangda-v1/releases/tag/v1.0.270"
271
+ "sha256": "86b676d7d8382b8618aad501cd8cf576d95ee0a6c29b8435b9b2edc22b43eee9",
272
+ "url": "https://github.com/1377385356/openxiangda-v1/releases/tag/v1.0.271"
274
273
  }
275
274
  }