openxiangda 1.0.269 → 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.
Files changed (29) hide show
  1. package/README.md +2 -0
  2. package/lib/application-environments.js +17 -0
  3. package/lib/cli.js +720 -78
  4. package/lib/design-gates.js +18 -0
  5. package/lib/release-plan.js +78 -63
  6. package/openxiangda-skills/SKILL.md +3 -1
  7. package/openxiangda-skills/references/notifications.md +28 -23
  8. package/openxiangda-skills/references/openxiangda-api.md +22 -30
  9. package/openxiangda-skills/references/pages/page-sdk.md +1 -1
  10. package/openxiangda-skills/references/resource-manifest-cheatsheet.md +37 -9
  11. package/openxiangda-skills/references/webhooks.md +213 -0
  12. package/openxiangda-skills/skills/openxiangda-core/SKILL.md +1 -1
  13. package/openxiangda-skills/skills/openxiangda-page/SKILL.md +2 -2
  14. package/openxiangda-skills/skills/openxiangda-workflow-automation/SKILL.md +3 -0
  15. package/package.json +24 -23
  16. package/packages/sdk/dist/runtime/index.cjs +39 -39
  17. package/packages/sdk/dist/runtime/index.mjs +39 -39
  18. package/packages/sdk/dist/runtime/react.cjs +39 -39
  19. package/packages/sdk/dist/runtime/react.d.mts +3 -0
  20. package/packages/sdk/dist/runtime/react.d.ts +3 -0
  21. package/packages/sdk/dist/runtime/react.mjs +46 -46
  22. package/packages/sdk/src/build-source/scripts/publish-all.mjs +33 -2
  23. package/packages/sdk/src/build-source/scripts/register.mjs +161 -1
  24. package/templates/openxiangda-react-spa/.cursor/rules/openxiangda-resources.mdc +1 -0
  25. package/templates/openxiangda-react-spa/.qoder/rules/openxiangda-resources.md +1 -0
  26. package/templates/openxiangda-react-spa/AGENTS.md +4 -0
  27. package/templates/sy-lowcode-app-workspace/.cursor/rules/openxiangda-resources.mdc +2 -0
  28. package/templates/sy-lowcode-app-workspace/.qoder/rules/openxiangda-resources.md +1 -0
  29. package/templates/sy-lowcode-app-workspace/AGENTS.md +3 -1
@@ -0,0 +1,213 @@
1
+ # OpenXiangda 1.x Inbound Webhooks
2
+
3
+ Inbound Webhook 用于让门禁、支付、IoT 等外部系统主动调用 OpenXiangda
4
+ 应用。平台只负责通用接入:接收和保存原始请求、限制大小、按幂等键串行化、
5
+ 调用唯一绑定的 App Function,并把 Function 的处理结果映射为稳定 HTTP 状态。
6
+ 供应商签名规则、事件筛选和业务写入始终属于应用 Function。
7
+
8
+ ## 声明 Webhook
9
+
10
+ 在 `src/resources/webhooks/<code>.json` 创建 Manifest:
11
+
12
+ ```json
13
+ {
14
+ "code": "yuquan_access",
15
+ "name": "玉泉门禁开门事件",
16
+ "description": "接收 REC_SUCCESS 事件并匹配琴房预约",
17
+ "targetFunctionCode": "qfyy_access_event",
18
+ "idempotencyQueryParam": "nonce",
19
+ "maxBodyBytes": 262144,
20
+ "status": "active"
21
+ }
22
+ ```
23
+
24
+ - `targetFunctionCode` 必须是同一租户、同一应用内已经启用的 Function。
25
+ - `idempotencyQueryParam` 默认是 `nonce`。该参数为空时,平台使用请求摘要。
26
+ - `maxBodyBytes` 默认 256 KiB,允许范围为 1 KiB 到 1 MiB。
27
+ - 平台网关对公开入口统一限制 1 MiB,并按来源 IP 限制 100 请求/秒、突发
28
+ 200;超限返回 429。该限制不代替验签和业务幂等。
29
+ - Manifest 不得包含 Webhook Secret、签名算法或供应商凭据。
30
+ - 发布后从 `.openxiangda/state.json` 的
31
+ `resources.webhooks.<code>.callbackUrl` 取得完整回调地址;`callbackPath` 是相对
32
+ 当前 profile API base(通常为 `<origin>/service`)的路径。不要自行用 `appType`
33
+ 拼接公开 URL。
34
+
35
+ ## 声明和配置 Secret
36
+
37
+ Webhook Secret 使用现有 App Function Secret。Function 必须使用
38
+ `function_v2 + trusted_node_v2`,并在 Manifest 顶层声明引用:
39
+
40
+ ```json
41
+ {
42
+ "code": "qfyy_access_event",
43
+ "name": "处理玉泉门禁事件",
44
+ "secretRefs": [
45
+ { "name": "yuquan_webhook_secret", "required": true }
46
+ ],
47
+ "definitionJson": {
48
+ "version": "function_v2",
49
+ "runtimeMode": "trusted_node",
50
+ "runtimeContractVersion": "trusted_node_v2",
51
+ "timeoutMs": 30000,
52
+ "sourceFile": {
53
+ "localPath": "src/functions/qfyy_access_event/index.ts"
54
+ }
55
+ },
56
+ "status": "active"
57
+ }
58
+ ```
59
+
60
+ Webhook Function 的 `timeoutMs` 必须在 1 到 120000 毫秒之间。平台在配置和
61
+ 每次调用时都会重新校验运行时契约,Function 后续被禁用、降级为旧运行时或把
62
+ 超时调到上限之外时,Webhook 会返回可重试的非 2xx,而不会以宽权限身份执行。
63
+
64
+ Secret 值只通过隐藏输入或标准输入写入平台:
65
+
66
+ ```bash
67
+ openxiangda secret create yuquan_webhook_secret \
68
+ --value-stdin --change <change-id> --profile <name>
69
+ ```
70
+
71
+ 禁止把值写进 Webhook/Function Manifest、源码、`.env`、日志、异常或函数返回值。
72
+
73
+ ## Function 输入
74
+
75
+ Function 的第二个参数和 `ctx.input` 都是以下对象:
76
+
77
+ ```ts
78
+ interface WebhookInput {
79
+ deliveryId: string;
80
+ idempotencyKey: string;
81
+ webhookCode: string;
82
+ receivedAt: string;
83
+ rawBody: string;
84
+ rawBodyBase64: string;
85
+ rawQueryString: string;
86
+ query: Record<string, string[]>;
87
+ headers: Record<string, string[]>;
88
+ body: unknown;
89
+ request: { method: "POST"; path: string };
90
+ }
91
+ ```
92
+
93
+ `query` 保留重复参数及其顺序。`rawBody` 是签名使用的原始 UTF-8 文本;
94
+ `body` 只用于验签成功后的业务解析。平台会从 `headers` 中移除 Authorization、
95
+ Cookie、代理凭据和客户端证书头。
96
+
97
+ ## 验签顺序
98
+
99
+ 验签、时间窗和事件来源检查必须发生在任何 `ctx.form`、`ctx.dataView`、
100
+ `ctx.connector`、`ctx.notification`、`ctx.platform` 或 `ctx.utils.http` 调用之前。
101
+ 永远不要对 `input.body` 再 `JSON.stringify` 后验签,空格、字段顺序或转义变化会
102
+ 使签名失真。
103
+
104
+ 下面展示 HMAC-SHA1 和常量时间比较。`buildCanonicalText` 只是结构示例;必须按
105
+ 供应商正式文档确认字段顺序、分隔符、URL 编码和 hex/base64 输出格式:
106
+
107
+ ```ts
108
+ import { createHmac, timingSafeEqual } from "node:crypto";
109
+ import type { AppFunctionContextV2 } from "openxiangda/runtime";
110
+
111
+ type WebhookInput = {
112
+ deliveryId: string;
113
+ idempotencyKey: string;
114
+ rawBody: string;
115
+ query: Record<string, string[]>;
116
+ body: unknown;
117
+ };
118
+
119
+ function first(query: Record<string, string[]>, name: string) {
120
+ return String(query[name]?.[0] || "");
121
+ }
122
+
123
+ function equalEncodedSignature(actual: string, expected: string) {
124
+ const left = Buffer.from(actual.trim().toLowerCase(), "utf8");
125
+ const right = Buffer.from(expected.trim().toLowerCase(), "utf8");
126
+ return left.length === right.length && timingSafeEqual(left, right);
127
+ }
128
+
129
+ function buildCanonicalText(input: WebhookInput) {
130
+ // Replace this illustrative order with the provider's exact contract.
131
+ return [
132
+ first(input.query, "nonce"),
133
+ first(input.query, "timestamp"),
134
+ first(input.query, "orgId"),
135
+ input.rawBody,
136
+ ].join("");
137
+ }
138
+
139
+ export default async function handleWebhook(
140
+ ctx: AppFunctionContextV2,
141
+ input: WebhookInput,
142
+ ) {
143
+ const secret = await ctx.secrets.get("yuquan_webhook_secret");
144
+ const expected = createHmac("sha1", secret)
145
+ .update(buildCanonicalText(input), "utf8")
146
+ .digest("hex");
147
+ const actual = first(input.query, "signature");
148
+ if (!actual || !equalEncodedSignature(actual, expected)) {
149
+ return { webhook: { outcome: "rejected", status: 401 } };
150
+ }
151
+
152
+ const timestamp = Number(first(input.query, "timestamp"));
153
+ if (!Number.isFinite(timestamp) || Math.abs(Date.now() - timestamp) > 5 * 60_000) {
154
+ return { webhook: { outcome: "rejected", status: 401 } };
155
+ }
156
+
157
+ const event = input.body as {
158
+ callbackTag?: string;
159
+ data?: {
160
+ isAuth?: boolean;
161
+ deviceSn?: string;
162
+ jobNum?: string;
163
+ recognizeTime?: string;
164
+ recognizeType?: number;
165
+ memberId?: string;
166
+ personType?: number;
167
+ };
168
+ };
169
+ if (event.callbackTag !== "REC_SUCCESS" || event.data?.isAuth !== true) {
170
+ return { webhook: { outcome: "ignored" } };
171
+ }
172
+
173
+ // The application must atomically claim input.idempotencyKey in its own
174
+ // business record before creating/updating reservation data.
175
+ await processAccessEventIdempotently(ctx, input.idempotencyKey, event.data);
176
+ return { webhook: { outcome: "accepted" } };
177
+ }
178
+ ```
179
+
180
+ `processAccessEventIdempotently` 是应用函数中的业务实现,不是平台内置 API。应把
181
+ `idempotencyKey` 写入带唯一约束或等价原子保护的业务记录,然后再执行副作用。
182
+ 平台提供 at-least-once 投递;如果 Function 已产生副作用,但进程在成功回执落库前
183
+ 中断,同一事件会再次执行。
184
+
185
+ ## 返回契约
186
+
187
+ - 普通返回,或 `{ webhook: { outcome: "accepted" } }`:HTTP 200。
188
+ - `{ webhook: { outcome: "ignored" } }`:HTTP 200,适合已验签但无需处理的事件。
189
+ - `{ webhook: { outcome: "rejected", status: 400|401|403|409|422 } }`:指定 4xx。
190
+ - `{ webhook: { outcome: "retry" } }` 或抛出异常:HTTP 503,通知供应商重试。
191
+
192
+ 平台不会把 Function 返回值、日志、异常详情或 Secret 回显给外部调用方。
193
+ 投递原始请求和审计信息默认保留 180 天,由平台部署环境统一配置;应用不应把
194
+ Webhook 投递表当作永久业务档案,需要长期保存的字段应在验签后写入自己的表单。
195
+
196
+ ## 校验、发布和诊断
197
+
198
+ ```bash
199
+ openxiangda resource validate webhook --profile <name>
200
+ openxiangda resource plan webhook --only yuquan_access --profile <name> --json
201
+ openxiangda resource publish webhook --only yuquan_access \
202
+ --change <change-id> --profile <name>
203
+
204
+ openxiangda webhook list --profile <name> --json
205
+ openxiangda webhook get yuquan_access --profile <name> --json
206
+ openxiangda webhook deliveries yuquan_access --profile <name> --json
207
+ openxiangda webhook delivery yuquan_access <delivery-id> --profile <name> --json
208
+ openxiangda webhook disable yuquan_access \
209
+ --change <change-id> --profile <name>
210
+ ```
211
+
212
+ 先发布并验证 Function,再启用 Webhook。新 Function 与 Webhook 同轮交付时,可以
213
+ 先以 `status: "disabled"` 发布 Webhook,验证 Function 后再把状态改为 `active`。
@@ -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.
@@ -105,6 +105,8 @@ JS_CODE is the backend execution escape hatch for workflow and automation. Use i
105
105
 
106
106
  App Function is the reusable backend execution model. Use it when the logic should be called by custom pages, multiple automations, workflows, or the runtime API. Source lives in `src/functions/<functionCode>/index.ts`; manifest lives in `src/resources/functions/<functionCode>.json`. Call it from pages with `sdk.function.invoke(code, { input })`, from graph definitions with `function_call`, or from the runtime endpoint `/:appType/v1/functions/:code/invoke.json`. Direct runtime invocation defaults to app automation management permission; ordinary page callers must declare `definitionJson.runtimeInvoke.audience` (`authenticated`, `page_permission_group`, `app_roles`, or `scope_policy`). Prefer `export default async function(ctx, input) {}` for App Function source; the second argument is the invoke input and the same value is available as `ctx.input`. Current MVP exposes controlled helpers only and does not expose raw SQL or Redis. App Functions and trusted-node Automation scripts can call `ctx.files.readAsBase64` with an attachment from the current `ctx.formData`, or with a server-verified form record/field reference. This helper reads platform storage without a browser session, rejects arbitrary URLs and non-images, caps files at 10 MiB, and must be used without logging or persisting the Base64. App Functions can call `ctx.process.startFromExistingInstance`, `resolveCapabilities`, `resubmitTask`, `withdraw`, and `transferTask`; published trusted code may access resources in its current tenant/application, while workflow/task authorization, audit, event, replay, cross-app, and cross-tenant boundaries remain enforced. `resources` is optional mapping/audit metadata rather than an application-internal authorization list. Keep internal forms closed to direct user submit unless the business explicitly needs raw form submission; use `runtimeWrite.mode="function_only"` for function-only forms.
107
107
 
108
+ For inbound third-party callbacks, declare `src/resources/webhooks/<code>.json` with a fixed `targetFunctionCode`; do not expose `appType` as a public routing identifier and do not put provider verification fields or Secret values in the Webhook manifest. The Function must use `input.rawBody` for signature verification before any data/helper/network call, read only top-level declared `secretRefs`, and use `input.idempotencyKey` to protect business writes because delivery is at-least-once. Read `references/webhooks.md` for the complete envelope, HMAC example, response directives, publish commands, and delivery diagnosis.
109
+
108
110
  For third-party credentials, use top-level metadata-only `secretRefs` plus `definitionJson.version="function_v2"` and `runtimeContractVersion="trusted_node_v2"`; read a declared value with `await ctx.secrets.get(name)` and call public business APIs through `ctx.utils.http`. Never use `process.env` for platform secrets. Values are managed with `openxiangda secret create|rotate --value-stdin --change <change> --profile <name>` and never appear in Git, build output, snapshots, plan diffs, logs, exceptions, or traces. Local tests may use `function test --secret-from-env logical=ENV` only; the value is passed to an isolated child over stdin and is never written to workspace/cache/state. For a Root App transaction, use exact-scope `resource publish function --only <code> --stage-only` and include its verified `stagedResource` in `release app-finalize`.
109
111
 
110
112
  For new AI-authored automations, prefer code-first `automation_code_ts` resources instead of visual v3 graph definitions. Put the source in `src/automations/<resourceCode>/index.ts`, define `definition.code.json` with `kind: "automation_code_ts"`, and provide `preview.json` for read-only frontend display. Use `ctx.logger.debug/info/warn/error(message, data?)` at every important step; OpenXiangda can inspect logs with `automation executions`, `automation logs`, and `automation diagnose`.
@@ -199,3 +201,4 @@ Use `automation disable` before risky edits. Published automations create a draf
199
201
  - Notification resources and runtime calls: `references/notifications.md`
200
202
  - API fields: `references/openxiangda-api.md`
201
203
  - Profile-isolated IDs: `references/workspace-state.md`
204
+ - Inbound Webhook declaration, verification, idempotency, and delivery diagnosis: `references/webhooks.md`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openxiangda",
3
- "version": "1.0.269",
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": {
@@ -68,7 +68,7 @@
68
68
  "source:admin-list": "node scripts/sync-admin-list-source.mjs",
69
69
  "check": "node --check bin/openxiangda.js && node --check lib/*.js && node --check packages/sdk/src/build-source/src/cli.mjs && node --check packages/sdk/src/build-source/scripts/*.mjs && node --check packages/sdk/src/build-source/scripts/utils/*.mjs",
70
70
  "test": "npm run test:fast",
71
- "test:fast": "npm run check && node scripts/delivery-v2-package-smoke.mjs && node scripts/delivery-v2-executor-smoke.mjs && node scripts/delivery-v2-release-conformance-smoke.mjs && node scripts/http-proxy-smoke.mjs && node scripts/form-export-cli-smoke.mjs && node scripts/open-api-cli-smoke.mjs && node scripts/release-plan-smoke.mjs && node scripts/typed-resource-plan-smoke.mjs && node scripts/integration-bundle-smoke.mjs && node scripts/content-cache-smoke.mjs && node scripts/dependency-capsule-smoke.mjs && node scripts/sdd-stages-smoke.mjs && node scripts/environment-swap-cli-smoke.mjs && node scripts/environment-policy-cli-smoke.mjs",
71
+ "test:fast": "npm run check && node scripts/delivery-v2-package-smoke.mjs && node scripts/delivery-v2-executor-smoke.mjs && node scripts/delivery-v2-release-conformance-smoke.mjs && node scripts/http-proxy-smoke.mjs && node scripts/form-export-cli-smoke.mjs && node scripts/open-api-cli-smoke.mjs && node scripts/release-plan-smoke.mjs && node scripts/typed-resource-plan-smoke.mjs && node scripts/webhook-resource-cli-smoke.mjs && node scripts/integration-bundle-smoke.mjs && node scripts/content-cache-smoke.mjs && node scripts/dependency-capsule-smoke.mjs && node scripts/sdd-stages-smoke.mjs && node scripts/environment-swap-cli-smoke.mjs && node scripts/environment-policy-cli-smoke.mjs",
72
72
  "test:changed": "npm run check && node scripts/run-test-suite.mjs --changed --evidence",
73
73
  "test:contract": "npm run check && node scripts/run-test-suite.mjs --contract --evidence",
74
74
  "test:release": "npm run check && node scripts/run-test-suite.mjs --release --evidence",
@@ -80,6 +80,7 @@
80
80
  "test:content-cache": "node scripts/content-cache-smoke.mjs",
81
81
  "test:dependency-capsule": "node scripts/dependency-capsule-smoke.mjs",
82
82
  "test:typed-resource-plan": "node scripts/typed-resource-plan-smoke.mjs",
83
+ "test:webhook-resource": "node scripts/webhook-resource-cli-smoke.mjs",
83
84
  "test:integration-bundle": "node scripts/integration-bundle-smoke.mjs",
84
85
  "prepublishOnly": "npm run release:evidence:verify && node scripts/release-mainline-guard.mjs",
85
86
  "prepack": "npm run source:admin-list && npm run build:sdk",
@@ -232,42 +233,42 @@
232
233
  },
233
234
  "openxiangdaRelease": {
234
235
  "schemaVersion": "openxiangda.release-notes/v1",
235
- "version": "1.0.269",
236
- "title": "OpenXiangda V1 工作区登录与 V2 升级建议",
236
+ "version": "1.0.271",
237
+ "title": "OpenXiangda V1 Legacy Atomic Mixed Releases",
237
238
  "status": "reviewed",
238
- "summary": "V1 平台登录态按工作区隔离,停止读取和合并全局 profiles;版本、升级与技能刷新入口展示 V2 采用建议。",
239
- "newFeatures": [
240
- "V1 人类可读命令展示 V2 推荐与迁移评估入口,JSON 结果提供结构化 migrationAdvice。",
241
- "login、auth 和 platform 支持 --cwd 指定登录态所属工作区。"
242
- ],
239
+ "summary": "Legacy PageRelease 应用现在可以在一次受审发布中同时暂存 FormRelease、BackendRelease 和 PageRelease,并通过一个 Root AppRelease 原子激活。",
240
+ "newFeatures": [],
243
241
  "fixes": [
244
- "登录、刷新与退出读写工作区 .openxiangda/profiles.json,避免不同项目覆盖同一全局账号。",
245
- "会话对象固定来源文件,切换目录后仍写回原工作区;登录文件原子保存、权限 0600 并自动忽略 Git 跟踪。",
246
- "新建工作区可保留受管登录文件;Git 基线只读检查使用隔离的工作区凭据副本。"
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。"
247
247
  ],
248
248
  "affectedUsers": [
249
- "维护 V1 应用、在多个工作区使用不同平台或账号的开发者。"
249
+ "使用 legacy PageRelease 且同一变更同时包含 Form、Function/Automation 或 Page 的 OpenXiangda V1 应用。"
250
250
  ],
251
251
  "compatibility": {
252
252
  "node": ">=18;全局 V2 统一入口需要 >=24",
253
253
  "workspaceGenerations": [
254
254
  "v1"
255
255
  ],
256
- "workspacePolicy": "原全局 profiles 保留但不再读取,升级后每个项目必须重新登录;已有工作区 profiles 可直接使用。",
257
- "platformPolicy": "不改变平台身份、授权协议或生产应用。OSS 环境配置仍由原全局 .env 管理。"
256
+ "workspacePolicy": "React SPA 发布计划保持兼容;legacy 混合发布会改用 FormRelease + PageRelease + Root AppRelease 的原子编排。",
257
+ "platformPolicy": "目标平台需已支持 FormRelease、PageRelease、BackendRelease 和 atomic staged children;无需数据库迁移。"
258
258
  },
259
259
  "upgradeSteps": [
260
- "将项目依赖锁定到 openxiangda@1.0.269 或运行同代 update install。",
261
- "进入项目重新运行 openxiangda login <platform-url>;新项目先用 --cwd <directory> 指定目标目录登录。",
262
- "执行 skill install --force 和 skill bootstrap --force 刷新技能与工作区指引,再运行项目检查。",
263
- "使用 Node.js 24 的统一入口查看 V2 迁移建议并执行 migrate assess,按评估结果规划应用迁移。"
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。"
264
265
  ],
265
266
  "knownLimitations": [
266
- "不会自动复制全局或其他项目的令牌,也不会自动迁移 V1 应用到 V2。",
267
- "同一工作区并发登录或刷新仍受平台令牌有效性约束。"
267
+ "1.0.270 生成的错误计划不会被原地信任;必须使用 1.0.271 重新计算并通过 SDD gate。",
268
+ "该补丁不改变网络链路;独立的 ECONNRESET 仍需按请求时间和 ingress 日志排查。"
268
269
  ],
269
270
  "issues": [],
270
- "sha256": "112d003eb255dbdf9b221e6ec16c19fefe4a6e164bff22854e6128ad62df5d01",
271
- "url": "https://github.com/1377385356/openxiangda-v1/releases/tag/v1.0.269"
271
+ "sha256": "86b676d7d8382b8618aad501cd8cf576d95ee0a6c29b8435b9b2edc22b43eee9",
272
+ "url": "https://github.com/1377385356/openxiangda-v1/releases/tag/v1.0.271"
272
273
  }
273
274
  }