openxiangda 1.0.261 → 1.0.262

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.
@@ -335,6 +335,7 @@ function discoverResourceInventory(workspaceRoot, files) {
335
335
  workflow: new Map(),
336
336
  };
337
337
  const formPermissionGroupForms = new Map();
338
+ const automationSourceFilesByCode = new Map();
338
339
  const addFingerprint = (bucket, code, file) => {
339
340
  if (!code || !file) return;
340
341
  if (!bucket.has(code)) bucket.set(code, new Map());
@@ -396,6 +397,13 @@ function discoverResourceInventory(workspaceRoot, files) {
396
397
  ) {
397
398
  continue;
398
399
  }
400
+ if (prefix === 'src/automations/') {
401
+ if (!automationSourceFilesByCode.has(code)) {
402
+ automationSourceFilesByCode.set(code, []);
403
+ }
404
+ automationSourceFilesByCode.get(code).push(file);
405
+ continue;
406
+ }
399
407
  bucket.add(code);
400
408
  addFingerprint(fingerprintBucket, code, file);
401
409
  }
@@ -497,6 +505,15 @@ function discoverResourceInventory(workspaceRoot, files) {
497
505
  }
498
506
  }
499
507
  }
508
+ // Automation source directories may contain shared TypeScript helpers such
509
+ // as src/automations/_shared. Only a resource manifest declares a deployable
510
+ // automation; source files belonging to a declared code still participate in
511
+ // that automation's fingerprint and transitive dependency expansion.
512
+ for (const code of inventory.automations) {
513
+ for (const file of automationSourceFilesByCode.get(code) || []) {
514
+ addFingerprint(fingerprints.automations, code, file);
515
+ }
516
+ }
500
517
  for (const bucket of [
501
518
  fingerprints.forms,
502
519
  fingerprints.functions,
@@ -32,7 +32,7 @@ Notification manifests live under `src/resources/notifications/` and contain `te
32
32
 
33
33
  Data view manifests live under `src/resources/data-views/` and define read-only joined or aggregate query resources. Use `storageMode: "materialized"` for refreshed lists/reports where lag is acceptable, and `storageMode: "live"` for bounded real-time query shapes. Use row views with `sdk.dataView.query` and aggregate views with `sdk.dataView.stats`. Before generating one, confirm freshness tolerance, query bounds, and indexes for materialized filters/sort fields/dimensions/date buckets. Do not use them for single-form CRUD, simple linkedForm selects, writes, write-back, or ad-hoc BI. See `data-views.md` before generating one.
34
34
 
35
- App Function manifests live under `src/resources/functions/`, with source in `src/functions/<functionCode>/index.ts`. Use them for reusable server-side logic that pages, automations, and workflows can share through `sdk.function.invoke` or `function_call` nodes. App Functions expose controlled runtime helpers such as `ctx.resources`, `ctx.form.queryOne/queryMany/getById/createOne/updateOne/updateById`, `ctx.process.startFromExistingInstance/resolveCapabilities/resubmitTask/withdraw/transferTask`, `ctx.dataView`, `ctx.connector`, `ctx.notification`, `ctx.organization`, `ctx.platform.roles`, and `ctx.platform.api`; they do not expose raw SQL or Redis in the current MVP. Published trusted code may use all application-owned Forms/DataViews in the current tenant/app. Function `resources` remains optional code-to-ID mapping and audit metadata, while cross-app/cross-tenant access stays forbidden. Use `ctx.platform.roles.list/findByCode/addUsers/removeUser` for app role lookup and membership changes; raw `ctx.platform.api` returns an HTTP response plus platform envelope. Runtime page invocations also expose trusted role context on `ctx.operator.roleCodes`, `ctx.operator.currentRoleCode`, `ctx.operator.hasFullAccess`, `ctx.currentUser`, and `ctx.permissions`; use that context for business authorization, and never trust page-submitted role codes for sensitive actions. Page-call grants should use `definitionJson.runtimeInvoke.audience` (`authenticated`, `page_permission_group`, `app_roles`, or `scope_policy`); `roleCodes` is only for current app-role grants and does not support `"*"` or `"all-app-roles"`. App Function form writes are trusted backend operations, not direct page-user submit access to the target form. For internal function-only forms, publish form settings with `runtimeWrite.mode="function_only"` so raw write endpoints are closed. App Function organization writes require `app:organization:manage` on the real operator/audit actor. Use JS_CODE V2 only for node-local workflow/automation scripts.
35
+ App Function manifests live under `src/resources/functions/`, with source in `src/functions/<functionCode>/index.ts`. Use them for reusable server-side logic that pages, automations, and workflows can share through `sdk.function.invoke` or `function_call` nodes. App Functions expose controlled runtime helpers such as `ctx.resources`, `ctx.form.queryOne/queryMany/getById/createOne/updateOne/updateById`, `ctx.process.startFromExistingInstance/resolveCapabilities/resubmitTask/withdraw/transferTask`, `ctx.dataView`, `ctx.connector`, `ctx.notification`, `ctx.organization`, `ctx.platform.roles`, and `ctx.platform.api`; they do not expose raw SQL or Redis in the current MVP. Published trusted code may use all application-owned Forms/DataViews in the current tenant/app. Function `resources` remains optional code-to-ID mapping and audit metadata, while cross-app/cross-tenant access stays forbidden. Use `ctx.platform.roles.list/findByCode/addUsers/removeUser` for app role lookup and membership changes; raw `ctx.platform.api` returns an HTTP response plus platform envelope. Runtime page invocations also expose trusted role context on `ctx.operator.roleCodes`, `ctx.operator.platformRoleCodes`, `ctx.operator.currentRoleCode`, `ctx.operator.hasFullAccess`, `ctx.currentUser`, and `ctx.permissions`; `platformRoleCodes` contains the synchronized platform identities `SCHOOL_GUARDIAN`, `SCHOOL_STUDENT`, and `SCHOOL_TEACHER`, while `roleCodes` contains app roles. Use that context for business authorization, and never trust page-submitted role codes for sensitive actions. Page-call grants should use `definitionJson.runtimeInvoke.audience` (`authenticated`, `page_permission_group`, `app_roles`, `platform_roles`, or `scope_policy`); use `roleCodes` for current app-role grants and `platformRoleCodes` for synchronized identities. App Function form writes are trusted backend operations, not direct page-user submit access to the target form. For internal function-only forms, publish form settings with `runtimeWrite.mode="function_only"` so raw write endpoints are closed. App Function organization writes require `app:organization:manage` on the real operator/audit actor. Use JS_CODE V2 only for node-local workflow/automation scripts.
36
36
 
37
37
  Auth manifests live under `src/resources/auth/`. Use them to enable app-level login methods and bind phone-code/CAS/custom providers to App Functions. Auth provider functions are called only by the platform auth flow. They validate external credentials and return identity assertions such as `phone`, `email`, `externalId`, or `unionId`; they must not issue tokens, set cookies, or mutate platform user/binding tables.
38
38
 
@@ -743,10 +743,16 @@ Body:
743
743
  {
744
744
  "name": "销售可见页面",
745
745
  "roles": ["sales"],
746
+ "platformRoleCodes": ["SCHOOL_TEACHER"],
746
747
  "menuFormUuids": ["FORM_XXX", "MENU_ID_FOR_CODE_PAGE"]
747
748
  }
748
749
  ```
749
750
 
751
+ `platformRoleCodes` is optional. It matches synchronized platform identities in addition to
752
+ `roles` (OR semantics): `SCHOOL_GUARDIAN`, `SCHOOL_STUDENT`, and `SCHOOL_TEACHER`. Both arrays
753
+ empty means unrestricted. These three system roles are maintained by the daily DingTalk
754
+ school-contact sync and cannot be assigned through role-management APIs.
755
+
750
756
  An empty `menuFormUuids` array means all menus/pages are visible to the matched roles. For form menus this field can contain form UUIDs. For custom code page menus, the editable permission group should contain the menu ID, which is what the platform permission editor uses for tree check state. OpenXiangda CLI resolves `--page-codes` / `--menu-codes` to an editable menu-ID group and creates a companion `(运行时别名)` group for required page ID, route key, and legacy `PAGE_...` runtime aliases by default. This keeps the main group editable while satisfying `/view` runtime guards. Use `--no-runtime-aliases` only for deployments whose runtime checks menu IDs directly.
751
757
 
752
758
  ### GET `/apps/:appType/page-permission-groups/:groupId`
@@ -763,7 +769,8 @@ Requires Bearer token. Deletes a page permission group.
763
769
 
764
770
  ### GET `/apps/:appType/page-permission-groups/user-menu-permissions`
765
771
 
766
- Requires Bearer token. Returns the current user's menu visibility summary for the app.
772
+ Requires Bearer token. Returns the current user's menu visibility summary for the app,
773
+ including the synchronized `platformRoleCodes`.
767
774
 
768
775
  ### GET `/apps/:appType/forms/:formUuid/permission-groups`
769
776
 
@@ -780,6 +787,7 @@ Body:
780
787
  "name": "销售查看",
781
788
  "type": "view",
782
789
  "roles": ["sales"],
790
+ "platformRoleCodes": ["SCHOOL_GUARDIAN"],
783
791
  "dataScope": [{ "type": "self" }],
784
792
  "operations": ["view"],
785
793
  "fieldPermissions": [],
@@ -20,6 +20,7 @@ Guidelines:
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.
22
22
  - Use `sdk.organization.schoolContact.*` for synchronized guardian/student relationships. Declare `app:organization:school-contact:read`; it defaults to all relationships in the current tenant. Use the optional `self:read` or `class:read` permission only when the product explicitly needs a narrower data scope. The response intentionally includes platform user ID, mobile, DingTalk userid, and name. Read `references/school-contact-relations.md` before building a family or class page.
23
+ - Synchronized school-contact users also receive platform identity codes: `SCHOOL_GUARDIAN`, `SCHOOL_STUDENT`, and `SCHOOL_TEACHER`. Page permission groups select them with `platformRoleCodes` (additive to app `roles`), and runtime permissions expose them in `permissions.platformRoleCodes`. App Functions can use a `platform_roles` invocation audience and trusted `ctx.operator.platformRoleCodes`; these roles are maintained only by the daily organization sync.
23
24
  - Use `sdk.workCenter.listItems({ boxType })` and `sdk.workCenter.getStats()` for current-user work center lists and counts. `boxType` is one of `todo`, `done`, `cc`, or `initiated`. This is the end-user task/handled/cc/initiated surface; do not build todo pages by reading workflow operation logs, automation logs, or raw process-task tables.
24
25
  - `sdk.form.create` returns identifiers and generated serial number values only. Treat `formInstId` / `formInstanceId` as the required success contract; `serialNumber` / `serialNumbers` are present only when the form has `SerialNumberField`. Do not expect the save response to contain the full row, formula results, or other server-generated field values. Call `sdk.form.getDetail` explicitly after create when those values are needed.
25
26
  - Missing `formInstId` / `formInstanceId` is a real error in strict mode. Do not create fake IDs, mock rows, or fallback display data to hide it.
@@ -189,7 +190,7 @@ App Function source should prefer `export default async function(ctx, input) {}`
189
190
  The second argument is the `input` passed by `sdk.function.invoke`, and the
190
191
  same value is also available as `ctx.input` for compatibility.
191
192
 
192
- Use App Functions when the logic must run server-side and be reusable by pages, automations, or workflows. Runtime invocation requires app automation management permission by default. To let ordinary app users call a function from a page, declare `definitionJson.runtimeInvoke.audience` with `authenticated`, `page_permission_group`, `app_roles`, or `scope_policy`; use `roleCodes` only for current app-role grants. Do not use `"*"` or `"all-app-roles"` as role codes. Inside the function, authorize sensitive actions with trusted runtime context such as `ctx.operator.roleCodes`, `ctx.operator.currentRoleCode`, `ctx.operator.hasFullAccess`, `ctx.currentUser`, or `ctx.permissions`; do not trust role codes sent in the page input. Published trusted code may write any form in its current tenant/application; per-function `resources.forms` is optional mapping and audit metadata, not an authorization allowlist. Do not open direct submit permission on internal forms just for that page action.
193
+ Use App Functions when the logic must run server-side and be reusable by pages, automations, or workflows. Runtime invocation requires app automation management permission by default. To let ordinary app users call a function from a page, declare `definitionJson.runtimeInvoke.audience` with `authenticated`, `page_permission_group`, `app_roles`, `platform_roles`, or `scope_policy`; use `roleCodes` only for current app-role grants and `platformRoleCodes` for synchronized identities. Do not use `"*"` or `"all-app-roles"` as role codes. Inside the function, authorize sensitive actions with trusted runtime context such as `ctx.operator.roleCodes`, `ctx.operator.platformRoleCodes`, `ctx.operator.currentRoleCode`, `ctx.operator.hasFullAccess`, `ctx.currentUser`, or `ctx.permissions`; do not trust role codes sent in the page input. Published trusted code may write any form in its current tenant/application; per-function `resources.forms` is optional mapping and audit metadata, not an authorization allowlist. Do not open direct submit permission on internal forms just for that page action.
193
194
 
194
195
  Server-defined export:
195
196
 
@@ -87,10 +87,16 @@ group so the platform tree can still round-trip the menu selection.
87
87
  {
88
88
  "name": "销售页面",
89
89
  "roles": ["sales"],
90
+ "platformRoleCodes": [],
90
91
  "menuFormUuids": ["FORM_CUSTOMER", "FORM_ORDER", "MENU_ID_FOR_CODE_PAGE"]
91
92
  }
92
93
  ```
93
94
 
95
+ `platformRoleCodes` is optional and is evaluated alongside `roles` using OR semantics. Use the
96
+ daily DingTalk school-contact identity codes `SCHOOL_GUARDIAN`, `SCHOOL_STUDENT`, and
97
+ `SCHOOL_TEACHER`; the platform maintains these system roles and applications must not assign
98
+ them manually. If both arrays are empty, the group is unrestricted.
99
+
94
100
  For custom code pages, keep the editable menu tree targets separate from runtime aliases. The
95
101
  platform permission editor only reliably checks real menu IDs, while `/view` runtime guards on
96
102
  some private deployments still check the code page id, route key, or legacy `PAGE_...` id. When
@@ -129,6 +135,7 @@ Submit group:
129
135
  "name": "销售提交",
130
136
  "type": "submit",
131
137
  "roles": ["sales"],
138
+ "platformRoleCodes": [],
132
139
  "operations": ["submit"]
133
140
  }
134
141
  ```
@@ -140,6 +147,7 @@ View group:
140
147
  "name": "销售查看",
141
148
  "type": "view",
142
149
  "roles": ["sales"],
150
+ "platformRoleCodes": [],
143
151
  "dataScope": [{ "type": "self" }],
144
152
  "operations": ["view", "edit"],
145
153
  "fieldPermissions": [
@@ -614,10 +614,13 @@ export default async function reservationReminderSummary(
614
614
  { headers: { authorization: `Bearer ${providerToken}` } },
615
615
  );
616
616
  const roleCodes = ctx.operator?.roleCodes || ctx.permissions?.roleCodes || [];
617
+ const platformRoleCodes =
618
+ ctx.operator?.platformRoleCodes || ctx.permissions?.platformRoleCodes || [];
617
619
  const canManage =
618
620
  ctx.operator?.hasFullAccess === true ||
619
621
  ctx.permissions?.hasFullAccess === true ||
620
- roleCodes.includes("union_admin");
622
+ roleCodes.includes("union_admin") ||
623
+ platformRoleCodes.includes("SCHOOL_TEACHER");
621
624
  if (!canManage) throw new Error("当前账号无权执行该操作。");
622
625
 
623
626
  const orders = await ctx.form.queryMany({
@@ -678,7 +681,7 @@ const result = await sdk.function.invoke("reservation_reminder_summary", {
678
681
  }
679
682
  ```
680
683
 
681
- 适用边界:可复用后端业务逻辑、跨页面/自动化/流程共享的查询编排、连接器调用、通知编排、受控平台 API 调用。App Function 支持 `ctx.form.queryOne/queryMany/getById/createOne/updateOne/updateById`、`ctx.dataView`、`ctx.connector`、`ctx.notification`、`ctx.platform.roles`、`ctx.platform.api` 等受控 helper,当前 MVP 不暴露原始 SQL/Redis。应用角色查询和成员维护优先使用 `ctx.platform.roles.list/findByCode/addUsers/removeUser`;底层 `ctx.platform.api` 返回 HTTP 包装与平台 envelope,需要自行解包。已发布可信代码可以访问当前租户、当前应用内的资源;function manifest 的 `resources` 是可选映射、审计和影响分析信息,不再是逐函数权限白名单。页面用户仍不能直接提交内部表单,跨应用和跨租户访问仍被拒绝。运行时接口默认需要应用自动化管理权限;普通用户页面要调用时,用 `definitionJson.runtimeInvoke.audience` 声明 `authenticated`、`page_permission_group`、`app_roles` 或 `scope_policy`,不要把 `"*"`、`"all-app-roles"` 写进 `roleCodes`。若表单只能由函数/流程写入,在 `src/resources/settings/forms/<formCode>.json` 设置 `runtimeWrite.mode="function_only"` 关闭原始写入接口。
684
+ 适用边界:可复用后端业务逻辑、跨页面/自动化/流程共享的查询编排、连接器调用、通知编排、受控平台 API 调用。App Function 支持 `ctx.form.queryOne/queryMany/getById/createOne/updateOne/updateById`、`ctx.dataView`、`ctx.connector`、`ctx.notification`、`ctx.platform.roles`、`ctx.platform.api` 等受控 helper,当前 MVP 不暴露原始 SQL/Redis。应用角色查询和成员维护优先使用 `ctx.platform.roles.list/findByCode/addUsers/removeUser`;底层 `ctx.platform.api` 返回 HTTP 包装与平台 envelope,需要自行解包。已发布可信代码可以访问当前租户、当前应用内的资源;function manifest 的 `resources` 是可选映射、审计和影响分析信息,不再是逐函数权限白名单。页面用户仍不能直接提交内部表单,跨应用和跨租户访问仍被拒绝。运行时接口默认需要应用自动化管理权限;普通用户页面要调用时,用 `definitionJson.runtimeInvoke.audience` 声明 `authenticated`、`page_permission_group`、`app_roles`、`platform_roles` 或 `scope_policy`,使用 `roleCodes` 匹配应用角色、使用 `platformRoleCodes` 匹配同步身份 `SCHOOL_GUARDIAN`、`SCHOOL_STUDENT`、`SCHOOL_TEACHER`,不要把 `"*"`、`"all-app-roles"` 写进角色编码。若表单只能由函数/流程写入,在 `src/resources/settings/forms/<formCode>.json` 设置 `runtimeWrite.mode="function_only"` 关闭原始写入接口。
682
685
 
683
686
  ## 5. Workflow — `src/resources/workflows/<code>/workflow.json`(manifest)+ `src/workflows/<code>/workflow.ts`(代码优先)
684
687
 
@@ -23,6 +23,40 @@
23
23
 
24
24
  ## 页面 SDK
25
25
 
26
+ ### 平台内置身份角色
27
+
28
+ 每次成功的钉钉家校通讯录全量同步都会对账并维护三种平台层系统身份角色:
29
+
30
+ | 展示名 | 稳定角色编码 | 含义 |
31
+ | --- | --- | --- |
32
+ | 家长 | `SCHOOL_GUARDIAN` | 在家校成员关系中作为 guardian |
33
+ | 学生 | `SCHOOL_STUDENT` | 在家校成员关系中作为 student |
34
+ | 老师 | `SCHOOL_TEACHER` | 在家校成员关系中作为 teacher |
35
+
36
+ 身份角色可组合,一个平台用户可以同时是老师和家长;角色由同步维护,不能在平台角色管理中手工分配、修改、删除,也不会被当前平台角色切换隐藏。应用权限组把这些编码填入 `platformRoleCodes`,与应用角色 `roles` 是并列的 OR 条件;两个数组都为空仍表示不限制。
37
+
38
+ 运行时页面权限会在 `permissions.platformRoleCodes` 返回当前用户的身份编码,它与 `permissions.roleCodes` 分开:
39
+
40
+ ```ts
41
+ const isGuardian =
42
+ sdk.context.permissions?.platformRoleCodes?.includes("SCHOOL_GUARDIAN")
43
+ ```
44
+
45
+ App Function 的调用受众可以声明平台身份角色,服务端按真实操作者的同步结果校验:
46
+
47
+ ```json
48
+ {
49
+ "runtimeInvoke": {
50
+ "audience": {
51
+ "type": "platform_roles",
52
+ "platformRoleCodes": ["SCHOOL_GUARDIAN", "SCHOOL_TEACHER"]
53
+ }
54
+ }
55
+ }
56
+ ```
57
+
58
+ 函数可信上下文同时提供 `ctx.operator.platformRoleCodes` 和 `ctx.permissions.platformRoleCodes`。不要信任页面输入中的角色编码;需要家庭双方或班级关系时,继续使用下方的 `schoolContact` 关系 SDK。
59
+
26
60
  在 React SPA 或代码页中通过 Page SDK 查询,不要硬编码接口地址:
27
61
 
28
62
  ```ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openxiangda",
3
- "version": "1.0.261",
3
+ "version": "1.0.262",
4
4
  "description": "OpenXiangda CLI, workspace build tools, runtime SDK, and form components.",
5
5
  "private": false,
6
6
  "bin": {