openxiangda 1.0.260 → 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.
Files changed (41) hide show
  1. package/README.md +12 -1
  2. package/lib/delivery-v2-package.js +17 -0
  3. package/lib/skills.js +56 -0
  4. package/openxiangda-skills/SKILL.md +12 -1
  5. package/openxiangda-skills/references/connector-resources.md +1 -1
  6. package/openxiangda-skills/references/openxiangda-api.md +9 -1
  7. package/openxiangda-skills/references/pages/page-sdk.md +5 -1
  8. package/openxiangda-skills/references/permission-design-patterns.md +5 -0
  9. package/openxiangda-skills/references/permissions-settings.md +8 -0
  10. package/openxiangda-skills/references/resource-manifest-cheatsheet.md +5 -2
  11. package/openxiangda-skills/references/school-contact-relations.md +165 -0
  12. package/openxiangda-skills/skills/openxiangda-page/SKILL.md +3 -0
  13. package/package.json +2 -1
  14. package/packages/sdk/dist/runtime/index.cjs +39 -39
  15. package/packages/sdk/dist/runtime/index.d.mts +1 -1
  16. package/packages/sdk/dist/runtime/index.d.ts +1 -1
  17. package/packages/sdk/dist/runtime/index.mjs +29 -29
  18. package/packages/sdk/dist/runtime/react.cjs +50 -50
  19. package/packages/sdk/dist/runtime/react.d.mts +88 -1
  20. package/packages/sdk/dist/runtime/react.d.ts +88 -1
  21. package/packages/sdk/dist/runtime/react.mjs +43 -43
  22. package/templates/openxiangda-react-spa/AGENTS.md +2 -0
  23. package/templates/sy-lowcode-app-workspace/AGENTS.md +2 -0
  24. package/v2/README.md +35 -0
  25. package/v2/skills/manifest.json +45 -0
  26. package/v2/skills/openxiangda-v1-maintenance/SKILL.md +21 -0
  27. package/v2/skills/openxiangda-v1-maintenance/agents/openai.yaml +4 -0
  28. package/v2/skills/openxiangda-v2/SKILL.md +38 -0
  29. package/v2/skills/openxiangda-v2/agents/openai.yaml +4 -0
  30. package/v2/skills/openxiangda-v2-architecture/SKILL.md +29 -0
  31. package/v2/skills/openxiangda-v2-architecture/agents/openai.yaml +4 -0
  32. package/v2/skills/openxiangda-v2-backend/SKILL.md +28 -0
  33. package/v2/skills/openxiangda-v2-backend/agents/openai.yaml +4 -0
  34. package/v2/skills/openxiangda-v2-data-authz/SKILL.md +28 -0
  35. package/v2/skills/openxiangda-v2-data-authz/agents/openai.yaml +4 -0
  36. package/v2/skills/openxiangda-v2-delivery/SKILL.md +30 -0
  37. package/v2/skills/openxiangda-v2-delivery/agents/openai.yaml +4 -0
  38. package/v2/skills/openxiangda-v2-frontend/SKILL.md +21 -0
  39. package/v2/skills/openxiangda-v2-frontend/agents/openai.yaml +4 -0
  40. package/v2/skills/openxiangda-v2-workflow-events/SKILL.md +28 -0
  41. package/v2/skills/openxiangda-v2-workflow-events/agents/openai.yaml +4 -0
package/README.md CHANGED
@@ -3,11 +3,20 @@
3
3
 
4
4
  OpenXiangda is a lightweight CLI and skill package for private low-code platforms.
5
5
 
6
+ ## Two runtime generations
7
+
8
+ OpenXiangda now has two deliberately separate development tracks:
9
+
10
+ - `openxiangda` 1.x maintains existing resource-based applications and its legacy Delivery V2 protocol. Stable applications keep this runtime and are not migrated by a platform upgrade.
11
+ - `v2/` contains the new platform-2.0 application toolchain: `create-openxiangda`, `@openxiangda/cli`, `@openxiangda/mcp`, standard React/NestJS packages, typed configuration, domain skills, and application-level deployment.
12
+
13
+ For a new 2.0 application, start with [`v2/docs/getting-started.md`](v2/docs/getting-started.md). A 2.0 workspace has `openxiangda.config.ts`, `apps/web`, and `apps/server`; it must not use the 1.x resource/SDD publishing commands.
14
+
6
15
  Normal OpenXiangda app development uses platform-user login tokens through `/openxiangda-api/v1`; it does not use AK/SK. External backend and third-party integrations use the separate `openxiangda-open-api` skill, `/dingtalk-api/v1.0`, and a platform-managed AK/SK credential.
7
16
 
8
17
  Private platform routing is fixed: backend APIs are under `/service`, platform management is under `/platform`, and app runtime access is under `/view`. Passing a root domain such as `https://yida.wisejob.cn/` to the CLI is supported; OpenXiangda stores the API base as `https://yida.wisejob.cn/service`.
9
18
 
10
- ## Delivery V2
19
+ ## OpenXiangda 1.x Delivery V2 (maintenance)
11
20
 
12
21
  New workspaces declare `deliveryVersion: 2`. The release flow is intentionally
13
22
  small and deterministic:
@@ -406,6 +415,8 @@ openxiangda form export customer --mode package --profile dev --output ./exports
406
415
 
407
416
  运行时页面读取当前用户信息时,优先使用 `sdk.user.getCurrent<PageUserRecord>()`。用户对象会返回常规组织成员关系 `departments`,也会返回系统维护的所属单位字段 `affiliatedDepartmentId` / `affiliatedDepartment`。`departments` 表示用户真实所在的部门、班级、专业等成员关系;`affiliatedDepartment` 表示业务上用于统计、筛选和展示的归属单位,通常是学院、单位或在源单位缺失时可用的具体部门节点,不用于替代权限部门成员关系。
408
417
 
418
+ 钉钉家校通讯录的家长、学生、教师身份和明确监护关系使用 `sdk.organization.schoolContact.*`(页面)或 `ctx.organization.schoolContact.*`(App Function)。角色声明 `app:organization:school-contact:read` 后默认可查询当前租户全部关系,返回平台用户 ID、手机号、钉钉 userid 和姓名;可选 `:self:read` / `:class:read` 用于收紧。完整示例见 [`docs/dingtalk-school-contact-relations.md`](docs/dingtalk-school-contact-relations.md)。
419
+
409
420
  ```ts
410
421
  import type { PageUserRecord } from "openxiangda/runtime"
411
422
 
@@ -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,
package/lib/skills.js CHANGED
@@ -82,6 +82,62 @@ const SKILL_SPECS = [
82
82
  sourceRelativePath: 'openxiangda-skills/skills/openxiangda-open-api',
83
83
  type: 'subskill',
84
84
  },
85
+ {
86
+ name: 'openxiangda-v2',
87
+ displayName: 'OpenXiangda 2.0',
88
+ shortDescription: '标准 React/NestJS 应用与应用级交付入口。',
89
+ sourceRelativePath: 'v2/skills/openxiangda-v2',
90
+ type: 'subskill',
91
+ },
92
+ {
93
+ name: 'openxiangda-v2-architecture',
94
+ displayName: 'OpenXiangda 2.0 Architecture',
95
+ shortDescription: '设计 2.0 应用边界与类型化平台契约。',
96
+ sourceRelativePath: 'v2/skills/openxiangda-v2-architecture',
97
+ type: 'subskill',
98
+ },
99
+ {
100
+ name: 'openxiangda-v2-frontend',
101
+ displayName: 'OpenXiangda 2.0 Frontend',
102
+ shortDescription: '开发 React、Ant Design 与标准后台页面。',
103
+ sourceRelativePath: 'v2/skills/openxiangda-v2-frontend',
104
+ type: 'subskill',
105
+ },
106
+ {
107
+ name: 'openxiangda-v2-backend',
108
+ displayName: 'OpenXiangda 2.0 Backend',
109
+ shortDescription: '开发平台托管的标准 NestJS 应用后端。',
110
+ sourceRelativePath: 'v2/skills/openxiangda-v2-backend',
111
+ type: 'subskill',
112
+ },
113
+ {
114
+ name: 'openxiangda-v2-data-authz',
115
+ displayName: 'OpenXiangda 2.0 Data and AuthZ',
116
+ shortDescription: '设计 Data API、RBAC 与上下文数据权限。',
117
+ sourceRelativePath: 'v2/skills/openxiangda-v2-data-authz',
118
+ type: 'subskill',
119
+ },
120
+ {
121
+ name: 'openxiangda-v2-workflow-events',
122
+ displayName: 'OpenXiangda 2.0 Workflow and Events',
123
+ shortDescription: '开发 Workflow Kernel v2 与持久事件消费者。',
124
+ sourceRelativePath: 'v2/skills/openxiangda-v2-workflow-events',
125
+ type: 'subskill',
126
+ },
127
+ {
128
+ name: 'openxiangda-v2-delivery',
129
+ displayName: 'OpenXiangda 2.0 Delivery',
130
+ shortDescription: '构建、部署、观察、晋级和回滚完整应用。',
131
+ sourceRelativePath: 'v2/skills/openxiangda-v2-delivery',
132
+ type: 'subskill',
133
+ },
134
+ {
135
+ name: 'openxiangda-v1-maintenance',
136
+ displayName: 'OpenXiangda 1.x Maintenance',
137
+ shortDescription: '不迁移地安全维护稳定 1.x 应用。',
138
+ sourceRelativePath: 'v2/skills/openxiangda-v1-maintenance',
139
+ type: 'subskill',
140
+ },
85
141
  ];
86
142
 
87
143
  function getDefaultCodexSkillsDir(env = process.env) {
@@ -10,7 +10,13 @@ OpenXiangda connects an AI coding workspace to the private low-code platform thr
10
10
 
11
11
  This file is a router and safety card. Read only the one or two subskills selected below; do not load every OpenXiangda reference into the same turn.
12
12
 
13
- ## Delivery V2 is the normal release path
13
+ ## Select the runtime generation first
14
+
15
+ If the workspace contains `openxiangda.config.ts`, `apps/web`, and `apps/server`, it is a platform-2.0 application. Stop this 1.x resource flow and use `$openxiangda-v2` plus its architecture, frontend, backend, data-authz, workflow-events, and delivery skills. The 2.0 CLI operates on one immutable application package and does not use SDD or per-resource publishing.
16
+
17
+ If the workspace contains `app-workspace.config.ts`, forms/pages/resource manifests, or an existing 1.x state directory, continue with this router. Never migrate a stable 1.x application merely because platform 2.0 is available.
18
+
19
+ ## 1.x Delivery V2 is the normal 1.x release path
14
20
 
15
21
  When `app-workspace.config.ts` declares `deliveryVersion: 2`, all later V1 SDD,
16
22
  Git-mainline, candidate/ship, `--change`, and `--only` release rules are
@@ -60,6 +66,7 @@ content/source/parent verification and never overwrites immutable storage.
60
66
  | React/classic page | `openxiangda-page` | inspect the target route/page |
61
67
  | Function, automation, workflow, JS_CODE | `openxiangda-workflow-automation` | inspect the exact resource codes |
62
68
  | Roles, permission groups, data scope | `openxiangda-permission-settings` | produce the permission matrix |
69
+ | DingTalk school-contact guardian/student relationships | `openxiangda-page` plus `references/school-contact-relations.md` | use the platform relationship SDK, defaulting to unrestricted tenant scope |
63
70
  | New app architecture, auth, public access | `openxiangda-architecture-design` plus the relevant domain skill | run the design gate |
64
71
  | External backend/OpenAPI/AK-SK | `openxiangda-open-api` | describe the exact operation |
65
72
 
@@ -184,6 +191,10 @@ The lease is app-level promotion ownership, while worktree ownership prevents tw
184
191
  - Use platform roles, permission groups, public grants, and backend Function checks for authorization; frontend hiding is presentation only.
185
192
  - Keep tokens and secrets out of project files and chat.
186
193
  - Use official public SDK entrypoints and platform form/file components.
194
+ - Read school-contact guardian/student relationships through
195
+ `sdk.organization.schoolContact` or the trusted App Function bridge. Keep the
196
+ default tenant-wide scope unless the product explicitly requires self/class
197
+ restrictions; never infer relations from department membership.
187
198
  - Report platform defects or repeated workarounds with `openxiangda feedback submit --yes` when a configured feedback channel is available.
188
199
  - Run `openxiangda update check --json` once at the start of substantial work, when a version mismatch is suspected, or when the cached check is older than one day. Do not repeat it on every confirmation turn.
189
200
  - Install an available update with `openxiangda update install`; it reuses the npm cache, avoids peer-resolution stalls, emits a heartbeat, and has a bounded timeout. Do not replace it with a bare repeated `npm install -g` unless bootstrapping the CLI for the first time.
@@ -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": [],
@@ -19,6 +19,8 @@ Guidelines:
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.
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.
22
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.
23
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.
24
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.
@@ -188,7 +190,7 @@ App Function source should prefer `export default async function(ctx, input) {}`
188
190
  The second argument is the `input` passed by `sdk.function.invoke`, and the
189
191
  same value is also available as `ctx.input` for compatibility.
190
192
 
191
- 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.
192
194
 
193
195
  Server-defined export:
194
196
 
@@ -251,6 +253,8 @@ await sdk.organization.accounts.resetPassword("alice", {
251
253
 
252
254
  Use `ctx.organization.departments.*` and `ctx.organization.accounts.*` inside App Functions or trusted JS nodes for the same capability. Runtime service principals must resolve a real operator/audit actor; permission is checked against that user, not against the runtime service account. Keep password changes on dedicated endpoints: update account metadata with `update`, reset another user's password with `resetPassword`, and change the current user's password with `changeMyPassword({ oldPassword, newPassword })`.
253
255
 
256
+ For synchronized school-contact relationships, use `sdk.organization.schoolContact.relations.list`, `children.list`, `guardians.list`, or `myFamily.get`; App Functions use the same paths under `ctx.organization.schoolContact`. Do not infer a family relationship from class membership or persist a second relationship master inside application forms. See `references/school-contact-relations.md` for permission codes, filters, response fields, and examples.
257
+
254
258
  Work center:
255
259
 
256
260
  ```ts
@@ -275,6 +275,9 @@ Before implementation, write a matrix with at least these columns:
275
275
  - Query parameters as sensitive authorization.
276
276
  - Frontend-only button hiding or route hiding.
277
277
  - Hardcoded role strings scattered through pages.
278
+ - OR data policies whose role-specific rules omit `roleCodes`; otherwise a
279
+ user's applicant rule can remain active after switching to an administrator
280
+ RoleSession.
278
281
  - Delegated administrators without `app:role:manage` or required permission
279
282
  group management API permissions.
280
283
  - New roles created by delegated administrators without their own
@@ -301,6 +304,8 @@ Before implementation, write a matrix with at least these columns:
301
304
  matched grants, cache state, and final policy for at least one allow and one
302
305
  deny case.
303
306
  - Allowed role paths and denied role paths are both tested.
307
+ - A multi-role user is tested after switching RoleSession, and rules bound to
308
+ the previously selected role no longer contribute to row visibility.
304
309
  - Query parameter tampering does not expand sensitive access.
305
310
  - App Functions that mutate sensitive data verify `ctx.operator.roleCodes`,
306
311
  `ctx.operator.currentRoleCode`, business scope, or explicit public ticket.
@@ -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
 
@@ -0,0 +1,165 @@
1
+ # 家校通讯录关系
2
+
3
+ 家校关系由平台组织中心统一同步和维护。应用不得直连钉钉,也不得根据班级成员自行推断家长和学生关系。
4
+
5
+ ## 权限与默认范围
6
+
7
+ 需要查询家校关系的应用角色在 `src/resources/roles/<code>.json` 的 `apiPermissionCodes` 中声明:
8
+
9
+ ```json
10
+ {
11
+ "apiPermissionCodes": [
12
+ "app:organization:school-contact:read"
13
+ ]
14
+ }
15
+ ```
16
+
17
+ `app:organization:school-contact:read` 默认允许当前租户内全部关系,不按本人或班级限制。只有产品明确要求收紧时,才改用以下权限之一:
18
+
19
+ - `app:organization:school-contact:self:read`:只返回当前操作者作为家长或学生直接参与的关系。
20
+ - `app:organization:school-contact:class:read`:只返回当前操作者以 `teacher` 身份加入的班级关系。
21
+
22
+ 默认全量可见不等于公开访问:跨租户、游客和匿名页面仍然拒绝。不要通过公开访问策略给游客授予家庭关系能力。
23
+
24
+ ## 页面 SDK
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
+
60
+ 在 React SPA 或代码页中通过 Page SDK 查询,不要硬编码接口地址:
61
+
62
+ ```ts
63
+ const response = await sdk.organization.schoolContact.relations.list({
64
+ name: keyword,
65
+ classId,
66
+ page: 1,
67
+ pageSize: 20,
68
+ })
69
+
70
+ const relations = response.result?.items || []
71
+ ```
72
+
73
+ 常用方法:
74
+
75
+ ```ts
76
+ await sdk.organization.schoolContact.children.list(guardianUserId, {
77
+ page: 1,
78
+ pageSize: 20,
79
+ })
80
+
81
+ await sdk.organization.schoolContact.guardians.list(studentUserId, {
82
+ page: 1,
83
+ pageSize: 20,
84
+ })
85
+
86
+ await sdk.organization.schoolContact.myFamily.get({ pageSize: 50 })
87
+ ```
88
+
89
+ 关系列表支持以下服务端过滤条件:
90
+
91
+ - `userId`、`guardianUserId`、`studentUserId`:平台用户 ID。
92
+ - `dingtalkUserId`:钉钉 userid。
93
+ - `mobile`:精确手机号。
94
+ - `name`:家长或学生姓名。
95
+ - `classId`:平台家校班级 ID 或钉钉教育 `class_id`。
96
+ - `role`:`guardian` 或 `student`,用于限定通用人员过滤作用在哪一端。
97
+ - `relationCode`、`page`、`pageSize`;`pageSize` 最大为 100。
98
+
99
+ ## App Function
100
+
101
+ App Function、Automation 或 Workflow 中需要复用关系时,调用对应的受控后端桥接:
102
+
103
+ ```ts
104
+ export default async function(ctx, input) {
105
+ const result = await ctx.organization.schoolContact.relations.list({
106
+ guardianUserId: input.guardianUserId,
107
+ page: 1,
108
+ pageSize: 20,
109
+ })
110
+
111
+ return result.items
112
+ }
113
+ ```
114
+
115
+ 对应方法为:
116
+
117
+ ```ts
118
+ ctx.organization.schoolContact.relations.list(params)
119
+ ctx.organization.schoolContact.children.list(userId, params)
120
+ ctx.organization.schoolContact.guardians.list(userId, params)
121
+ ctx.organization.schoolContact.myFamily.get(params)
122
+ ```
123
+
124
+ 运行时使用平台提供的真实操作者做权限校验。不要信任页面 input 中伪造的租户、角色或当前用户 ID。
125
+
126
+ ## 返回结构
127
+
128
+ ```ts
129
+ interface SchoolContactRelation {
130
+ relationId: string
131
+ relationCode: string | null
132
+ relationName: string | null
133
+ guardian: {
134
+ userId: string
135
+ dingtalkUserId: string
136
+ name: string
137
+ mobile: string | null
138
+ }
139
+ student: {
140
+ userId: string
141
+ dingtalkUserId: string
142
+ name: string
143
+ mobile: string | null
144
+ }
145
+ class: {
146
+ id: string
147
+ dingtalkClassId: string
148
+ name: string
149
+ campusName: string | null
150
+ periodName: string | null
151
+ gradeName: string | null
152
+ }
153
+ syncedAt: string
154
+ syncState: "current" | "not_synced" | "disabled"
155
+ }
156
+ ```
157
+
158
+ 手机号可能为空。`sync.state=disabled` 表示管理员关闭了后续同步,现有数据仍保留但可能过期;业务页面在对时效敏感时应展示 `lastSuccessfulSyncAt`。
159
+
160
+ ## 持久化边界
161
+
162
+ - 平台关系是当前事实,不要复制全量关系到应用表单或 Data View。
163
+ - 业务单据确实需要历史证据时,只保存 `relationId`、双方平台用户 ID 和当时的最小显示快照。
164
+ - 不保存或依赖 `unionid`、同步原文、行业部门 ID,也不要假设行业 `dept_id` 等于教育 `class_id`。
165
+ - 所有列表保持分页;不要拉取全部关系后在浏览器过滤。
@@ -36,6 +36,7 @@ openxiangda workspace publish --profile <name> --page <pageCode>
36
36
  - ✅ For external APIs use `src/resources/connectors/<code>.json` + `sdk.connector.invoke()`; for joined read-only lists use row data views + `sdk.dataView.query`; for stable dashboard metrics use aggregate data views + `sdk.dataView.stats`.
37
37
  - ✅ Choose data view `storageMode` deliberately: `materialized` for refreshed reports/lists that tolerate delay, `live` for bounded real-time multi-form reads.
38
38
  - ✅ For reusable backend orchestration use App Function (`src/functions/<functionCode>/index.ts` + `src/resources/functions/<functionCode>.json`) and call `sdk.function.invoke(code, { input })`; keep page components out of multi-table business orchestration.
39
+ - ✅ 家长、学生、教师身份或监护关系页面使用 `sdk.organization.schoolContact.*`;先读 `references/school-contact-relations.md`,声明家校关系读取权限并保持服务端分页。
39
40
  - ✅ Filters, search bars, modal forms, drawers, and inline edits use platform components for platform data fields, otherwise `antd` / `antd-mobile` controls.
40
41
  - ❌ Single-file giant pages. Split per `references/best-practices.md`.
41
42
  - ❌ Hardcoded `/view/...&isRenderNav=false` URLs scattered through page code; use the runtime navigation helper.
@@ -114,6 +115,7 @@ Read these references only when editing page code:
114
115
  - `references/pages/app-shell.md` — formal backend / PC portal / mobile portal entry pattern. Read before creating any user-facing main entry or admin console.
115
116
  - `references/best-practices.md` — initialized examples for modular pages, status lifecycles, role governance, high-performance queries, portal shells, and interactive workbenches. Read before scaffolding complex pages or data management pages.
116
117
  - `references/pages/page-sdk.md`
118
+ - `references/school-contact-relations.md` — 家校关系权限、分页查询、返回字段以及 Page SDK/App Function 示例。创建家长、学生或班级关系页面前读取。
117
119
  - `references/notifications.md` — notification resources and `sdk.notification` usage. Read before adding reminders, alerts, or message templates to a page.
118
120
  - `references/pages/publish-flow.md`
119
121
  - `references/style-system.md` — style isolation defaults, Tailwind/CSS guidance, and legacy namespace compatibility. Read before writing substantial page CSS or Tailwind classes.
@@ -130,6 +132,7 @@ Read these references only when editing page code:
130
132
  - All visible page copy must be end-user-facing business text. Do not put developer explanations, implementation notes, schema descriptions, or "this module is generated by..." text into sections, cards, alerts, empty states, tooltips, or helper copy.
131
133
  - Store live `pageId`, `routeKey`, and `legacyFormUuid` under the current profile only.
132
134
  - Use `openxiangda/runtime` for platform data access instead of hardcoding backend URLs in page code.
135
+ - 家校关系必须通过 `sdk.organization.schoolContact` / `ctx.organization.schoolContact` 读取;不要直连钉钉、查询系统表或用同班成员推断亲属关系。
133
136
  - For reminders, alerts, and business messages, declare `src/resources/notifications/` first and call `sdk.notification`; do not hardcode notification API URLs.
134
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.
135
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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openxiangda",
3
- "version": "1.0.260",
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": {
@@ -55,6 +55,7 @@
55
55
  "bin/",
56
56
  "lib/",
57
57
  "openxiangda-skills/",
58
+ "v2/skills/",
58
59
  "policy/",
59
60
  "templates/",
60
61
  "packages/sdk/bin/",