@tier0/sdk-v2 0.9.5 → 0.9.8

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 (72) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +8 -6
  3. package/dist/cjs/capabilities.js +7 -4
  4. package/dist/cjs/capabilities.js.map +1 -1
  5. package/dist/cjs/client.d.ts +4 -6
  6. package/dist/cjs/data/bootstrap.js +6 -1
  7. package/dist/cjs/data/bootstrap.js.map +1 -1
  8. package/dist/cjs/data/define.d.ts +7 -0
  9. package/dist/cjs/data/define.js +21 -1
  10. package/dist/cjs/data/define.js.map +1 -1
  11. package/dist/cjs/data/schema.d.ts +1 -0
  12. package/dist/cjs/data/schema.js +9 -0
  13. package/dist/cjs/data/schema.js.map +1 -1
  14. package/dist/cjs/openapi/api.generated.d.ts +1 -0
  15. package/dist/cjs/openapi/client.js +7 -0
  16. package/dist/cjs/openapi/client.js.map +1 -1
  17. package/dist/cjs/openapi/codecs.generated.d.ts +55 -28
  18. package/dist/cjs/openapi/codecs.generated.js +1 -1
  19. package/dist/cjs/openapi/codecs.generated.js.map +1 -1
  20. package/dist/cjs/openapi/types.generated.d.ts +21 -2
  21. package/dist/cjs/openapi/types.generated.js.map +1 -1
  22. package/dist/cjs/source.d.ts +0 -3
  23. package/dist/cjs/source.js +0 -1
  24. package/dist/cjs/source.js.map +1 -1
  25. package/dist/cjs/uns-v2/generated.d.ts +29 -88
  26. package/dist/cjs/uns-v2/generated.js +21 -23
  27. package/dist/cjs/uns-v2/generated.js.map +1 -1
  28. package/dist/esm/capabilities.js +7 -4
  29. package/dist/esm/capabilities.js.map +1 -1
  30. package/dist/esm/client.d.ts +4 -6
  31. package/dist/esm/data/bootstrap.js +6 -1
  32. package/dist/esm/data/bootstrap.js.map +1 -1
  33. package/dist/esm/data/define.d.ts +7 -0
  34. package/dist/esm/data/define.js +19 -0
  35. package/dist/esm/data/define.js.map +1 -1
  36. package/dist/esm/data/schema.d.ts +1 -0
  37. package/dist/esm/data/schema.js +10 -1
  38. package/dist/esm/data/schema.js.map +1 -1
  39. package/dist/esm/openapi/api.generated.d.ts +1 -0
  40. package/dist/esm/openapi/client.js +7 -0
  41. package/dist/esm/openapi/client.js.map +1 -1
  42. package/dist/esm/openapi/codecs.generated.d.ts +55 -28
  43. package/dist/esm/openapi/codecs.generated.js +1 -1
  44. package/dist/esm/openapi/codecs.generated.js.map +1 -1
  45. package/dist/esm/openapi/types.generated.d.ts +21 -2
  46. package/dist/esm/openapi/types.generated.js.map +1 -1
  47. package/dist/esm/source.d.ts +0 -3
  48. package/dist/esm/source.js +0 -1
  49. package/dist/esm/source.js.map +1 -1
  50. package/dist/esm/uns-v2/generated.d.ts +29 -88
  51. package/dist/esm/uns-v2/generated.js +21 -23
  52. package/dist/esm/uns-v2/generated.js.map +1 -1
  53. package/package.json +2 -1
  54. package/skills/tier0-sdk-v2/tier0-sdk-v2-data/SKILL.md +4 -1
  55. package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/consistency.md +3 -3
  56. package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/errors.md +4 -0
  57. package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/modeling.md +32 -1
  58. package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/read-write.md +3 -3
  59. package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/schema-evolution.md +3 -2
  60. package/skills/tier0-sdk-v2/tier0-sdk-v2-members/SKILL.md +1 -1
  61. package/skills/tier0-sdk-v2/tier0-sdk-v2-members/references/app-members.md +2 -1
  62. package/skills/tier0-sdk-v2/tier0-sdk-v2-source/SKILL.md +5 -6
  63. package/skills/tier0-sdk-v2/tier0-sdk-v2-source/references/changes.md +2 -2
  64. package/skills/tier0-sdk-v2/tier0-sdk-v2-source/references/create.md +6 -6
  65. package/skills/tier0-sdk-v2/tier0-sdk-v2-source/references/delete.md +1 -1
  66. package/skills/tier0-sdk-v2/tier0-sdk-v2-source/references/read.md +1 -1
  67. package/skills/tier0-sdk-v2/tier0-sdk-v2-source/references/update.md +8 -8
  68. package/skills/tier0-sdk-v2/tier0-sdk-v2-stream/SKILL.md +1 -1
  69. package/skills/tier0-sdk-v2/tier0-sdk-v2-stream/references/create.md +1 -1
  70. package/skills/tier0-sdk-v2/tier0-sdk-v2-stream/references/delete.md +1 -1
  71. package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/references/namespace.md +1 -1
  72. package/skills/tier0-sdk-v2/tier0-sdk-v2-source/references/state.md +0 -36
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tier0/sdk-v2",
3
- "version": "0.9.5",
3
+ "version": "0.9.8",
4
4
  "description": "Tier0 Cloud new-architecture SDK for TypeScript and JavaScript",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -22,6 +22,7 @@
22
22
  "dist",
23
23
  "skills",
24
24
  "README.md",
25
+ "CHANGELOG.md",
25
26
  "LICENSE",
26
27
  "NOTICE.md"
27
28
  ],
@@ -5,7 +5,7 @@ description: 使用 @tier0/sdk-v2/data 在 App 服务端声明并读写本 App
5
5
 
6
6
  # App 数据层
7
7
 
8
- 工业场景创建数据前,先按[工业命名空间指导](../tier0-sdk-v2-uns/references/namespace.md)查找并复用已有 ISA-95 目录,只创建业务需要且缺失的层级。`defineCollection` / ensure 不接收 `parentId`,新集合默认放入 App 专属目录;需要指定工业目录时,由 Agent 在构建期用现有元数据接口创建并通过 `data.parentId` 放置,运行时按已有集合 ID 访问。
8
+ 创建 App 集合必须在 `defineCollection` 中声明 `directoryPath`:从 Workspace 根开始的实际业务目录名称数组,例如已确认的 `['苏州工厂', '装配一线', 'MES']`(代码写法见下方)。工业场景先按[工业命名空间指导](../tier0-sdk-v2-uns/references/namespace.md)查询并复用已有 ISA-95 目录,只声明业务需要且已知的层级;不同集合按实际范围放置,共享数据放到共同祖先。ensure 按路径逐级复用或创建目录,不再把所有集合放入缺少业务范围的根级 App 目录;允许在已知工厂、产线等业务范围下声明 App 或业务模块目录,例如“工厂 / 产线 / MES / 工单”中的 MES。不强行补满五层,不编造企业、工厂或设备。非工业场景使用真实业务目录。
9
9
 
10
10
  `@tier0/sdk-v2/data` 是 App 业务数据的服务端入口:用 `defineCollection` 声明集合,`createDataClient` 在首次访问时一次性 ensure 全部声明,然后用 Mongo 驱动风格的集合句柄读写 UNS 记录。没有 SQL、ORM、连接串或迁移步骤;Preview 与部署后的 App 共用同一批集合和记录。
11
11
 
@@ -37,6 +37,7 @@ import { defineCollection, type Doc } from '@tier0/sdk-v2/data/define';
37
37
  export const workOrders = defineCollection({
38
38
  alias: 'work_orders',
39
39
  name: '生产工单',
40
+ directoryPath: ['苏州工厂', '装配一线', 'MES'],
40
41
  description: 'Production work orders',
41
42
  schema: z.object({
42
43
  code: z.string(),
@@ -56,6 +57,8 @@ export async function releasedOrders(): Promise<WorkOrder[]> {
56
57
  }
57
58
  ```
58
59
 
60
+ 示例假设“苏州工厂 / 装配一线 / MES”已由业务确认;替换为实际目录,不照抄示例工厂。每段为独立业务名称,不使用斜杠拼接。新建缺少路径会报 `INVALID_REQUEST`;已有 alias 可兼容省略路径,后续声明的路径变化不会自动搬迁集合,返回 `DIRECTORY_PATH_NOT_APPLIED` 提示。
61
+
59
62
  ## 核心约束
60
63
 
61
64
  1. 声明文件只从 `@tier0/sdk-v2/data/define` 导入,客户端代码可以安全引用它;由 ensure 管理的自有集合放在一个 `collections` 列表里(每个 App 最多 64 个),只建一个进程级 client;不要手动 ensure、不要在 import 时访问数据、不要自建“已初始化”标记。
@@ -14,9 +14,9 @@
14
14
  import { z } from 'zod';
15
15
  import { createDataClient, defineCollection } from '@tier0/sdk-v2/data';
16
16
 
17
- const stock = defineCollection({ alias: 'stock', name: '库存', schema: z.object({ sku: z.string(), available: z.number().int().min(0).meta({ format: 'int32' }) }) });
18
- const reservations = defineCollection({ alias: 'reservations', name: '库存预留', schema: z.object({ sku: z.string(), qty: z.number().int().min(1).meta({ format: 'int32' }), orderId: z.string() }) });
19
- const counters = defineCollection({ alias: 'counters', name: '业务计数器', schema: z.object({ value: z.number().int().meta({ format: 'int32' }) }) });
17
+ const stock = defineCollection({ alias: 'stock', name: '库存', directoryPath: ['仓储管理'], schema: z.object({ sku: z.string(), available: z.number().int().min(0).meta({ format: 'int32' }) }) });
18
+ const reservations = defineCollection({ alias: 'reservations', name: '库存预留', directoryPath: ['仓储管理'], schema: z.object({ sku: z.string(), qty: z.number().int().min(1).meta({ format: 'int32' }), orderId: z.string() }) });
19
+ const counters = defineCollection({ alias: 'counters', name: '业务计数器', directoryPath: ['业务管理'], schema: z.object({ value: z.number().int().meta({ format: 'int32' }) }) });
20
20
  const data = createDataClient({ collections: [stock, reservations, counters] });
21
21
 
22
22
  /** 扣减库存并写预留:库存不足时整体不提交。 */
@@ -59,6 +59,10 @@ export function toResponse(error: unknown): Response {
59
59
  | code | 含义与处理 |
60
60
  | --- | --- |
61
61
  | `SCHEMA_CONFLICT` (409) | 声明无法与存储合并,没有任何改动。`data.conflicts` 列出 `{ alias, path, reason, current?, declared? }`:`TYPE_CONFLICT` → 换新字段名;`LIMIT_EXCEEDED` → 拆分结构;`JOIN_UNSUPPORTED` → 存储中有声明无法放宽的节点,用 `schema:replace` 修复;`DATA_RULE_FIELD` → 员工数据规则(owner/state 字段)在用该路径,保持必填且类型不变,或请管理员修改规则;`OPEN_OBJECT` → 用 `z.object`;`ALIAS_IN_USE` → 选择未占用的 alias;`NAME_IN_USE` → 选择同目录下不同且有业务含义的 name,保留 alias,不拼随机后缀;`INVALID_INDEX` → 修正索引 |
62
+ | `INVALID_REQUEST` (400),`path=collections/<i>/directoryPath` | 新建集合缺少目录或路径非法。声明实际业务路径后再调用;不回退到缺少业务范围的根级 App 目录,不添加虚构层级 |
63
+ | `SCHEMA_CONFLICT`,`data.reason=DIRECTORY_PATH_CONFLICT` | 路径被已归档目录或同名非目录对象占用;核对真实目录,不能自动恢复、改名或扩大授权 |
64
+ | `DIRECTORY_PATH_NOT_APPLIED`(`ignored` 提示) | 已有 alias 的目录与声明不同;保留当前位置继续服务。需要移动时走明确授权的元数据操作,ensure 不迁移 |
65
+ | `CONDITION_FAILED`,`data.reason=DIRECTORY_UNAVAILABLE` | 恢复集合时原父目录或祖先不可用;明确恢复目录后再试,不自动恢复父链或改挂集合 |
62
66
  | `ANNOTATION_NOT_APPLIED`(`ignored` 提示) | 不是错误:ensure 保留已存储的描述,用 `schema:replace` 应用修改 |
63
67
  | `REQUIRED_NOT_ENFORCED`(警告) | 声明要求的字段在存储中仍是可选;改回可选,或回填后用 `schema:replace` 收窄 |
64
68
  | `INDEX_BUILD_FAILED`(警告) | 已有记录违反新唯一索引;修正重复数据,下次 ensure 会重试建索引 |
@@ -1,6 +1,6 @@
1
1
  # 声明集合与建模
2
2
 
3
- `defineCollection({ alias, name, schema, indexes?, description?, seed? })` 返回冻结的声明;全部声明放进一个数组传给 `createDataClient({ collections })`。声明文件从 `@tier0/sdk-v2/data/define` 导入:它不含数据运行时,组件和 route 也能引用这些声明与 `Doc` 类型。alias 在 App 内唯一、不区分大小写,匹配 `^[A-Za-z][A-Za-z0-9_-]{0,62}$`。
3
+ `defineCollection({ alias, name, directoryPath, schema, indexes?, description?, seed? })` 返回冻结的声明;全部声明放进一个数组传给 `createDataClient({ collections })`。声明文件从 `@tier0/sdk-v2/data/define` 导入:它不含数据运行时,组件和 route 也能引用这些声明与 `Doc` 类型。alias 在 App 内唯一、不区分大小写,匹配 `^[A-Za-z][A-Za-z0-9_-]{0,62}$`。
4
4
 
5
5
  `name` 是必填的业务名称,界面直接展示,例如“生产工单”;1–63 个 Unicode 字符,无首尾空白,不含 `/` 或空字符。`alias` 是独立、稳定的程序标识,例如 `production_orders`,改业务名称不改 alias 或集合 ID。名称应表达实际用途,语言遵循用户偏好和已有业务术语,不附加无业务含义的随机串、时间戳或 ID。没有 displayName,也不从 alias 回退生成名称。ensure 只在首次创建时写入 name;命中已有 alias 时保留用户当前名称,显式改名走 MCP 元数据更新。
6
6
 
@@ -27,6 +27,7 @@ import { defineCollection, type Doc } from '@tier0/sdk-v2/data/define';
27
27
  export const customers = defineCollection({
28
28
  alias: 'customers',
29
29
  name: '客户',
30
+ directoryPath: ['销售管理'],
30
31
  schema: z.object({ name: z.string(), tier: z.enum(['A', 'B', 'C']) }),
31
32
  indexes: [{ path: 'name' }],
32
33
  seed: [
@@ -38,6 +39,7 @@ export const customers = defineCollection({
38
39
  export const salesOrders = defineCollection({
39
40
  alias: 'sales_orders',
40
41
  name: '销售订单',
42
+ directoryPath: ['销售管理', '订单履约'],
41
43
  description: 'Customer sales orders',
42
44
  schema: z.object({
43
45
  code: z.string(),
@@ -63,6 +65,34 @@ export const createSalesOrderInput = salesOrders.schema
63
65
  .extend({ code: z.string().trim().min(1).max(32) });
64
66
  ```
65
67
 
68
+ ## 按实际业务范围声明目录
69
+
70
+ 新建集合必须提供 `directoryPath`,类型保留可选仅为兼容已经存在的 alias。路径从 Workspace 根开始,包含 1–16 个名称;每段 1–63 个 Unicode 字符,无首尾空白、`/` 或空字符。SDK 校验后复制并冻结数组,按原样发送,不自动追加 App 名称或补层级。已知业务范围下可以显式声明 App 或业务模块目录,例如 `directoryPath: ['工厂', '产线', 'MES']` 后放“工单”集合;不应仅用根级 App 名称代替已知工厂和产线范围。
71
+
72
+ 工业场景按实际 ISA-95 设备层级组织:企业 → 工厂/站点 → 区域/车间 → 工作中心(产线等)→ 工作单元。先通过元数据查询确认已有目录、名称与父子关系,复用完整已知路径。只保留业务实际需要且已知的层级,不要求五层齐全;跨产线共享集合放在共同祖先,不为每个集合重复建一套树。非工业业务按真实业务目录组织。
73
+
74
+ 例如业务已明确“苏州工厂”的“装配一线”及 MES 应用,产线工单放在 MES 下,工厂共享物料仍放在工厂下;无需编造企业或车间:
75
+
76
+ ```ts
77
+ import { z } from 'zod';
78
+ import { defineCollection } from '@tier0/sdk-v2/data/define';
79
+
80
+ export const lineOrders = defineCollection({
81
+ alias: 'line_orders', name: '产线工单',
82
+ directoryPath: ['苏州工厂', '装配一线', 'MES'],
83
+ schema: z.object({ code: z.string() }),
84
+ });
85
+ export const siteMaterials = defineCollection({
86
+ alias: 'site_materials', name: '工厂物料',
87
+ directoryPath: ['苏州工厂'],
88
+ schema: z.object({ code: z.string() }),
89
+ });
90
+ ```
91
+
92
+ 后端在同一事务中逐级复用或创建活动目录,再创建集合;已归档目录或同名非目录对象会明确报冲突,不自动恢复或追加随机后缀。目录位置不代表数据授权或 App 所属关系;共享目录不会成为某个 App 的专属资源。
93
+
94
+ 目录声明只决定首次创建位置。ensure 找到已有 alias 时保留集合 ID、现有目录和用户改名;不同路径返回 `DIRECTORY_PATH_NOT_APPLIED`,不移动已有集合,也不为了被忽略的路径创建目录。要整理存量数据,需要明确迁移授权后用元数据接口操作。不要通过改 alias 复制集合来绕过这一规则。
95
+
66
96
  ## 可存储的 Zod 子集
67
97
 
68
98
  集合 Schema 会转成 UNS 记录 Schema 子集;不能存储的写法在 `compileCollection` / 首次 ensure 时抛 `CollectionSchemaError`,并给出替代写法。
@@ -111,6 +141,7 @@ import { defineCollection } from '@tier0/sdk-v2/data/define';
111
141
  export const loginCodes = defineCollection({
112
142
  alias: 'login_codes',
113
143
  name: '登录验证码',
144
+ directoryPath: ['账号管理', '登录验证'],
114
145
  schema: z.object({ phone: z.string(), code: z.string(), issuedAt: z.number().int().meta({ format: 'int32' }) }),
115
146
  // issuedAt 存 Date.now();签发 5 分钟后删除。
116
147
  indexes: [{ path: 'phone' }, { path: 'issuedAt', expireAfterSeconds: 300 }],
@@ -8,7 +8,7 @@ import { createDataClient, defineCollection } from '@tier0/sdk-v2/data';
8
8
 
9
9
  const workOrders = defineCollection({
10
10
  alias: 'work_orders',
11
- name: '生产工单',
11
+ name: '生产工单', directoryPath: ['生产管理'],
12
12
  schema: z.object({
13
13
  code: z.string(),
14
14
  status: z.enum(['DRAFT', 'RELEASED', 'DONE']),
@@ -64,7 +64,7 @@ import { createDataClient, defineCollection } from '@tier0/sdk-v2/data';
64
64
 
65
65
  const workOrders = defineCollection({
66
66
  alias: 'work_orders',
67
- name: '生产工单',
67
+ name: '生产工单', directoryPath: ['生产管理'],
68
68
  schema: z.object({ code: z.string(), status: z.enum(['DRAFT', 'RELEASED', 'DONE']) }),
69
69
  indexes: [{ path: 'code', unique: true }, { path: 'status' }],
70
70
  });
@@ -163,7 +163,7 @@ export async function bumpAll(items: Collection<{ status: string; views: number
163
163
  import { z } from 'zod';
164
164
  import { createDataClient, defineCollection, runAsApp, type SubscriptionEvent } from '@tier0/sdk-v2/data';
165
165
 
166
- const tickets = defineCollection({ alias: 'tickets', name: '问题工单', schema: z.object({ title: z.string(), status: z.enum(['OPEN', 'DONE']) }) });
166
+ const tickets = defineCollection({ alias: 'tickets', name: '问题工单', directoryPath: ['问题管理'], schema: z.object({ title: z.string(), status: z.enum(['OPEN', 'DONE']) }) });
167
167
  const data = createDataClient({ collections: [tickets] });
168
168
 
169
169
  /** 一个进程一条 App 级订阅,在进程内扇出给浏览器。 */
@@ -6,6 +6,7 @@ Preview 与部署后的 App 共用集合,较旧的部署版本随时可能用
6
6
  | --- | --- |
7
7
  | 新增可选字段(任意层级,含数组元素内)、新增枚举值、字段改为可选、`int` → `number`、放宽或去掉 `min`/`max`、新增索引 | 修改声明;下次访问时 ensure 生效 |
8
8
  | 修改集合名称 | 通过 tier0 MCP 的 metadata:updateOne 显式修改 name,保留 alias/ID;修改声明后重复 ensure 不覆盖已有名称 |
9
+ | 修改集合目录 | `directoryPath` 只决定首次创建位置;已有 alias 不自动移动,差异报告 `DIRECTORY_PATH_NOT_APPLIED`。确认迁移授权后使用元数据接口,不通过改 alias 重建集合 |
9
10
  | 修改 `.describe()` 文本或集合 `description` | 修改声明,然后通过 tier0 MCP 执行 `schema:replace`,请求体从 `compileCollection(declaration)` 中只选 `alias`、`recordSchema`、`indexes`、`description`,不传 `name`(先 `dryRun`:期望 `widened`;`narrowed` 说明存储里还有该声明之外的内容,例如其他版本的字段——合并进请求体或停止)。ensure 保留已存储的描述并报告 `ANNOTATION_NOT_APPLIED`;新字段和新集合在创建时带上描述 |
10
11
  | 新增每条记录都必须有的字段 | 先以 `.optional()` 加入,所有写入路径都写它,回填旧记录,等部署版本也写入后再用 `schema:replace` 改为必填 |
11
12
  | 删除或重命名字段 | 代码不再使用旧字段(它可以留在存储里);新名字就是新的可选字段。通过 tier0 MCP 一次性回填(`records:updateMany` 配 `$rename` / `$set`) |
@@ -16,7 +17,7 @@ Preview 与部署后的 App 共用集合,较旧的部署版本随时可能用
16
17
 
17
18
  ## 运行时代码与 worker 操作的边界
18
19
 
19
- `schema:replace`、`archive`、`restore` 由 worker 通过 tier0 MCP 显式执行(`/uns/app/collections/schema:replace`、`archive`、`restore`),App 运行时代码不调用。数据层导出的 `compileCollection(declaration)` 给出与运行时 ensure 完全一致的条目 `{ alias, name, recordSchema, indexes, description? }`,可用于 ensure `dryRun`。`schema:replace` 只接收其中的 alias、recordSchema、indexes、description,不接收 name;不能把 compileCollection 的完整结果直接作为替换请求。
20
+ `schema:replace`、`archive`、`restore` 由 worker 通过 tier0 MCP 显式执行(`/uns/app/collections/schema:replace`、`archive`、`restore`),App 运行时代码不调用。数据层导出的 `compileCollection(declaration)` 给出与运行时 ensure 完全一致的条目 `{ alias, name, directoryPath?, recordSchema, indexes, description? }`,可用于 ensure `dryRun`。`schema:replace` 只接收其中的 alias、recordSchema、indexes、description,不接收 name 或 directoryPath;不能把 compileCollection 的完整结果直接作为替换请求。
20
21
 
21
22
  ```ts
22
23
  import { z } from 'zod';
@@ -24,7 +25,7 @@ import { compileCollection, defineCollection } from '@tier0/sdk-v2/data';
24
25
 
25
26
  const tickets = defineCollection({
26
27
  alias: 'tickets',
27
- name: '问题工单',
28
+ name: '问题工单', directoryPath: ['问题管理'],
28
29
  schema: z.object({ title: z.string().describe('Short summary'), severity: z.enum(['LOW', 'HIGH']).optional() }),
29
30
  });
30
31
 
@@ -49,7 +49,7 @@ export async function searchMembers(keyword: string, page = 1) {
49
49
 
50
50
  ## App 可用成员
51
51
 
52
- 能使用某个 App 的成员用 `members.listApp`,参数、示例与错误处理见 [App 可用成员](references/app-members.md)。`appId` 必须显式传入,SDK 不隐式读取 `APP_ID`;查询当前 App 时传 `getCurrentAppId()`。接口只接受 API Key,登录会话不能调用;绑定 App 的凭证只能查自己的 appId,查其他 App 返回 403。3003016 / 3003017 都是 HTTP 503,表示访问名单暂时无法给出,不是空名单:按 `ApiError.code` 分支,不匹配 message 文本;3003016 需要重新部署 App,3003017 可稍后重试。
52
+ 能使用某个 App 的成员用 `members.listApp`,参数、示例与错误处理见 [App 可用成员](references/app-members.md)。`appId` 必须显式传入,SDK 不隐式读取 `APP_ID`;查询当前 App 时传 `getCurrentAppId()`。Builder 预览里查到的是当前草稿角色的成员,部署后是线上版本的成员;这个 `scope` 由 SDK 自动带上,App 代码不要传。接口只接受 API Key,登录会话不能调用;绑定 App 的凭证只能查自己的 appId,查其他 App 返回 403。3003016 / 3003017 都是 HTTP 503,表示访问名单暂时无法给出,不是空名单:按 `ApiError.code` 分支,不匹配 message 文本;3003016 需要重新部署 App,3003017 可稍后重试。
53
53
 
54
54
  ## 当前边界
55
55
 
@@ -1,6 +1,6 @@
1
1
  # App 可用成员
2
2
 
3
- 方法:`members.listApp(body)`;POST /openapi/v2/apps/access-members/list。返回能使用该 App 线上版本的有效成员。`appId` 必填且由调用方显式传入,SDK 不隐式读取 `APP_ID`;查询当前 App 时传 `getCurrentAppId()`。接口只接受 API Key,登录会话不能调用;绑定 App 的凭证只能查自己的 appId,查其他 App 返回 403。
3
+ 方法:`members.listApp(body)`;POST /openapi/v2/apps/access-members/list。返回能使用该 App 的有效成员:在 Builder 预览里按当前草稿的角色计算(从没发布过的 App 也一样),部署后按线上版本计算。口径由请求的 `scope` 决定,SDK 按平台注入的 `TIER0_RUNTIME` 自动带上(`preview` → `draft`,`deployed` → `production`);App 代码不要传 `scope`,也不要在 `.env` 或 `artifact.toml` 里设置 `TIER0_RUNTIME`。`appId` 必填且由调用方显式传入,SDK 不隐式读取 `APP_ID`;查询当前 App 时传 `getCurrentAppId()`。接口只接受 API Key,登录会话不能调用;绑定 App 的凭证只能查自己的 appId,查其他 App 返回 403。
4
4
 
5
5
  | 字段 | 类型 | 说明 |
6
6
  | --- | --- | --- |
@@ -8,6 +8,7 @@
8
8
  | roleKeys | string[] | 任一命中即可,大小写不敏感 |
9
9
  | grantedAtStart / grantedAtEnd | number / bigint | 授权时间,Unix 毫秒,闭区间,精度为秒 |
10
10
  | page / size | number / bigint | 分页 |
11
+ | scope | string | 由 SDK 按运行环境自动带上,App 代码不传 |
11
12
 
12
13
  ```ts
13
14
  import { ApiError, createClient, getCurrentAppId, type Tier0Client } from '@tier0/sdk-v2';
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: tier0-sdk-v2-source
3
- description: 使用 @tier0/sdk-v2 的 client.source 查询、创建、修改、启停和删除新架构 Source,配置协议连接、处理影响分析、版本冲突与运行观测。
3
+ description: 使用 @tier0/sdk-v2 的 client.source 查询、创建、修改和删除新架构 Source,配置协议连接、处理影响分析、版本冲突与运行观测。
4
4
  ---
5
5
 
6
6
  # Source 数据接入
@@ -15,8 +15,7 @@ Source 维护连接配置、认证引用与源状态。脚本处理用 [Stream](
15
15
  | 创建 HTTP/SQL 带凭据源 | [创建](references/create.md) | documents.create |
16
16
  | 列表、详情、运行状态 | [查询](references/read.md) | documents.list/get、observations.get、runtime.get |
17
17
  | 修改名称、端点或配置 | [修改](references/update.md) | documents.update |
18
- | 查看修改/启停/删除影响 | [影响分析](references/changes.md) | impact |
19
- | 启用、停用 | [状态变更](references/state.md) | state.update |
18
+ | 查看修改/删除影响 | [影响分析](references/changes.md) | impact |
20
19
  | 删除并核验 | [删除](references/delete.md) | delete |
21
20
 
22
21
  先加载对应操作文档,使用其中的参数表、完整请求及返回判断。只执行用户任务所需步骤;示例函数调用才会执行写入,导入不执行。新建→查询→修改→删除可组成生命周期,但查询任务不触发其他操作。
@@ -25,13 +24,13 @@ Source 维护连接配置、认证引用与源状态。脚本处理用 [Stream](
25
24
 
26
25
  1. Source 配置统一使用 documents.*,原生文档为 `{name,protocol,config}`,HTTP API 显式指定 `config.role`。HTTP 统一为 `protocol: http-api` + `config.role: client/server`;Client 主动请求,Server 接收入站请求,不能互换。HTTPServer 统一接收入站请求,固定前缀后的路由和响应时机属于 Stream。
27
26
  2. 已有 ID 和 revision 时优先用 documents.update 的 patch,避免额外 get;完整替换 document 前读取完整文档,安全列表摘要不能无损回写。原生 ID 是十进制字符串,revision 原样传递,不转 Number。
28
- 3. 现有 Source 修改、启停、删除都要 expectedRevision。没有任何 Stream 引用时可直接提交;有草稿、活动、历史或不可见引用时仍需 confirmedImpactRevision。先形成精确意图,再读取全部影响分页;回执不可合成、跨意图复用或静默刷新。提供的回执始终严格校验,不自动获取并同意影响。
27
+ 3. 现有 Source 修改、删除都要 expectedRevision。仅修改名称、描述可直接提交;修改连接配置或删除时,没有任何 Stream 引用可直接提交,有草稿、活动、历史或不可见引用则需 confirmedImpactRevision。先形成精确意图,再读取全部影响分页;回执不可合成、跨意图复用或静默刷新。提供的回执始终严格校验,不自动获取并同意影响。
29
28
  4. 配置中保留认证/TLS 引用,不写明文秘密;HTTP Client 与 SQL 新凭据走创建专用 credentials(SQL 使用 usernamePassword,省略 method),普通创建省略 credentials.method(服务端默认 REQUEST 基址绑定),真实 method/path/query 由 Stream 定义。Basic/Bearer 显式凭据方法限制精确 URL 和方法,OAuth 旧 GET 保留既有兼容语义,不自动扩大旧授权;Server 不携带 Client auth/TLS 配置。
30
- 5. desiredState 不代表实际健康,保存配置不代表 Stream 已切换至新 configRevision。运行状态另行读取。
29
+ 5. Source 创建后即可供 Stream 使用,不提供启停开关。删除 Source 后停止后续使用;runtimeState 和 health 是运行观测,不是用户开关。保存配置不代表 Stream 已切换至新 configRevision。
31
30
 
32
31
  ## 返回值与异常
33
32
 
34
- 所有调用返回完整 code/msg/requestId/data。documents.create/update 的预检返回 validated/sideEffects,提交返回完整 Source view,可直接使用其中的文档和 revision,无需额外 get;批量状态/删除返回 Outcome。只有 committed 且无项错误才可报告提交成功;accepted/jobId 继续查询作业。创建、更新、状态变更的 dryRun 默认 false;预检按需使用,不是每次提交的必经步骤。
33
+ 所有调用返回完整 code/msg/requestId/data。documents.create/update 的预检返回 validated/sideEffects,提交返回完整 Source view,可直接使用其中的文档和 revision,无需额外 get;批量删除返回 Outcome。只有 committed 且无项错误才可报告提交成功;accepted/jobId 继续查询作业。创建、更新的 dryRun 默认 false;预检按需使用,不是每次提交的必经步骤。
35
34
 
36
35
  需要查看历史用 source.history({id,beforeRevision,size}),按 nextBeforeRevision 继续;探测用 source.probe,其外部系统访问应属于当前任务。配置校验或连接探测成功不证明某个 Stream 请求已获授权或采集成功,实际结果看对应发布的 runs。连接配置修改使用 documents.update,遵守同一影响确认契约。
37
36
 
@@ -5,7 +5,7 @@
5
5
  | 字段 | 类型 | 必填 | 说明 |
6
6
  | --- | --- | --- | --- |
7
7
  | id / expectedRevision | string / integer | 是 | 当前 ID 和最近写回执或读取取得的修订 |
8
- | action | update/enable/disable/delete | 是 | 精确变更类型 |
8
+ | action | update/delete | 是 | 精确变更类型 |
9
9
  | patch / document | CloudSourceDocumentPatch / CloudSourceDocument | 更新时二选一 | 与随后提交完全相同的补丁或完整文档;其他 action 不传 |
10
10
  | offset / size | integer | 否 | 首次 0;size 1–200 |
11
11
  | impactRevision | string | 翻页时 | 沿用首次返回的确认值 |
@@ -37,4 +37,4 @@ export async function collectSourceImpact(
37
37
 
38
38
  data:impactRevision、sourceID/sourceRevision、action、counts(draft/activeRelease/historicalRelease)、restrictedCount、references、nextOffset、expiresAt。nextOffset=0 结束;complete=1 表示计算完成,不表示首页有全部引用。
39
39
 
40
- 确认值绑定身份、修订、精确意图,不能合成或自动刷新。分析时用 patch,提交时就沿用该 patch;不要另行补齐为 document 或更改字段。评估 counts 和 restrictedCount;有受限引用时不能说“无影响”。Source 更新不会自动替换 Stream 冻结修订。已有明确操作授权时不重复索要确认;影响分析本身不授予额外操作权限。没有任何引用时可直接变更,见 [修改](update.md),不必先执行本流程。
40
+ 确认值绑定身份、修订、精确意图,不能合成或自动刷新。分析时用 patch,提交时就沿用该 patch;不要另行补齐为 document 或更改字段。评估 counts 和 restrictedCount;有受限引用时不能说“无影响”。Source 更新不会自动替换 Stream 冻结修订。已有明确操作授权时不重复索要确认;影响分析本身不授予额外操作权限。仅修改名称、描述或没有任何引用时可直接变更,见 [修改](update.md),不必先执行本流程。
@@ -5,7 +5,7 @@
5
5
  | 字段 | 类型 | 必填 | 说明 |
6
6
  | --- | --- | --- | --- |
7
7
  | dryRun | boolean | 否 | 默认 false 提交;true 仅预检 |
8
- | document.name | string | 是 | 1–128 字符 |
8
+ | document.name | string | 是 | 1–63 个 Unicode 字符 |
9
9
  | document.protocol | string | 是 | 原生协议,如 http-api、mqtt、modbus |
10
10
  | document.config.role | client / server | HTTP API 显式指定 | client 主动请求;server 接收入站请求 |
11
11
  | document.config | object | 是 | endpoint、options;见[协议配置](protocols.md) |
@@ -15,7 +15,7 @@
15
15
 
16
16
  ## 完整示例:HTTP Client 数据源
17
17
 
18
- 传入实际端点、名称和意图键。此函数创建 `http-api` + `config.role: client` 配置,不启用源。client 从服务端环境读取 API Host/Key,也可显式注入。
18
+ 传入实际端点、名称和意图键。此函数创建 `http-api` + `config.role: client` 配置,创建后即可供 Stream 使用。client 从服务端环境读取 API Host/Key,也可显式注入。
19
19
 
20
20
  ```ts
21
21
  import { createClient, type Tier0Client, type CloudSourceDocument } from '@tier0/sdk-v2';
@@ -35,7 +35,7 @@ export async function createHttpSource(
35
35
  }
36
36
  ```
37
37
 
38
- 一次提交即可返回完整 data:id、revision、document、desiredState、runtimeState、health、createdAt、updatedAt,无需另行 get。需要单独预检时可先传 `dryRun: true`,返回 `{ validated: true, sideEffects: false }`,没有 ID。预检不保留提交条件;实际保存仍会校验。ID 原样保留十进制字符串。
38
+ 一次提交即可返回完整 data:id、revision、document、runtimeState、health、createdAt、updatedAt,无需另行 get。需要单独预检时可先传 `dryRun: true`,返回 `{ validated: true, sideEffects: false }`,没有 ID。预检不保留提交条件;实际保存仍会校验。ID 原样保留十进制字符串。
39
39
 
40
40
  HTTP Server 同样使用 `protocol: http-api`,角色为 `config.role: server`;其配置以 `source.protocols.list({})` 返回的正式合同为准,不能复用下方 Client 的主动请求示例。入站业务统一绑定 HTTPServer Source,路由和响应时机在 Stream 定义;202 状态不能据此报告 Stream 执行完成。
41
41
 
@@ -62,11 +62,11 @@ export async function createAuthenticatedHttpSource(
62
62
  }
63
63
  ```
64
64
 
65
- 配置保存不代表连接健康;见[查询](read.md)。仅在任务包含启用时执行[启停](state.md)。结果未知时保留原请求、requestId、意图键,先核对列表/历史,不自动重复创建。
65
+ 配置保存不代表连接健康;见[查询](read.md)。Source 无需额外启用。结果未知时保留原请求、requestId、意图键,先核对列表/历史,不自动重复创建。
66
66
 
67
67
  ## HTTP Server:管理配置
68
68
 
69
- 正式协议目录规定 Server 的 `config.endpoint` 为 `http://managed`、`options` 为 `{}`,不携带 Client 的 `auth` / `tls` 或创建专用 `credentials`。该函数只保存 Source,不启用、不创建 Stream,也不发起入站请求。
69
+ 正式协议目录规定 Server 的 `config.endpoint` 为 `http://managed`、`options` 为 `{}`,不携带 Client 的 `auth` / `tls` 或创建专用 `credentials`。该函数只保存 Source,不创建 Stream,也不发起入站请求。
70
70
 
71
71
  ```ts
72
72
  import { createClient, type Tier0Client, type CloudSourceDocument } from '@tier0/sdk-v2';
@@ -125,4 +125,4 @@ export async function createHttpSourceWithCredentials(
125
125
 
126
126
  SQL 单条创建可直接传入 `credentials: { kind: "usernamePassword", material: { username, password } }`,不再预先调用独立凭据接口。`document.protocol` 为 `sql`,`config.endpoint` 只包含数据库地址(不包含用户名或密码),`credentials.method` 必须省略。仍需 `idempotencyKey`。秘密由 Vault 保存,Source 文档仅含引用;预检不写入凭据。
127
127
 
128
- `dryRun` 省略时直接提交;需要预检才设为 true。后续启用 Source、部署 Stream,并通过运行记录和 Collection 读回确认采集成功。
128
+ `dryRun` 省略时直接提交;需要预检才设为 true。后续绑定 Source 并部署 Stream,并通过运行记录和 Collection 读回确认采集成功。
@@ -13,7 +13,7 @@
13
13
  | allowPartial | false | 否 | 不支持部分提交 |
14
14
  | idempotencyKey | string | 否 | 最多 128 字符 |
15
15
 
16
- Source 必须已停用。没有任何 Stream 引用时,直接用已有 ID 和 revision 提交;有引用时先 impact(action=delete) 并[遍历和评估影响](changes.md),再显式传入回执。服务端在同一事务中检查依赖和修订,不自动删除或停用依赖流。
16
+ Source 可直接删除,无需先停用;删除后停止后续采集和入站接入。没有任何 Stream 引用时,直接用已有 ID 和 revision 提交;有引用时先 impact(action=delete) 并[遍历和评估影响](changes.md),再显式传入回执。服务端在同一事务中检查依赖和修订,不自动删除或停用依赖流。
17
17
 
18
18
  ```ts
19
19
  import { createClient, type Tier0Client, type UnsV2Request } from '@tier0/sdk-v2';
@@ -2,7 +2,7 @@
2
2
 
3
3
  | 方法 | POST 路径 | 输入 | data |
4
4
  | --- | --- | --- | --- |
5
- | source.documents.list | /openapi/v2/uns/sources/documents/list | page、size、search、protocol、desiredState、health、archived、includeDocument | list、total、page、hasMore、nextPage |
5
+ | source.documents.list | /openapi/v2/uns/sources/documents/list | page、size、search、protocol、health、archived、includeDocument | list、total、page、hasMore、nextPage |
6
6
  | source.documents.get | /openapi/v2/uns/sources/documents/get | id(必填)、view:'summary' 或 'full' | id、revision、状态;full 才包含完整 document |
7
7
  | source.runtime.get | /openapi/v2/uns/sources/runtime/get | id(必填) | 运行状态 |
8
8
  | source.observations.get | /openapi/v2/uns/sources/observations/get | items:id、expectedRevision | 指定源和修订的观测 |
@@ -7,13 +7,13 @@
7
7
  | id | string | 是 | 已知的 Source ID |
8
8
  | patch / document | object / CloudSourceDocument | 二选一 | patch 保留未提供的字段;document 完整替换 |
9
9
  | expectedRevision | integer | 是 | 已读取或上次提交返回的 revision,原样传回 |
10
- | confirmedImpactRevision | string | 有引用时 | 相同修改意图和修订的 impact 回执 |
10
+ | confirmedImpactRevision | string | 有引用的连接变更时 | 相同修改意图和修订的 impact 回执;仅改名称、描述无需提供 |
11
11
  | dryRun | boolean | 否 | 默认 false 提交;true 仅预检 |
12
12
  | idempotencyKey | string | 否 | 最多 128 字符 |
13
13
 
14
14
  ## 示例:一次请求修改名称
15
15
 
16
- 已有 ID 和 revision 时直接提交 patch。服务端在同一事务和依赖锁下检查引用并修改:没有任何草稿、活动或历史 Stream 引用时无需 impact。patch 中未提供的字段保留原值,config 如果提供则整体替换;完整 document 更新需先取得完整文档并保留未改配置和认证引用。
16
+ 已有 ID 和 revision 时直接提交 patch。仅改名称、描述无需 impact;连接配置变更在没有任何草稿、活动或历史 Stream 引用时也无需 impact。服务端在同一事务和依赖锁下检查并修改。patch 中未提供的字段保留原值,config 如果提供则整体替换;完整 document 更新需先取得完整文档并保留未改配置和认证引用。
17
17
 
18
18
  ```ts
19
19
  import { createClient, type Tier0Client, type UnsV2Request } from '@tier0/sdk-v2';
@@ -32,19 +32,19 @@ export async function renameSource(
32
32
 
33
33
  提交直接返回完整 Source view 和新的 revision,无需额外 get。版本冲突时不会覆盖其他人的修改。返回 `SOURCE_IMPACT_CONFIRMATION_REQUIRED` 表示存在引用且未提交,进入下面的显式评估流程;不要自动获取并同意影响。
34
34
 
35
- ## 有引用时:评估后确认相同意图
35
+ ## 有引用的连接变更:评估后确认相同意图
36
36
 
37
37
  prepare 收集全部影响分页。在已有授权范围内评估 counts、restrictedCount 和引用后,再调用 commit。不可见引用也要求确认。影响字段说明见[影响分析](changes.md)。
38
38
 
39
39
  ```ts
40
- import { createClient, type Tier0Client, type UnsV2Request } from '@tier0/sdk-v2';
40
+ import { createClient, type Tier0Client, type UnsV2Request, type CloudSourceDocument } from '@tier0/sdk-v2';
41
41
 
42
- export async function prepareSourceRename(
42
+ export async function prepareSourceConfigUpdate(
43
43
  id: string, expectedRevision: UnsV2Request<'sourceImpact'>['expectedRevision'],
44
- name: string, client: Tier0Client = createClient(),
44
+ config: CloudSourceDocument['config'], client: Tier0Client = createClient(),
45
45
  ) {
46
46
  const intent: UnsV2Request<'sourceImpact'> = {
47
- id, expectedRevision, action: 'update', patch: { name },
47
+ id, expectedRevision, action: 'update', patch: { config },
48
48
  };
49
49
  const impact = await client.source.impact({ ...intent, offset: 0, size: 200 });
50
50
  const references = [...impact.data.references];
@@ -62,7 +62,7 @@ export async function prepareSourceRename(
62
62
  }
63
63
  return { intent, impact: { ...impact.data, references, nextOffset: offset } };
64
64
  }
65
- export async function commitSourceRename(
65
+ export async function commitSourceConfigUpdate(
66
66
  plan: { intent: UnsV2Request<'sourceImpact'>; impact: { impactRevision: string } },
67
67
  intentKey: string, client: Tier0Client = createClient(),
68
68
  ) {
@@ -37,7 +37,7 @@ description: 使用 @tier0/sdk-v2 的 client.stream 创建、查询、修改和
37
37
  5. resources=[] 是空冻结资源集,不是通配符。写入本身由服务端校验,需要预览时单独 validate;当前云端程序 timeoutMs 上限 30000。
38
38
  6. HTTPServer 响应时机在 Stream:普通返回等处理/提交完成;HTTP onRequest 内 await ctx.reply 可先回复一次再继续,仍受超时/并发/权限约束,断线不取消但 shutdown 可取消,不保证崩溃重放。
39
39
  7. 仅执行用户要求的生命周期步骤;修改脚本不自动授予发布、停用或删除权限。
40
- 8. 用户要求采集落库时,先创建或复用 Collection 并确定字段映射与稳定 recordId,再绑定已启用 Source 的最新修订;脚本显式 api.uns.use(...).write。部署后核对实际运行并读回记录,返回上游结果、保存草稿或样例预演均不代表已落库。
40
+ 8. 用户要求采集落库时,先创建或复用 Collection 并确定字段映射与稳定 recordId,再绑定 Source 的当前修订;脚本显式 api.uns.use(...).write。部署后核对实际运行并读回记录,返回上游结果、保存草稿或样例预演均不代表已落库。
41
41
 
42
42
  ## 成功与异常
43
43
 
@@ -61,7 +61,7 @@ export async function createHttpStream(
61
61
 
62
62
  先创建或复用真实 Collection,再传入 collectionId;此库存示例的 recordSchema 必须包含 sku(string)与 quantity(number),以业务 SKU 作为稳定 recordId。按真实上游合同修改字段映射、path="inventory"、query 和 60 秒周期;不能只返回 response.data 就声称已经落库。resources=[] 是空冻结资源集,不是通配授权。源地址和凭据留在 Source,不能拷进 acquisition 或脚本。
63
63
 
64
- 此函数保存草稿。完整采集还需启用 Source,取得最新 revision 后更新绑定并部署 Stream;核对本次发布的运行成功,再通过 records:find 读回目标 Collection 的业务记录。acceptedRows 只表示处理器已接受写入,隔离 sampleTest 的 writePreview 不会落库;未完成真实运行和读回前,不得报告采集完成。
64
+ 此函数保存草稿。完整采集使用 Source 创建回执中的 revision 绑定并部署 Stream,无需额外启用或读取;核对本次发布的运行成功,再通过 records:find 读回目标 Collection 的业务记录。acceptedRows 只表示处理器已接受写入,隔离 sampleTest 的 writePreview 不会落库;未完成真实运行和读回前,不得报告采集完成。
65
65
 
66
66
  `upstream.request` 收到完整的 401/403/429/5xx 响应时仍返回 `{status,headers,data}`,不会仅因非 2xx 自动抛错;必须先检查状态,再校验业务数据,最后才缓冲 UNS 写入。此库存示例约定 200 和数组;其他接口按其合同调整,不能把错误响应当业务数据。脚本异常只使用固定文案,不拼接响应正文、认证头或 URL。
67
67
 
@@ -9,7 +9,7 @@
9
9
  | dryRun | boolean | 是 | true 预检;false 提交 |
10
10
  | idempotencyKey | string | 否 | 最多 128 字符 |
11
11
 
12
- 使用已确认目标的回执或查询快照,评估活跃发布/调用依赖。停止运行与删除是两个操作,删除请求不自动授予对 Source/UNS 数据的清理权限。
12
+ 使用已确认目标的回执或查询快照,评估活跃发布/调用依赖。运行中的 Stream 可直接删除,删除会同时撤销后续执行准入;无需先停用。删除请求不自动授予对 Source/UNS 数据的清理权限。
13
13
 
14
14
  ```ts
15
15
  import { createClient, type Tier0Client, type CloudStreamStopView } from '@tier0/sdk-v2';
@@ -6,7 +6,7 @@
6
6
 
7
7
  工业目录优先按实际 ISA-95 设备层级组织:企业 → 工厂/站点 → 区域/车间 → 工作中心(产线等)→ 工作单元。创建前用 `metadataFind` 查询相关已有目录和集合,结合名称与 `parentId` 确认位置,有分页时继续读取。优先复用,只创建业务需要且缺失的层级,通过 `data.parentId` 挂接;不为凑齐层级编造工厂设备,也不按每个 App 重建一套目录。跨区域共享数据放到适合其业务范围的共同祖先;非工业业务按真实业务结构组织,不用 Level 0–4 控制功能层级替代设备层级。整理时不擅自移动已有数据或更改权限。
8
8
 
9
- 例如已有“上海工厂 / 装配车间”,新增一条产线的数据只补缺少的产线节点;车间共享集合可直接放在车间下。App 的 `defineCollection` / ensure 不接收 `parentId`,新集合默认放在 App 专属目录。需要工业目录位置时,由 Agent 在构建期通过 `metadataInsertOne` 的 `data.parentId` 放置集合;为本 App 新建时可同时提供顶层 `appId/alias`,记录唯一 `ownerAppId` 和授权。随后 App 按已有集合 ID 访问,目录位置不决定归属或权限。
9
+ 例如已有“上海工厂 / 装配车间”,新增一条产线的数据只补缺少的产线节点;车间共享集合可直接放在车间下。App 的 `defineCollection` / ensure 使用 `directoryPath` 声明从 Workspace 根开始的真实业务目录名称数组,逐级复用或创建目录;不接收 `parentId`,也不默认放在缺少业务范围的根级 App 目录。允许在已知业务范围下使用 App 或模块目录,例如“工厂 / 产线 / MES / 工单”中的 MES;App 目录不能替代已知的工厂、产线范围。其他元数据创建入口仍使用 `data.parentId`。已有集合可按 ID 引用,目录位置不决定归属或权限;声明路径变化不会自动移动已有集合。
10
10
 
11
11
  | 场景 | 方法 |
12
12
  | --- | --- |
@@ -1,36 +0,0 @@
1
- # 启用、停用 Source
2
-
3
- 方法:`source.state.update`;POST `/openapi/v2/uns/sources/state/update`。
4
-
5
- | 字段 | 类型 | 必填 | 说明 |
6
- | --- | --- | --- | --- |
7
- | dryRun | boolean | 否 | 默认 false 提交;true 仅预检 |
8
- | items[].id / expectedRevision | string / integer | 是 | 当前对象和已知修订 |
9
- | items[].desiredState | enabled/disabled | 是 | 目标状态 |
10
- | items[].confirmedImpactRevision | string | 有引用时 | enable/disable 对应的影响回执 |
11
- | mode / allowPartial | string / boolean | 否 | 单项示例 sync / false |
12
- | idempotencyKey | string | 否 | 本次状态变更的独立意图键 |
13
-
14
- 没有任何 Stream 引用时,一次请求即可提交期望状态。有引用时先以相同修订执行 impact(action=enable 或 disable),按[影响分析](changes.md)遍历并评估,再显式传入回执。服务端在同一事务中检查引用和修订,不会因未传回执而自动同意影响。
15
-
16
- ```ts
17
- import { createClient, type Tier0Client, type UnsV2Request } from '@tier0/sdk-v2';
18
-
19
- export async function setSourceState(
20
- id: string, expectedRevision: UnsV2Request<'sourceImpact'>['expectedRevision'],
21
- desiredState: 'enabled' | 'disabled', intentKey: string,
22
- client: Tier0Client = createClient(), confirmedImpactRevision?: string,
23
- ) {
24
- const receipt = await client.source.state.update({
25
- dryRun: false, mode: 'sync', allowPartial: false, idempotencyKey: intentKey,
26
- items: [{ id, expectedRevision, desiredState,
27
- ...(confirmedImpactRevision === undefined ? {} : { confirmedImpactRevision }) }],
28
- });
29
- if (receipt.data.status !== 'committed' || receipt.data.results?.some(item => item.error)) {
30
- return { state: 'unconfirmed' as const, receipt };
31
- }
32
- return { state: 'committed' as const, receipt };
33
- }
34
- ```
35
-
36
- 响应为 Outcome;committed 且无项错误即可确认期望状态已提交,无需额外 get。accepted/jobId 仅表示受理,继续查询作业。enabled 是期望状态;健康与连通性另用 observations/runtime 检查。提供的回执始终严格校验,确认过期或依赖变化不能自动刷新同意。