openxiangda-skill-kit 2.1.1 → 2.1.2
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.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openxiangda-skill-kit",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.2",
|
|
4
4
|
"description": "OpenXiangda 2.0 中文 AI 技能的校验、分发与安装。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"README.md"
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"openxiangda-devkit-core": "2.
|
|
20
|
+
"openxiangda-devkit-core": "2.10.0"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"tsx": "4.23.12",
|
|
@@ -99,7 +99,7 @@ pnpm exec openxiangda --mcp-stdio --cwd <workspace>
|
|
|
99
99
|
|
|
100
100
|
平台拥有身份、授权、业务数据、环境和部署状态。应用只声明自己的模型、页面和规则;普通 CRUD 走 Data API,标准审批和通知按需声明,真实业务动作才启用 Nest。编译器生成契约,应用不改生成输出、不维护第二份权限或能力目录。菜单建议只供初次复制到应用声明,不是运行时自动发现。
|
|
101
101
|
|
|
102
|
-
匿名访问使用 frontend.publicAccess、createAnonymousPublicClient 与平台浏览器凭证,不建立 guest 角色或公开普通 Data API
|
|
102
|
+
匿名访问使用 frontend.publicAccess、createAnonymousPublicClient 与平台浏览器凭证,不建立 guest 角色或公开普通 Data API。提交策略的 `create` 必须配套 `draft`;只读外部数据使用 `public.list`/`public.read` 和显式 `publicRecordFields`,附件/图片/清洗后的富文本走平台代理 URL,子表用 `publicSubtableFields` 显式投影,不需要 draft,且不接受任意筛选、排序或投影。角色并集来自当前用户,Perspective 只收窄读取。
|
|
103
103
|
|
|
104
104
|
## 完成与失败
|
|
105
105
|
|
|
@@ -41,7 +41,21 @@ impersonation token。mutation 必须携带 UUID `operationId`、`reason`,更
|
|
|
41
41
|
|
|
42
42
|
匿名外部访问不属于 RBAC 角色或 current-user 行策略。公开表单、续填、附件、重复校验和同一
|
|
43
43
|
浏览器的本人记录访问只通过[`frontend.publicAccess` 专用合同](public-access.md)开放;平台继续在
|
|
44
|
-
专用端点和 PostgreSQL/RLS
|
|
44
|
+
专用端点和 PostgreSQL/RLS 中强制匿名主体、字段、策略及提交回执边界。需要向外部发布目录、公告或
|
|
45
|
+
可用性列表时,使用同一合同的 `public.list`/`public.read` 与 `publicRecordFields`,明确绑定资源和
|
|
46
|
+
字段;`file`、`image` 和清洗后的 `text.rich` 可以公开,返回的托管文件引用只能通过匿名文件内容路由
|
|
47
|
+
读取,不暴露对象存储地址。子表字段必须在 `publicSubtableFields` 中再次选择子资源字段,例如:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
publicRecordFields: ['name', 'cover', 'description', 'items'],
|
|
51
|
+
publicSubtableFields: { items: ['sku', 'quantity'] },
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
子表只支持一层、固定子字段和有界行数;嵌套子表、签名字段、未声明字段都会在编译或运行时拒绝。
|
|
55
|
+
公共读取不需要 `draft`,不继承角色权限,也不开放普通 Native Data API、where、排序、聚合或导出参数。
|
|
56
|
+
普通 Native Data API 的 `select`、`where`、批量、聚合和导出输入只适用于已认证用户或应用后端;把它暴露给匿名
|
|
57
|
+
浏览器会产生资源/字段枚举、条件推断、查询放大和文件 ID 猜测面。公共端点可以复用 Native RLS 和文件
|
|
58
|
+
绑定校验,但必须保留固定资源、固定字段、固定排序和固定分页的较小输入面。
|
|
45
59
|
|
|
46
60
|
数值边界直接声明在字段上,`min`/`max` 为闭区间,并且只允许用于
|
|
47
61
|
`number.integer` 和 `number.decimal`。跨字段约束声明在资源的
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
OpenXiangda 2.0 支持没有平台账号的外部访客打开一个明确公开的用户页面,保存并续填草稿、
|
|
4
4
|
上传平台托管附件、执行具名重复校验、正式提交,并在同一浏览器中查看自己已提交的列表和详情。
|
|
5
|
+
应用也可以显式发布某个资源的部分记录字段,让外部浏览器分页查询公开记录或读取一条公开记录。
|
|
5
6
|
|
|
6
7
|
该能力识别的是“持有同一个平台 HttpOnly 浏览器凭证的访问者”,不是经过实名验证的自然人。
|
|
7
8
|
清除 Cookie、无痕模式、另一浏览器或另一设备都会成为新的匿名访问者,不能找回原草稿和记录。
|
|
@@ -21,7 +22,7 @@ User-Agent 或浏览器指纹猜测同一个人。
|
|
|
21
22
|
## 应用声明
|
|
22
23
|
|
|
23
24
|
资源仍然按普通 Native Resource 声明。公开能力只在一个静态 `surface: 'user'` 路由上增加一个
|
|
24
|
-
严格有界的 `frontend.publicAccess`
|
|
25
|
+
严格有界的 `frontend.publicAccess` 策略;同一路由可以按设备或资源声明多条策略,调用方必须带策略码:
|
|
25
26
|
|
|
26
27
|
```ts
|
|
27
28
|
export default defineOpenXiangdaApp({
|
|
@@ -104,9 +105,83 @@ export default defineOpenXiangdaApp({
|
|
|
104
105
|
| `create` | 以幂等键正式提交当前草稿 |
|
|
105
106
|
| `own.list` | 分页查看同一浏览器正式提交的记录 |
|
|
106
107
|
| `own.read` | 查看同一浏览器的一条正式提交详情 |
|
|
108
|
+
| `public.list` | 分页查看当前策略明确发布的资源记录 |
|
|
109
|
+
| `public.read` | 读取当前策略明确发布的一条资源记录 |
|
|
107
110
|
|
|
108
111
|
`own.list` 和 `own.read` 不是一般查询权限。服务端固定注入匿名主体、当前公开策略和已提交草稿
|
|
109
|
-
回执条件,不接受调用方的 where
|
|
112
|
+
回执条件,不接受调用方的 where、排序、投影或统计表达式。`public.list` 和 `public.read` 同样不是
|
|
113
|
+
一般查询权限:它们只读取策略绑定资源在当前租户、应用和环境下的记录,服务端固定按创建时间和 id
|
|
114
|
+
倒序分页,只返回 `publicRecordFields` 中显式列出的字段和记录 `id`。附件、图片和清洗后的富文本
|
|
115
|
+
通过平台托管文件路由公开,不返回对象存储地址。子表字段必须在 `publicSubtableFields` 中再次显式
|
|
116
|
+
列出子资源字段,服务端按声明顺序和行数上限返回一层子表数据。签名字段仍不公开。当前不支持调用方
|
|
117
|
+
筛选、排序、聚合或导出。
|
|
118
|
+
|
|
119
|
+
## `draft` 与公共读取
|
|
120
|
+
|
|
121
|
+
`draft` 是匿名提交的服务端事务载体,不是“外部访问”本身,也不是公共读取的前置条件。提交表单时,
|
|
122
|
+
平台需要先把不完整的输入保存到当前匿名浏览器的草稿,并用 `revision` 做并发控制;附件元数据绑定
|
|
123
|
+
到草稿;最终 `create` 会在同一数据库事务中锁定草稿、重新执行必填和重复校验、创建业务记录、标记
|
|
124
|
+
草稿已提交,并用幂等键保证不重复创建。因此声明 `create` 必须同时声明 `draft: { enabled: true }`,
|
|
125
|
+
而且同一策略的 `draft.read` 与 `draft.update` 必须成对出现。
|
|
126
|
+
|
|
127
|
+
公共只读场景不需要草稿。只声明 `public.list`/`public.read` 和 `publicRecordFields` 的策略可以直接
|
|
128
|
+
调用公共查询;它不会获得 `create`、`draft`、`own.*` 或普通 Native Data API 权限。不要为了查询已发布
|
|
129
|
+
数据创建一个“空草稿”,也不要把 `draft id` 传给浏览器。
|
|
130
|
+
|
|
131
|
+
例如,目录页面可以只发布明确选定的字段:
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
{
|
|
135
|
+
code: 'catalog-public',
|
|
136
|
+
routeCode: 'catalog',
|
|
137
|
+
mode: 'anonymous',
|
|
138
|
+
resourceCode: 'catalog-items',
|
|
139
|
+
operations: ['public.list', 'public.read'],
|
|
140
|
+
fields: ['name', 'category', 'available', 'internalNote'],
|
|
141
|
+
publicRecordFields: ['name', 'category', 'available', 'items'],
|
|
142
|
+
publicSubtableFields: { items: ['sku', 'quantity'] },
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`publicRecordFields` 必须是 `fields` 和资源字段的子集;附件、图片和 `text.rich` 可公开,签名仍被
|
|
147
|
+
拒绝。公开子表字段必须配置 `publicSubtableFields`,其键是父资源的子表字段,值是子资源字段列表;
|
|
148
|
+
编译器会拒绝未声明字段、嵌套子表和缺少子字段投影。公共读取沿用明确公开的 `frontend.publicAccess`
|
|
149
|
+
路由和匿名浏览器凭证,不创建 guest 角色或虚拟内部用户。
|
|
150
|
+
|
|
151
|
+
需要隐藏停用、归档或租户标记记录时,使用固定 `publicFilters`,由服务端对每次 `public.list`/
|
|
152
|
+
`public.read` 强制追加。当前只支持最多 16 个不同字段的 `eq` 等值条件,字段必须属于策略的
|
|
153
|
+
`fields` 且仅允许布尔、文本、数值、日期和时间标量;调用方不能覆盖、追加或删除这些条件:
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
publicFilters: [{ field: 'enabled', operator: 'eq', value: true }]
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
匿名创建需要平台生成的不可预测字段时,使用 `serverGeneratedFields`。这些字段不属于
|
|
160
|
+
`fields`,调用方不能在草稿中写入;提交事务会由平台生成随机值并在提交回执的 `generated`
|
|
161
|
+
对象中返回。`random-token` 只适用于不承载身份信息的核验令牌等用途:
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
serverGeneratedFields: [{ field: 'qrToken', kind: 'random-token' }]
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
需要跨资源复核预约窗口等业务不变量时,可声明 `schedule`,绑定两个只读公开策略和资源字段。
|
|
168
|
+
平台会在最终创建事务中重新读取启用校区与规则,校验星期、日期范围、提前小时数和离散时段;页面端
|
|
169
|
+
校验只能改善体验,不能替代这次服务端复核。
|
|
170
|
+
|
|
171
|
+
子表中的文件引用会自动带上受控的 `resourceCode` 与父字段绑定;应用如需为附件生成下载地址,使用
|
|
172
|
+
`fileContentUrl(fileId, disposition, variant, resourceCode, parentFieldCode)`,不要自行拼接文件路径。
|
|
173
|
+
|
|
174
|
+
## 为什么不开放普通 Native Data API
|
|
175
|
+
|
|
176
|
+
普通 Native Data API 是内部或应用后端的可信数据边界,允许调用方提交
|
|
177
|
+
`select`、`where`、`order`、批量查询、聚合和导出,并按当前登录用户角色执行行列权限。匿名
|
|
178
|
+
公开发布的语义不同:它必须只绑定一个资源和不可变字段白名单,不接受调用方筛选、排序、聚合、
|
|
179
|
+
导出或自带角色,也必须把文件绑定到公开记录和公开字段后再读取。
|
|
180
|
+
|
|
181
|
+
直接把普通 Native Data API 暴露给浏览器会允许枚举内部资源和字段、通过筛选和计数推断未公开数据,
|
|
182
|
+
放大查询资源消耗,并增加文件 ID 猜测、审计字段泄露和权限合同混用的风险。因此公共端点继续是
|
|
183
|
+
专用的 `public.list`/`public.read`;底层可以复用 Native 的 RLS 和文件所有权校验,但不把 Native
|
|
184
|
+
Data API 的输入面开放给匿名调用方。
|
|
110
185
|
|
|
111
186
|
## 页面客户端
|
|
112
187
|
|
|
@@ -133,6 +208,10 @@ const receipt = await client.submit(withPhoto.revision, crypto.randomUUID());
|
|
|
133
208
|
|
|
134
209
|
const page = await client.listOwn({ pageSize: 20 });
|
|
135
210
|
const detail = await client.getOwn(receipt.recordId);
|
|
211
|
+
|
|
212
|
+
// 只读公开目录不需要先读取或保存 draft。
|
|
213
|
+
const publicPage = await client.listPublic({ pageSize: 20 });
|
|
214
|
+
const publicDetail = await client.getPublic(publicPage.items[0].data.id as string);
|
|
136
215
|
```
|
|
137
216
|
|
|
138
217
|
必须先 `bootstrap()`。草稿更新始终使用最近返回的 revision,冲突时重新读取,不能覆盖写。
|