openxiangda-skill-kit 2.0.0-alpha.38 → 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,13 @@
1
+ # School-contact default access v2
2
+
3
+ ## Decision
4
+
5
+ - Problem evidence: the product contract says an authenticated application user defaults to the unrestricted tenant-local school-contact scope, while `OpenXiangdaSchoolContactV2Service.resolveAccess` currently denies a valid Principal when its active RoleSession has no explicit school-contact capability.
6
+ - Capability owner: the platform school-contact service owns relation data-scope resolution. The Native authorization kernel remains the owner of Principal and RoleSession verification and is not bypassed or duplicated.
7
+ - Stable invariants: platform identity codes remain descriptive claims rather than switchable application roles; tenant, application, environment, Principal, RoleSession, pagination, and query enforcement remain server-verified.
8
+ - Contract: a verified non-guest Principal with no explicit school-contact scope defaults to `all`. Explicit `read`, `class:read`, and `self:read` resolve in the order `all > class > self`, allowing an application role to narrow the default. Invalid or missing Principal/RoleSession continues to fail before data access.
9
+ - Failure and concurrency: authorization errors propagate unchanged; access resolution is read-only and introduces no cache, retry, queue, lock, or cross-request state.
10
+ - Security and resource bounds: the default applies only after the existing v2 authentication and RoleSession boundary and never relaxes tenant/application isolation. The broader authenticated-user visibility is the explicitly accepted product policy.
11
+ - Affected contracts: the typed NestJS school-contact client and response types do not change. Only the default scope selected by the platform service changes.
12
+ - Rollback: code and documentation rollback only; there is no migration or persisted authorization rewrite.
13
+ - Falsifiable verification: tests prove default `all`, explicit `class`/`self`, widest-capability precedence, and fail-closed missing Principal evidence; `pnpm verify:affected` must pass.
@@ -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,108 @@
1
+ # Standard surface runtime corrections v2
2
+
3
+ Status: accepted for implementation on 2026-08-20.
4
+
5
+ ## Problem evidence
6
+
7
+ - Standard Admin filters send scalar `eq` predicates for stable JSON option and
8
+ directory values. PostgreSQL rejects values such as `software` as invalid
9
+ JSON with SQLSTATE `22P02`.
10
+ - A department field is rendered by the platform field kit in forms, but its
11
+ search value is not normalized with the same field contract before a Data
12
+ API query is created.
13
+ - Admin tabs persist path metadata while the active Umi `Outlet` is unmounted
14
+ on every navigation. Returning to a tab therefore recreates and reloads the
15
+ page. Query-string changes do not have an explicit page-instance invalidation
16
+ key.
17
+ - The official mobile application shell explicitly contributes a role switcher
18
+ even though this surface is the normal end-user entry.
19
+ - The procurement reference workflow binds department approval to an
20
+ application role scope. The configured platform organization department
21
+ supervisor is therefore not consulted.
22
+ - Native clients request the durable record audit endpoint at
23
+ `/native/data/:resource/records/:id/audit`, but the Native Data controller has
24
+ no matching route. Desktop and mobile detail pages load data and audit in one
25
+ request group, so the missing audit route makes the whole detail page fail.
26
+ - The desktop end-user workflow submission route removes primary navigation
27
+ without contributing a back action.
28
+
29
+ ## Capability owners
30
+
31
+ - `openxiangda-admin` owns standard search-value normalization, tab metadata,
32
+ and bounded mounted page instances.
33
+ - `openxiangda-field-kit` remains the owner of department and other platform
34
+ field controls and stable values; no application-specific selector is added.
35
+ - Native Data API owns durable business-audit reads and row/field enforcement.
36
+ - Platform organization data and `department_supervisor` resolution are the
37
+ only owners of configured department supervisors.
38
+ - `openxiangda-user` owns standard desktop submission navigation affordances;
39
+ the application supplies the destination.
40
+ - The application template owns which optional controls appear on its mobile
41
+ shell and which workflow provider its domain workflow selects.
42
+
43
+ ## Stable invariants and affected contracts
44
+
45
+ 1. Data API remains the only business-record store and query boundary. Stable
46
+ directory and option values keep their existing `{ label, value }` shapes.
47
+ 2. Search predicates for those values use JSON containment; text, dates,
48
+ numbers, booleans, and physical scalar identifiers keep their current
49
+ operators.
50
+ 3. Tabs, mounted page instances, query responses, and drafts remain separate
51
+ state. At most twelve tabs and six mounted pages are retained per identity.
52
+ 4. A tab is identified by pathname. A change to that pathname's search/hash
53
+ location key remounts the active page and therefore reloads URL-bound data.
54
+ 5. Identity epoch changes discard every mounted instance. Closing or LRU
55
+ eviction discards only the affected instance; the persisted tab may remain.
56
+ 6. Native audit returns the existing `openxiangda.data-audit-page/v2`
57
+ contract. No second application audit API or database is introduced.
58
+ 7. Native audit rechecks the current role's resource capability, row policy,
59
+ and field policy before returning each durable outbox event.
60
+ 8. Mobile RoleSession behavior is unchanged; only the visible top-level role
61
+ switch button is removed from the standard user template.
62
+ 9. Workflow Kernel contracts do not change. The reference binding selects the
63
+ already-supported `department_supervisor` provider with `departmentId`
64
+ facts.
65
+ 10. OpenXiangda 1.x applications and runtime contracts are not affected.
66
+
67
+ ## Failure, concurrency, security, and resource bounds
68
+
69
+ - Invalid or unreadable search fields still fail closed at Data API. The Admin
70
+ never falls back to browser filtering.
71
+ - Cached pages are hidden but remain mounted. LRU eviction is deterministic and
72
+ bounded to six instances; identity changes synchronously invalidate the pool.
73
+ - A changed URL location key replaces only the active cached instance. Stale
74
+ requests remain guarded by each standard page's request generation.
75
+ - Audit pagination is bounded, reads at most 500 matching durable events, and
76
+ omits events whose record snapshot no longer passes the current row policy.
77
+ - Audit is read-only and introduces no lock, receipt, retry loop, or mutable
78
+ application state.
79
+ - Department supervisor resolution remains tenant-scoped and uses the existing
80
+ organization service. Empty resolution continues to fail closed.
81
+
82
+ ## Rollback boundary
83
+
84
+ - Platform rollback is the previous platform-server image; no SQL migration is
85
+ required.
86
+ - Frontend rollback is the previous independently published package set.
87
+ - Application rollback is the previous immutable preproduction AppVersion.
88
+ - No compatibility switch or duplicate endpoint is kept after rollback.
89
+
90
+ ## Falsifiable verification
91
+
92
+ 1. Admin tests prove department search uses the Field Kit control and stable
93
+ option/directory values compile to JSON containment rather than scalar `eq`.
94
+ 2. Admin lifecycle tests prove switching between two tabs preserves component
95
+ state, the seventh cacheable page evicts the least recently used instance,
96
+ closing a tab unmounts it, identity changes clear all instances, and a query
97
+ change remounts the active instance.
98
+ 3. Native Data tests prove the audit route exists, returns masked durable
99
+ history, enforces current row policy, and supports bounded pagination.
100
+ 4. Workflow tests/config checks prove a configured platform department
101
+ supervisor resolves for `department-review` without an application-managed
102
+ department membership.
103
+ 5. Mobile Chromium has no role button and can open a purchase detail page.
104
+ 6. Desktop Chromium can return from the standalone submission page and URL
105
+ query changes reload the appropriate page.
106
+ 7. Platform `verify:openxiangda-v2:release`, toolchain `verify:release`, and the
107
+ standard application's generate/check/test/build gates pass before a new
108
+ preproduction deployment is activated.
@@ -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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  2.0 权限由 RBAC 与上下文数据策略共同组成:角色回答“可以做什么”,策略回答“在当前角色下可以对哪些数据和字段做”。
4
4
 
5
- 家校关系使用相同的活动角色边界。默认授予 `app:<appCode>:school-contact:read`,范围为不限制的 `all`;需要收紧时改授 `self:read` 或 `class:read`,范围由平台在查询中强制执行,页面不能靠客户端过滤。见[家校通讯录关系](./school-contact-relations.md)。
5
+ 家校关系使用相同的活动角色边界。有效 Principal/RoleSession 未声明任何 `school-contact:*` 范围能力时默认使用不限制的 `all`;需要收紧时显式授予 `self:read` 或 `class:read`,范围由平台在查询中强制执行,页面不能靠客户端过滤。应用自己的 App API 仍必须使用正常的业务操作 capability。见[家校通讯录关系](./school-contact-relations.md)。
6
6
 
7
7
  ## 请求模型
8
8
 
@@ -72,6 +72,8 @@ authz: {
72
72
 
73
73
  `roleCodes` 把一条数据规则限定到当前 `RoleSession` 选中的角色。同一用户同时是申请人、学院管理员时,切换到学院管理员后,`applicant` 的 `current_user` 规则不再参与 OR 计算。多角色应用的角色特有规则必须声明 `roleCodes`;未声明时表示该规则对所有活动角色通用。编译器会拒绝空数组、重复或未声明的角色代码。
74
74
 
75
+ 平台家校身份是另一条只读维度:使用 `platformRoleCodes`(`SCHOOL_GUARDIAN`、`SCHOOL_STUDENT`、`SCHOOL_TEACHER`、`SCHOOL_HEAD_TEACHER`)表达家长、学生、老师、班主任,不把它们塞进应用 `roleCodes`。班主任同时保留老师身份;`SCHOOL_HEAD_TEACHER` 只表达“至少担任一个班的班主任”,具体班级必须通过老师班级关系的 `isHeadTeacher` 判断。这些编码由平台家校同步维护并在可信 Principal/RoleSession context 中返回;默认不增加数据限制,应用只有在明确需要时才在 App API 服务端做身份条件判断。浏览器请求中的同名字段不是授权凭据,不能用于放行。
76
+
75
77
  角色成员、学院范围授权和“用户—仪器”关系授权属于环境运行数据,不进入不可变应用包。它们通过 Native 管理 API 维护,部署新版本不会删除。所有写操作要求 UUID `operationId`、目标资源 revision 和活动管理 RoleSession;平台在数据库事务内完成权限复核、幂等回执、CAS、相关会话撤销与审计。
76
78
 
77
79
  新应用由 `openxiangda app provision` 为创建者建立平台保留的 `platform.app-super-admin` grant。它不是应用包中的普通角色,不能被业务流程代理或通过多个角色合并获得。重复执行 provision 会复用同一个 grant,可用于补齐早期创建但尚未初始化 Native 管理员的 2.0 应用。
@@ -4,6 +4,28 @@
4
4
 
5
5
  ## 数据合同
6
6
 
7
+ ### 平台内置身份角色
8
+
9
+ 成功的家校全量同步会维护平台层身份角色(不属于应用 `RoleSession` 的可切换角色):
10
+
11
+ | 展示名 | 稳定编码 | 来源 |
12
+ | --- | --- | --- |
13
+ | 家长 | `SCHOOL_GUARDIAN` | 家校成员 `guardian` |
14
+ | 学生 | `SCHOOL_STUDENT` | 家校成员 `student` |
15
+ | 老师 | `SCHOOL_TEACHER` | 家校成员 `teacher` |
16
+ | 班主任 | `SCHOOL_HEAD_TEACHER` | 至少一个老师班级成员带 `is_adviser=true` |
17
+
18
+ 身份可组合,同一用户可以同时拥有多个编码。2.0 的 `NativePrincipal` / `Principal` 和 `RoleSessionContext` 以可选的 `platformRoleCodes` 暴露这些编码;它们只用于业务身份判断,不能替代当前应用角色、不能参与 `roleCodes` 的切换,也不能由浏览器提交。权限默认不限制,应用若需要按身份限制页面或 App API,应在服务端使用这些可信编码显式校验。
19
+
20
+ ```ts
21
+ const isTeacher =
22
+ request.openxiangda?.principal.platformRoleCodes?.includes('SCHOOL_TEACHER')
23
+ ```
24
+
25
+ `SCHOOL_HEAD_TEACHER` 表示此人至少在一个当前班级担任班主任,并不能说明具体班级。班主任同时保留 `SCHOOL_TEACHER`。需要判断具体班级时,使用下方 `querySchoolContactTeachers` 返回的 `isHeadTeacher`。
26
+
27
+ 应用开发者不直接写 `roles` 绑定,也不在本地复制同步结果;下一次完整同步会自动增删平台身份绑定。
28
+
7
29
  平台同步钉钉家校通讯录后,以只读关系返回监护人、学生和班级。关系两端都包含:
8
30
 
9
31
  - `userId`:平台用户 ID,业务数据应保存这个稳定标识;
@@ -15,15 +37,15 @@
15
37
 
16
38
  ## 权限与默认范围
17
39
 
18
- 应用在 `openxiangda.config.ts` 声明需要的能力,并把其中一种能力授予角色:
40
+ 通过既有认证和 `RoleSession` 校验的非游客用户,在没有声明任何 `school-contact:*` 范围能力时,平台默认使用 `all`,可以查看当前租户全部已同步关系。应用自己的 App API 仍按正常方式声明业务操作能力,例如:
19
41
 
20
42
  ```ts
21
43
  authz: {
22
44
  capabilities: [
23
45
  {
24
- code: 'app:school-service:school-contact:read',
46
+ code: 'app:school-service:family:read',
25
47
  kind: 'backend',
26
- name: '读取全部家校关系',
48
+ name: '使用家庭关系功能',
27
49
  },
28
50
  {
29
51
  code: 'app:school-service:school-contact:self:read',
@@ -40,13 +62,13 @@ authz: {
40
62
  {
41
63
  code: 'user',
42
64
  name: '普通用户',
43
- capabilities: ['app:school-service:school-contact:read'],
65
+ capabilities: ['app:school-service:family:read'],
44
66
  },
45
67
  ],
46
68
  }
47
69
  ```
48
70
 
49
- 应用自己的查询接口也要进入不可变 App API 合同,并复用同一个能力:
71
+ 应用自己的查询接口进入不可变 App API 合同并使用业务操作能力:
50
72
 
51
73
  ```ts
52
74
  backend: {
@@ -55,7 +77,7 @@ backend: {
55
77
  code: 'family.mine',
56
78
  method: 'GET',
57
79
  path: '/api/family/mine',
58
- capability: 'app:school-service:school-contact:read',
80
+ capability: 'app:school-service:family:read',
59
81
  requestSchema: { type: 'object', additionalProperties: false },
60
82
  responseSchema: { type: 'object' },
61
83
  },
@@ -63,7 +85,7 @@ backend: {
63
85
  }
64
86
  ```
65
87
 
66
- 默认配置使用 `school-contact:read`,数据范围是 `all`,即不限制,可以查看全部已同步关系。只有业务明确需要最小化可见范围时,才把角色改为 `self:read` 或 `class:read`。同一角色同时获得多个能力时按 `all > class > self` 取最宽范围,避免前后端对范围产生不同解释。
88
+ 未给活动角色授予任何 `school-contact:*` 范围能力时,数据范围就是 `all`,不限制,可以查看全部已同步关系。只有业务明确需要最小化可见范围时,才给角色增加 `self:read` 或 `class:read`;可选的 `school-contact:read` 表示显式保持 `all`。同一角色同时获得多个范围能力时按 `all > class > self` 取最宽范围,避免前后端对范围产生不同解释。无有效 Principal/RoleSession、游客和跨租户请求仍然拒绝。
67
89
 
68
90
  ## NestJS App API
69
91
 
@@ -75,7 +97,7 @@ import {
75
97
  OpenXiangdaOperation,
76
98
  OpenXiangdaPlatformClient,
77
99
  } from 'openxiangda-nest';
78
- import { Controller, Get, Req } from '@nestjs/common';
100
+ import { Controller, Get, Param, Req } from '@nestjs/common';
79
101
  import { appOperations } from '@app/contracts';
80
102
 
81
103
  @Controller('/api/family')
@@ -92,18 +114,35 @@ export class FamilyController {
92
114
  { page: 1, pageSize: 50 }
93
115
  );
94
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
+ }
95
131
  }
96
132
  ```
97
133
 
98
134
  可用方法:
99
135
 
100
136
  - `querySchoolContactRelations(authorization, roleSessionId, query)`:按平台用户 ID、手机号、钉钉 userid、姓名、班级或关系类型分页检索;
137
+ - `querySchoolContactTeachers(authorization, roleSessionId, query)`:按老师的平台用户 ID、手机号、钉钉 userid、姓名、班级和 `isHeadTeacher` 分页检索老师班级成员;
101
138
  - `schoolContactChildren(..., userId, query)`:查询指定监护人的学生;
102
139
  - `schoolContactGuardians(..., userId, query)`:查询指定学生的监护人;
103
140
  - `mySchoolContactFamily(...)`:同时查询当前用户作为监护人和学生的两个方向。
104
141
 
105
142
  应用前端用 `useOpenXiangdaAdmin().appApi()` 调用上面的 App API。不要把登录 token、RoleSession ID、平台 URL 或完整关系快照放入浏览器缓存;手机号只展示给确有业务需要的页面,日志不得记录手机号和完整返回体。
106
143
 
144
+ 老师成员返回 `teacher`(`userId`、`dingtalkUserId`、`name`、可空 `mobile`)、`class`、布尔值 `isHeadTeacher`、`syncedAt` 和 `syncState`。查询某班班主任使用 `{ classId, isHeadTeacher: true }`;不要只用 `SCHOOL_HEAD_TEACHER` 推断班级。
145
+
107
146
  ## 失效与降级
108
147
 
109
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.38",
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.27"
24
+ "openxiangda-devkit-core": "2.0.0-alpha.29"
25
25
  },
26
26
  "devDependencies": {
27
27
  "tsx": "4.23.12",
@@ -39,8 +39,13 @@ For a new repository, link it to the target platform and let an authorized platf
39
39
  - Build one immutable application package; the platform owns deployment execution, health gates, promotion, and rollback.
40
40
  - Reject any workspace that does not declare the native 2.0 application configuration.
41
41
  - Read school-contact relationships only through a NestJS App API bound to the
42
- active RoleSession. The default relation scope is unrestricted; narrower
43
- self/class access must be an explicit authorization decision.
42
+ active RoleSession. A verified non-guest Principal defaults to unrestricted
43
+ `all` without a school-contact scope capability; narrower self/class access
44
+ must be an explicit authorization decision. The App API still requires its
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.
44
49
 
45
50
  ## Delivery Gate
46
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
 
@@ -27,10 +27,21 @@ Model permissions as capability checks plus contextual data predicates. A role g
27
27
  - Require the application directory capability before using department or user
28
28
  selectors. Store stable IDs and resolve display labels through Directory v2;
29
29
  do not copy tenant contact data into application records.
30
- - School-contact access defaults to the unrestricted `school-contact:read`
31
- capability. Use `school-contact:self:read` or
30
+ - School-contact access defaults to unrestricted `all` for a verified
31
+ non-guest Principal/RoleSession even when no `school-contact:*` scope
32
+ capability is assigned. Use `school-contact:self:read` or
32
33
  `school-contact:class:read` only when the product explicitly requires a
33
- narrower scope; rely on the platform query boundary, not browser filtering.
34
+ narrower scope; keep the application's own App API behind its normal
35
+ business-operation capability and rely on the platform query boundary, not
36
+ browser filtering.
37
+ - The synchronized platform identities `SCHOOL_GUARDIAN`, `SCHOOL_STUDENT`,
38
+ `SCHOOL_TEACHER`, and `SCHOOL_HEAD_TEACHER` are additive `platformRoleCodes` on the trusted
39
+ Principal/RoleSession context, not selectable application `roleCodes`.
40
+ They are maintained by the platform sync and are unrestricted by default;
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.
34
45
  - Apply field policies consistently to queries, search controls, forms, CSV
35
46
  transfer and restricted transactions.
36
47
  - Use the bounded server-side aggregate endpoint for charts and summaries. Only
@@ -47,4 +58,4 @@ Run `openxiangda check` and `openxiangda test` after every policy or schema chan
47
58
 
48
59
  Read [Data and Authorization](../../docs/data-authz.md) for policy examples and
49
60
  [School contact relations](../../docs/school-contact-relations.md) for the
50
- guardian/student contract.
61
+ guardian/student and teacher/class contracts.