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

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,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.
@@ -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`)表达家长、学生、老师,不把它们塞进应用 `roleCodes`。这些编码由平台家校同步维护并在可信 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,25 @@
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
+
17
+ 身份可组合,同一用户可以同时拥有多个编码。2.0 的 `NativePrincipal` / `Principal` 和 `RoleSessionContext` 以可选的 `platformRoleCodes` 暴露这些编码;它们只用于业务身份判断,不能替代当前应用角色、不能参与 `roleCodes` 的切换,也不能由浏览器提交。权限默认不限制,应用若需要按身份限制页面或 App API,应在服务端使用这些可信编码显式校验。
18
+
19
+ ```ts
20
+ const isTeacher =
21
+ request.openxiangda?.principal.platformRoleCodes?.includes('SCHOOL_TEACHER')
22
+ ```
23
+
24
+ 应用开发者不直接写 `roles` 绑定,也不在本地复制同步结果;下一次完整同步会自动增删平台身份绑定。
25
+
7
26
  平台同步钉钉家校通讯录后,以只读关系返回监护人、学生和班级。关系两端都包含:
8
27
 
9
28
  - `userId`:平台用户 ID,业务数据应保存这个稳定标识;
@@ -15,15 +34,15 @@
15
34
 
16
35
  ## 权限与默认范围
17
36
 
18
- 应用在 `openxiangda.config.ts` 声明需要的能力,并把其中一种能力授予角色:
37
+ 通过既有认证和 `RoleSession` 校验的非游客用户,在没有声明任何 `school-contact:*` 范围能力时,平台默认使用 `all`,可以查看当前租户全部已同步关系。应用自己的 App API 仍按正常方式声明业务操作能力,例如:
19
38
 
20
39
  ```ts
21
40
  authz: {
22
41
  capabilities: [
23
42
  {
24
- code: 'app:school-service:school-contact:read',
43
+ code: 'app:school-service:family:read',
25
44
  kind: 'backend',
26
- name: '读取全部家校关系',
45
+ name: '使用家庭关系功能',
27
46
  },
28
47
  {
29
48
  code: 'app:school-service:school-contact:self:read',
@@ -40,13 +59,13 @@ authz: {
40
59
  {
41
60
  code: 'user',
42
61
  name: '普通用户',
43
- capabilities: ['app:school-service:school-contact:read'],
62
+ capabilities: ['app:school-service:family:read'],
44
63
  },
45
64
  ],
46
65
  }
47
66
  ```
48
67
 
49
- 应用自己的查询接口也要进入不可变 App API 合同,并复用同一个能力:
68
+ 应用自己的查询接口进入不可变 App API 合同并使用业务操作能力:
50
69
 
51
70
  ```ts
52
71
  backend: {
@@ -55,7 +74,7 @@ backend: {
55
74
  code: 'family.mine',
56
75
  method: 'GET',
57
76
  path: '/api/family/mine',
58
- capability: 'app:school-service:school-contact:read',
77
+ capability: 'app:school-service:family:read',
59
78
  requestSchema: { type: 'object', additionalProperties: false },
60
79
  responseSchema: { type: 'object' },
61
80
  },
@@ -63,7 +82,7 @@ backend: {
63
82
  }
64
83
  ```
65
84
 
66
- 默认配置使用 `school-contact:read`,数据范围是 `all`,即不限制,可以查看全部已同步关系。只有业务明确需要最小化可见范围时,才把角色改为 `self:read` 或 `class:read`。同一角色同时获得多个能力时按 `all > class > self` 取最宽范围,避免前后端对范围产生不同解释。
85
+ 未给活动角色授予任何 `school-contact:*` 范围能力时,数据范围就是 `all`,不限制,可以查看全部已同步关系。只有业务明确需要最小化可见范围时,才给角色增加 `self:read` 或 `class:read`;可选的 `school-contact:read` 表示显式保持 `all`。同一角色同时获得多个范围能力时按 `all > class > self` 取最宽范围,避免前后端对范围产生不同解释。无有效 Principal/RoleSession、游客和跨租户请求仍然拒绝。
67
86
 
68
87
  ## NestJS App API
69
88
 
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.39",
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.28"
25
25
  },
26
26
  "devDependencies": {
27
27
  "tsx": "4.23.12",
@@ -39,8 +39,10 @@ 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.
44
46
 
45
47
  ## Delivery Gate
46
48
 
@@ -27,10 +27,18 @@ 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
+ and `SCHOOL_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.
34
42
  - Apply field policies consistently to queries, search controls, forms, CSV
35
43
  transfer and restricted transactions.
36
44
  - Use the bounded server-side aggregate endpoint for charts and summaries. Only