openxiangda-skill-kit 2.0.0-alpha.39 → 2.0.0-alpha.41

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.
@@ -0,0 +1,10 @@
1
+ # School-contact head-teacher contract v2
2
+
3
+ - Problem evidence: DingTalk returns head-teacher status only as `teacher` membership `feature.is_adviser`; the platform stores it but the Native school-contact API currently exposes only guardian/student relations and the three broad platform identities.
4
+ - Capability owner: the platform organization service remains the sole owner of synchronized membership facts and `platformRoleCodes`. A 2.0 application reads them through the typed NestJS platform client and exposes its own App API; React never calls the platform endpoint directly.
5
+ - Stable invariants: `SCHOOL_HEAD_TEACHER` is additive to `SCHOOL_TEACHER`; it means the user heads at least one current class and cannot identify which class. Exact class membership comes from `querySchoolContactTeachers` and `isHeadTeacher`. No application-local copy or fourth DingTalk source role is introduced.
6
+ - Affected contracts: `openxiangda-nest` adds typed teacher/class query, record and page contracts plus `querySchoolContactTeachers`. Skills, docs and the standard template explain the global-role versus class-relation boundary. Existing guardian/student APIs are unchanged.
7
+ - Failure and concurrency: the client is a bounded read using the verified authorization and RoleSession. It adds no retry, cache, queue, mutable state or browser credential exposure; platform sync retains its existing complete-snapshot transaction and tenant lock.
8
+ - Security and resource bounds: the platform enforces tenant, app, RoleSession and all/self/class scope. Responses contain platform user ID, DingTalk userid, name and nullable mobile; applications return only fields needed by their App API and must not log full responses. Page size remains capped at 100.
9
+ - Rollback boundary: the Nest method, docs and template are independently removable. The additive platform identity and migration may remain after an SDK rollback. Existing 1.x applications and other 2.0 methods ignore the new contract.
10
+ - Falsifiable verification: typed tests must prove the exact `/school-contact/teachers` request, RoleSession headers and boolean serialization; package checks and `pnpm verify:affected` pass; Skills validation names both `SCHOOL_HEAD_TEACHER` and `teachers.list`/`querySchoolContactTeachers` without suggesting browser direct access.
@@ -0,0 +1,198 @@
1
+ # Workflow participant surface v2
2
+
3
+ Status: accepted for implementation on 2026-08-20.
4
+
5
+ ## Problem evidence
6
+
7
+ - The standard purchase form already persists the selected department and
8
+ submits `departmentId` as a workflow fact, while the workflow definition also
9
+ asks the initiator to choose a submission department. The confirmation
10
+ surface therefore repeats a decision the user has already made and exposes a
11
+ second "resolve approval path" click.
12
+ - Department supervisor resolution returns valid user identifiers without
13
+ display names. A node with four supervisors is rendered as four identical
14
+ unresolved placeholders even though assignment succeeded.
15
+ - Workflow preview already contains its canonical end node. Admin, desktop user,
16
+ and mobile user renderers append another synthetic completion node.
17
+ - Task surfaces project only a subset of their owning instance, omitting
18
+ `startedAt` and `completedAt`. The timeline endpoint returns command logs only,
19
+ so a newly started instance has no visible process history and no future-node
20
+ preview.
21
+ - A department supervisor can be frozen into a workflow task by user identity
22
+ without owning an application role membership. Native RoleSession bootstrap
23
+ stops at `unassigned` before the Workflow Kernel can apply its existing
24
+ task/instance participant authorization.
25
+
26
+ ## Capability owners
27
+
28
+ - Native RoleSession remains the only owner of the active human runtime
29
+ identity. It may expose one reserved `workflow_participant` subject when the
30
+ user is an initiator or participant in this application's environment.
31
+ - Workflow Kernel remains the only owner of task assignment, instance access,
32
+ commands, node state, timestamps, and immutable operation logs.
33
+ - Native Data remains the owner of physical business records. Workflow Surface
34
+ may request one revision-bound record for an already-authorized instance and
35
+ project only fields visible under the workflow field policy.
36
+ - Workflow Definition and Binding remain the only owners of path shape and
37
+ assignee resolution. Renderers do not infer workflow transitions.
38
+ - Admin and user renderers own only presentation and automatic re-prepare after
39
+ all required answers are present.
40
+
41
+ ## Stable invariants and affected contracts
42
+
43
+ 1. A business form's explicit department fact is used directly by a
44
+ `department_supervisor` binding. The reference workflow does not ask for a
45
+ second organization-context department.
46
+ 2. `prepare-start` remains revision-bound and side-effect free. When genuine
47
+ requirements remain, changing a complete answer set automatically requests a
48
+ new preparation; starting still requires a ready preparation token and one
49
+ explicit confirmation.
50
+ 3. Candidate identifiers and assignment semantics do not change. The platform
51
+ adds directory display names when they are available, and renderers fall back
52
+ to a concise assignee count instead of repeating unresolved labels.
53
+ 4. A canonical end node is the sole completion step. A synthetic completion
54
+ step is added only when a preview has no end node.
55
+ 5. Workflow timeline adds a deterministic `flow` projection containing submit,
56
+ planned nodes, node visit state, assignees, and lifecycle timestamps. Existing
57
+ immutable command logs remain available as `items`.
58
+ 6. Task Surface returns the complete public instance lifecycle projection and a
59
+ field-policy-filtered `presentation.businessData` record when the DataRef is
60
+ valid. The client does not need ordinary Data API read capability to render
61
+ an assigned approval.
62
+ 7. The reserved `workflow_participant` RoleSession has zero application
63
+ capabilities and no data scopes. It does not create an application role or
64
+ membership. Workflow endpoints still authorize every task, instance,
65
+ work-center row, timeline, and command from stored participation.
66
+ 8. `workflow_participant` cannot prepare or start a workflow and cannot call
67
+ ordinary Data/App APIs. Existing membership and super-admin behavior is
68
+ unchanged.
69
+ 9. OpenXiangda 1.x, unrelated tenants, and existing production AppVersions do
70
+ not consume these 2.0 contracts.
71
+
72
+ ## Failure, concurrency, security, and resource bounds
73
+
74
+ - Automatic re-prepare is generation-guarded; a later answer change wins and a
75
+ stale response cannot replace the current preparation token.
76
+ - RoleSession creation and switching keep the existing serializable transaction,
77
+ single-active-session rule, expiry, Head revision binding, and CAS behavior.
78
+ - The reserved subject exists only while a matching workflow instance or task
79
+ participation row exists in the same tenant, application, environment, and
80
+ user identity. It is invalidated by the normal bootstrap/context checks when
81
+ that relation disappears.
82
+ - Workflow instance, task, timeline, and business-data reads fail closed unless
83
+ the caller is the initiator, a stored participant, or the application super
84
+ admin. Completed participants retain read-only history access; only an active
85
+ task assignment receives command operations.
86
+ - Timeline planning uses the activated immutable definition and frozen fact
87
+ snapshot. It is bounded by the existing 200-step planner limit. Business-data
88
+ projection reads exactly one referenced record and never enables list queries.
89
+
90
+ ## Rollback boundary
91
+
92
+ - Platform rollback uses the previous platform-server image plus the down
93
+ section of the additive RoleSession subject-kind SQL migration.
94
+ - Frontend and contract rollback uses the previous independently published
95
+ OpenXiangda 2.0 package versions.
96
+ - Reference application rollback uses the previous immutable preproduction
97
+ AppVersion. No compatibility flag or duplicate authorization store remains.
98
+
99
+ ## Falsifiable verification
100
+
101
+ 1. Reference workflow tests prove that the department selected in the business
102
+ form is the only department input and resolves the configured platform
103
+ supervisor.
104
+ 2. Admin, desktop user, and mobile user tests prove answer changes re-prepare
105
+ automatically, real assignee names render, and previews contain one terminal
106
+ node.
107
+ 3. Platform tests prove task Surface carries start/completion timestamps and
108
+ field-policy-filtered business data, while timeline returns submit, complete,
109
+ active, and waiting node states plus immutable command logs.
110
+ 4. A user with no application membership but an assigned department-supervisor
111
+ task receives a `workflow_participant` RoleSession, can open and operate that
112
+ task, can view the instance history after completion, and cannot prepare a
113
+ new workflow or read ordinary Data API records.
114
+ 5. Users with no application role and no workflow relationship remain
115
+ `unassigned`; membership, multi-role selection, and super-admin tests remain
116
+ unchanged.
117
+ 6. Platform OpenXiangda v2 release verification, toolchain affected/release
118
+ verification, reference application generate/check/test/build, and desktop
119
+ plus mobile Chromium approval paths pass before preproduction activation.
120
+
121
+ ## Release-gate correction: local department supervisor resolution
122
+
123
+ The first formal packed-distribution run failed in the fresh PostgreSQL
124
+ application at `department-review` with
125
+ `WORKFLOW_V2_APPROVER_RESOLUTION_EMPTY`. Production correctly delegates this
126
+ provider to platform organization data, but the local platform adapter still
127
+ expected every approval binding to carry an application `roleCode`; the
128
+ standard `department_supervisor` binding intentionally has none.
129
+
130
+ - Ownership and contracts remain unchanged: production organization data owns
131
+ configured supervisors, while the local adapter only emulates that provider
132
+ from the fixture's `department_manager` membership and department approval
133
+ scope.
134
+ - The local adapter must require the binding's `departmentIdFrom` fact and a
135
+ matching `department` scope grant with `approve` (or `*`). Missing facts,
136
+ roles, or scope continue to fail closed with the existing resolution error.
137
+ - No new state store, public API, environment branch, concurrency behavior, or
138
+ production authorization path is introduced. Rollback is the previous
139
+ `openxiangda-local-platform` package.
140
+ - Unit verification covers both a matching and a non-matching department, and
141
+ the packed fresh-application PostgreSQL lifecycle must resolve the same
142
+ `department-review` node before this candidate can be published.
143
+
144
+ ## Release-gate correction: persistent delegation policy
145
+
146
+ The next packed-distribution run resolved the department supervisor correctly,
147
+ but PostgreSQL start rejected the direct assignment with
148
+ `WORKFLOW_V2_PREPARATION_ASSIGNMENT_CHANGED`. The local assignment owner had
149
+ correctly ignored an unrelated long-term delegation, while the transaction
150
+ guard still queried delegation rules for every provider.
151
+
152
+ - The activated workflow binding remains the sole owner of whether long-term
153
+ delegation applies. The local platform passes that resolved policy into the
154
+ PostgreSQL transaction; the store does not infer provider semantics.
155
+ - Delegatable nodes retain the existing delegator lock, active-rule query, and
156
+ frozen participant comparison so a concurrent rule change still invalidates
157
+ preparation. Non-delegatable nodes validate a direct primary participant and
158
+ deliberately do not consult or lock unrelated delegation rules.
159
+ - No public contract, persisted document, production provider, application
160
+ identity, or business-data path changes. The rollback boundary is the prior
161
+ `openxiangda-local-platform` package, and 1.x applications, other tenants,
162
+ and already deployed AppVersions remain outside the blast radius.
163
+ - Verification must keep an active department-manager delegation present while
164
+ a `department_supervisor` preparation starts directly in the real local
165
+ PostgreSQL lifecycle. Its add-sign target remains a second explicit,
166
+ in-scope department-manager identity; the test must not widen the current
167
+ node to a finance role. Existing delegatable-node concurrency and snapshot
168
+ tests must continue to pass.
169
+
170
+ ## Release-gate correction: department supervisor participant targets
171
+
172
+ The formal packed-distribution lifecycle then reached the explicit after-add-sign
173
+ command, but the local platform rejected the second department-manager identity
174
+ with `WORKFLOW_V2_TARGET_ROLE_SUBJECT_INVALID`. Initial assignee resolution
175
+ already derived the implicit `department_manager` role and required the selected
176
+ department's approval scope; participant-target validation still read only an
177
+ explicit binding `roleCode`, which `department_supervisor` intentionally omits.
178
+
179
+ - The activated Workflow binding remains the only owner of target eligibility.
180
+ The local adapter must apply the same implicit `department_manager` role,
181
+ `departmentIdFrom` fact, and `department` `approve` (or `*`) scope check to
182
+ transfer, delegate, and add-sign targets that it applies to the original
183
+ department-supervisor assignment.
184
+ - Missing department facts, a different application role, an out-of-scope
185
+ department, an expired local target, or an unknown RoleSubject continues to
186
+ fail closed with the existing target-validation error. No fallback role or
187
+ second resolver is introduced.
188
+ - Public Workflow contracts, remote provider behavior, participant persistence,
189
+ task/instance CAS, idempotency receipts, and concurrency behavior are
190
+ unchanged. Validation remains a bounded scan of declared local fixture roles
191
+ and scope grants and does not add remote calls or mutable state.
192
+ - The rollback boundary is the prior `openxiangda-local-platform` package.
193
+ OpenXiangda 1.x, other tenants, production platform authorization, and already
194
+ deployed AppVersions remain outside the blast radius.
195
+ - A focused local-platform test must accept a same-role target for the selected
196
+ department and reject the same target for another department. The full packed
197
+ PostgreSQL lifecycle must complete add-sign, restart recovery, and subsequent
198
+ approvals before this candidate can be published.
@@ -72,7 +72,7 @@ authz: {
72
72
 
73
73
  `roleCodes` 把一条数据规则限定到当前 `RoleSession` 选中的角色。同一用户同时是申请人、学院管理员时,切换到学院管理员后,`applicant` 的 `current_user` 规则不再参与 OR 计算。多角色应用的角色特有规则必须声明 `roleCodes`;未声明时表示该规则对所有活动角色通用。编译器会拒绝空数组、重复或未声明的角色代码。
74
74
 
75
- 平台家校身份是另一条只读维度:使用 `platformRoleCodes`(`SCHOOL_GUARDIAN`、`SCHOOL_STUDENT`、`SCHOOL_TEACHER`)表达家长、学生、老师,不把它们塞进应用 `roleCodes`。这些编码由平台家校同步维护并在可信 Principal/RoleSession context 中返回;默认不增加数据限制,应用只有在明确需要时才在 App API 服务端做身份条件判断。浏览器请求中的同名字段不是授权凭据,不能用于放行。
75
+ 平台家校身份是另一条只读维度:使用 `platformRoleCodes`(`SCHOOL_GUARDIAN`、`SCHOOL_STUDENT`、`SCHOOL_TEACHER`、`SCHOOL_HEAD_TEACHER`)表达家长、学生、老师、班主任,不把它们塞进应用 `roleCodes`。班主任同时保留老师身份;`SCHOOL_HEAD_TEACHER` 只表达“至少担任一个班的班主任”,具体班级必须通过老师班级关系的 `isHeadTeacher` 判断。这些编码由平台家校同步维护并在可信 Principal/RoleSession context 中返回;默认不增加数据限制,应用只有在明确需要时才在 App API 服务端做身份条件判断。浏览器请求中的同名字段不是授权凭据,不能用于放行。
76
76
 
77
77
  角色成员、学院范围授权和“用户—仪器”关系授权属于环境运行数据,不进入不可变应用包。它们通过 Native 管理 API 维护,部署新版本不会删除。所有写操作要求 UUID `operationId`、目标资源 revision 和活动管理 RoleSession;平台在数据库事务内完成权限复核、幂等回执、CAS、相关会话撤销与审计。
78
78
 
@@ -13,6 +13,7 @@
13
13
  | 家长 | `SCHOOL_GUARDIAN` | 家校成员 `guardian` |
14
14
  | 学生 | `SCHOOL_STUDENT` | 家校成员 `student` |
15
15
  | 老师 | `SCHOOL_TEACHER` | 家校成员 `teacher` |
16
+ | 班主任 | `SCHOOL_HEAD_TEACHER` | 至少一个老师班级成员带 `is_adviser=true` |
16
17
 
17
18
  身份可组合,同一用户可以同时拥有多个编码。2.0 的 `NativePrincipal` / `Principal` 和 `RoleSessionContext` 以可选的 `platformRoleCodes` 暴露这些编码;它们只用于业务身份判断,不能替代当前应用角色、不能参与 `roleCodes` 的切换,也不能由浏览器提交。权限默认不限制,应用若需要按身份限制页面或 App API,应在服务端使用这些可信编码显式校验。
18
19
 
@@ -21,6 +22,8 @@ const isTeacher =
21
22
  request.openxiangda?.principal.platformRoleCodes?.includes('SCHOOL_TEACHER')
22
23
  ```
23
24
 
25
+ `SCHOOL_HEAD_TEACHER` 表示此人至少在一个当前班级担任班主任,并不能说明具体班级。班主任同时保留 `SCHOOL_TEACHER`。需要判断具体班级时,使用下方 `querySchoolContactTeachers` 返回的 `isHeadTeacher`。
26
+
24
27
  应用开发者不直接写 `roles` 绑定,也不在本地复制同步结果;下一次完整同步会自动增删平台身份绑定。
25
28
 
26
29
  平台同步钉钉家校通讯录后,以只读关系返回监护人、学生和班级。关系两端都包含:
@@ -94,7 +97,7 @@ import {
94
97
  OpenXiangdaOperation,
95
98
  OpenXiangdaPlatformClient,
96
99
  } from 'openxiangda-nest';
97
- import { Controller, Get, Req } from '@nestjs/common';
100
+ import { Controller, Get, Param, Req } from '@nestjs/common';
98
101
  import { appOperations } from '@app/contracts';
99
102
 
100
103
  @Controller('/api/family')
@@ -111,18 +114,35 @@ export class FamilyController {
111
114
  { page: 1, pageSize: 50 }
112
115
  );
113
116
  }
117
+
118
+ @Get('/classes/:classId/head-teachers')
119
+ @OpenXiangdaOperation(appOperations.familyMine)
120
+ async headTeachers(
121
+ @Req() request: OpenXiangdaHttpRequest,
122
+ @Param('classId') classId: string,
123
+ ) {
124
+ const context = request.openxiangda!;
125
+ return await this.platform.querySchoolContactTeachers(
126
+ context.authorization,
127
+ context.roleSessionId!,
128
+ { classId, isHeadTeacher: true, page: 1, pageSize: 100 }
129
+ );
130
+ }
114
131
  }
115
132
  ```
116
133
 
117
134
  可用方法:
118
135
 
119
136
  - `querySchoolContactRelations(authorization, roleSessionId, query)`:按平台用户 ID、手机号、钉钉 userid、姓名、班级或关系类型分页检索;
137
+ - `querySchoolContactTeachers(authorization, roleSessionId, query)`:按老师的平台用户 ID、手机号、钉钉 userid、姓名、班级和 `isHeadTeacher` 分页检索老师班级成员;
120
138
  - `schoolContactChildren(..., userId, query)`:查询指定监护人的学生;
121
139
  - `schoolContactGuardians(..., userId, query)`:查询指定学生的监护人;
122
140
  - `mySchoolContactFamily(...)`:同时查询当前用户作为监护人和学生的两个方向。
123
141
 
124
142
  应用前端用 `useOpenXiangdaAdmin().appApi()` 调用上面的 App API。不要把登录 token、RoleSession ID、平台 URL 或完整关系快照放入浏览器缓存;手机号只展示给确有业务需要的页面,日志不得记录手机号和完整返回体。
125
143
 
144
+ 老师成员返回 `teacher`(`userId`、`dingtalkUserId`、`name`、可空 `mobile`)、`class`、布尔值 `isHeadTeacher`、`syncedAt` 和 `syncState`。查询某班班主任使用 `{ classId, isHeadTeacher: true }`;不要只用 `SCHOOL_HEAD_TEACHER` 推断班级。
145
+
126
146
  ## 失效与降级
127
147
 
128
148
  - `sync.state = disabled`:租户未开启后续同步,历史关系仍可能存在;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openxiangda-skill-kit",
3
- "version": "2.0.0-alpha.39",
3
+ "version": "2.0.0-alpha.41",
4
4
  "description": "Validation and deterministic packaging for OpenXiangda 2.0 AI skills.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -21,7 +21,7 @@
21
21
  "README.md"
22
22
  ],
23
23
  "dependencies": {
24
- "openxiangda-devkit-core": "2.0.0-alpha.28"
24
+ "openxiangda-devkit-core": "2.0.0-alpha.29"
25
25
  },
26
26
  "devDependencies": {
27
27
  "tsx": "4.23.12",
@@ -43,6 +43,9 @@ For a new repository, link it to the target platform and let an authorized platf
43
43
  `all` without a school-contact scope capability; narrower self/class access
44
44
  must be an explicit authorization decision. The App API still requires its
45
45
  normal business-operation capability.
46
+ - Treat `SCHOOL_HEAD_TEACHER` as a global platform identity only. Resolve the
47
+ exact class through `querySchoolContactTeachers(..., { isHeadTeacher: true })`;
48
+ never infer a class from the role code or call the platform from React.
46
49
 
47
50
  ## Delivery Gate
48
51
 
@@ -24,6 +24,10 @@ Build a normal NestJS service in `apps/server`. The platform deploys one contain
24
24
  the NestJS backend with the verified authorization and RoleSession. Expose
25
25
  only an application App API to the browser; never call the platform relation
26
26
  endpoint from React.
27
+ 10. A head teacher keeps `SCHOOL_TEACHER` and additionally receives
28
+ `SCHOOL_HEAD_TEACHER`. Use `querySchoolContactTeachers` and its
29
+ `isHeadTeacher` field for the exact class; the global role alone is not a
30
+ class authorization fact.
27
31
 
28
32
  ## Runtime Path
29
33
 
@@ -35,10 +35,13 @@ Model permissions as capability checks plus contextual data predicates. A role g
35
35
  business-operation capability and rely on the platform query boundary, not
36
36
  browser filtering.
37
37
  - The synchronized platform identities `SCHOOL_GUARDIAN`, `SCHOOL_STUDENT`,
38
- and `SCHOOL_TEACHER` are additive `platformRoleCodes` on the trusted
38
+ `SCHOOL_TEACHER`, and `SCHOOL_HEAD_TEACHER` are additive `platformRoleCodes` on the trusted
39
39
  Principal/RoleSession context, not selectable application `roleCodes`.
40
40
  They are maintained by the platform sync and are unrestricted by default;
41
41
  use them only for an explicit server-side identity check.
42
+ - `SCHOOL_HEAD_TEACHER` means the user heads at least one current class and is
43
+ additive to `SCHOOL_TEACHER`. Authorize the exact class from the platform
44
+ teacher-membership result `isHeadTeacher`, never from the global role alone.
42
45
  - Apply field policies consistently to queries, search controls, forms, CSV
43
46
  transfer and restricted transactions.
44
47
  - Use the bounded server-side aggregate endpoint for charts and summaries. Only
@@ -55,4 +58,4 @@ Run `openxiangda check` and `openxiangda test` after every policy or schema chan
55
58
 
56
59
  Read [Data and Authorization](../../docs/data-authz.md) for policy examples and
57
60
  [School contact relations](../../docs/school-contact-relations.md) for the
58
- guardian/student contract.
61
+ guardian/student and teacher/class contracts.