@tier0/sdk-v2 0.6.3 → 0.8.0

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 (91) hide show
  1. package/README.md +36 -10
  2. package/dist/cjs/capabilities.d.ts +20 -4
  3. package/dist/cjs/capabilities.js +66 -6
  4. package/dist/cjs/capabilities.js.map +1 -1
  5. package/dist/cjs/client.d.ts +9 -7
  6. package/dist/cjs/client.js +0 -2
  7. package/dist/cjs/client.js.map +1 -1
  8. package/dist/cjs/cloud-transport.d.ts +3 -2
  9. package/dist/cjs/cloud-transport.js +8 -7
  10. package/dist/cjs/cloud-transport.js.map +1 -1
  11. package/dist/cjs/data/bootstrap.js +5 -5
  12. package/dist/cjs/data/bootstrap.js.map +1 -1
  13. package/dist/cjs/data/define.d.ts +6 -0
  14. package/dist/cjs/data/define.js +11 -1
  15. package/dist/cjs/data/define.js.map +1 -1
  16. package/dist/cjs/data/schema.d.ts +1 -0
  17. package/dist/cjs/data/schema.js +7 -0
  18. package/dist/cjs/data/schema.js.map +1 -1
  19. package/dist/cjs/index.d.ts +0 -1
  20. package/dist/cjs/index.js +0 -1
  21. package/dist/cjs/index.js.map +1 -1
  22. package/dist/cjs/openapi/api.generated.d.ts +9 -5
  23. package/dist/cjs/openapi/codecs.generated.d.ts +943 -684
  24. package/dist/cjs/openapi/codecs.generated.js +1 -1
  25. package/dist/cjs/openapi/codecs.generated.js.map +1 -1
  26. package/dist/cjs/openapi/types.generated.d.ts +638 -508
  27. package/dist/cjs/openapi/types.generated.js.map +1 -1
  28. package/dist/cjs/uns-v2/client.js +2 -2
  29. package/dist/cjs/uns-v2/client.js.map +1 -1
  30. package/dist/cjs/uns-v2/generated.d.ts +183 -253
  31. package/dist/cjs/uns-v2/generated.js +16 -19
  32. package/dist/cjs/uns-v2/generated.js.map +1 -1
  33. package/dist/esm/capabilities.d.ts +20 -4
  34. package/dist/esm/capabilities.js +66 -6
  35. package/dist/esm/capabilities.js.map +1 -1
  36. package/dist/esm/client.d.ts +9 -7
  37. package/dist/esm/client.js +0 -2
  38. package/dist/esm/client.js.map +1 -1
  39. package/dist/esm/cloud-transport.d.ts +3 -2
  40. package/dist/esm/cloud-transport.js +8 -7
  41. package/dist/esm/cloud-transport.js.map +1 -1
  42. package/dist/esm/data/bootstrap.js +5 -5
  43. package/dist/esm/data/bootstrap.js.map +1 -1
  44. package/dist/esm/data/define.d.ts +6 -0
  45. package/dist/esm/data/define.js +9 -0
  46. package/dist/esm/data/define.js.map +1 -1
  47. package/dist/esm/data/schema.d.ts +1 -0
  48. package/dist/esm/data/schema.js +8 -1
  49. package/dist/esm/data/schema.js.map +1 -1
  50. package/dist/esm/index.d.ts +0 -1
  51. package/dist/esm/index.js +0 -1
  52. package/dist/esm/index.js.map +1 -1
  53. package/dist/esm/openapi/api.generated.d.ts +9 -5
  54. package/dist/esm/openapi/codecs.generated.d.ts +943 -684
  55. package/dist/esm/openapi/codecs.generated.js +1 -1
  56. package/dist/esm/openapi/codecs.generated.js.map +1 -1
  57. package/dist/esm/openapi/types.generated.d.ts +638 -508
  58. package/dist/esm/openapi/types.generated.js.map +1 -1
  59. package/dist/esm/uns-v2/client.js +2 -2
  60. package/dist/esm/uns-v2/client.js.map +1 -1
  61. package/dist/esm/uns-v2/generated.d.ts +183 -253
  62. package/dist/esm/uns-v2/generated.js +16 -19
  63. package/dist/esm/uns-v2/generated.js.map +1 -1
  64. package/package.json +5 -7
  65. package/skills/tier0-sdk-v2/SKILL.md +14 -4
  66. package/skills/tier0-sdk-v2/tier0-sdk-v2-data/SKILL.md +3 -0
  67. package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/consistency.md +3 -3
  68. package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/errors.md +1 -1
  69. package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/modeling.md +6 -1
  70. package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/read-write.md +3 -1
  71. package/skills/tier0-sdk-v2/tier0-sdk-v2-data/references/schema-evolution.md +6 -4
  72. package/skills/tier0-sdk-v2/tier0-sdk-v2-mq/SKILL.md +1 -1
  73. package/skills/tier0-sdk-v2/tier0-sdk-v2-mq/references/quickstart.md +1 -1
  74. package/skills/tier0-sdk-v2/tier0-sdk-v2-source/SKILL.md +2 -2
  75. package/skills/tier0-sdk-v2/tier0-sdk-v2-source/references/create.md +10 -6
  76. package/skills/tier0-sdk-v2/tier0-sdk-v2-source/references/protocols.md +1 -1
  77. package/skills/tier0-sdk-v2/tier0-sdk-v2-stream/SKILL.md +1 -1
  78. package/skills/tier0-sdk-v2/tier0-sdk-v2-stream/references/create.md +12 -2
  79. package/skills/tier0-sdk-v2/tier0-sdk-v2-stream/references/http-server.md +1 -1
  80. package/skills/tier0-sdk-v2/tier0-sdk-v2-stream/references/lifecycle.md +2 -0
  81. package/skills/tier0-sdk-v2/tier0-sdk-v2-stream/references/read.md +6 -0
  82. package/skills/tier0-sdk-v2/tier0-sdk-v2-system/SKILL.md +7 -2
  83. package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/references/create.md +1 -1
  84. package/skills/tier0-sdk-v2/tier0-sdk-v2-uns/references/namespace.md +4 -0
  85. package/dist/cjs/controls.d.ts +0 -105
  86. package/dist/cjs/controls.js +0 -172
  87. package/dist/cjs/controls.js.map +0 -1
  88. package/dist/esm/controls.d.ts +0 -105
  89. package/dist/esm/controls.js +0 -166
  90. package/dist/esm/controls.js.map +0 -1
  91. package/skills/tier0-sdk-v2/tier0-sdk-v2-controls/SKILL.md +0 -65
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tier0/sdk-v2",
3
- "version": "0.6.3",
3
+ "version": "0.8.0",
4
4
  "description": "Tier0 Cloud new-architecture SDK for TypeScript and JavaScript",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -155,7 +155,7 @@
155
155
  "scripts": {
156
156
  "sync:contracts": "node scripts/sync-contracts.mjs",
157
157
  "generate": "node scripts/generate-openapi.mjs && node scripts/generate-uns-v2.mjs",
158
- "check:contracts": "node scripts/check-contracts.mjs && node --test scripts/retired-stream-probe.test.mjs scripts/retired-http-push.test.mjs scripts/sync-contracts.test.mjs scripts/live-program-contract.test.mjs scripts/sync-http-run-observations.test.mjs && node scripts/generate-openapi.mjs --check && node scripts/generate-uns-v2.mjs --check",
158
+ "check:contracts": "node scripts/check-contracts.mjs && node --test scripts/retired-stream-probe.test.mjs scripts/retired-http-push.test.mjs scripts/retired-controls.test.mjs scripts/retired-public-labels.test.mjs scripts/platform-schema-projection.test.mjs scripts/data-capability-vector.test.mjs scripts/sync-contracts.test.mjs scripts/live-program-contract.test.mjs scripts/sync-http-run-observations.test.mjs && node scripts/generate-openapi.mjs --check && node scripts/generate-uns-v2.mjs --check",
159
159
  "check:skills": "node scripts/check-skills.mjs && node scripts/check-skill-examples.mjs",
160
160
  "typecheck": "tsc --noEmit && tsc -p tsconfig.tests.json",
161
161
  "build": "node scripts/build.mjs",
@@ -199,7 +199,9 @@
199
199
  "vitest": "^4.1.7",
200
200
  "esbuild": "^0.25.0",
201
201
  "yaml": "^2.8.0",
202
- "zod": "^4.0.0"
202
+ "zod": "^4.0.0",
203
+ "ajv": "^8.20.0",
204
+ "ajv-formats": "^3.0.1"
203
205
  },
204
206
  "agents": {
205
207
  "skills": [
@@ -239,10 +241,6 @@
239
241
  "name": "tier0-sdk-v2-system",
240
242
  "path": "./skills/tier0-sdk-v2/tier0-sdk-v2-system"
241
243
  },
242
- {
243
- "name": "tier0-sdk-v2-controls",
244
- "path": "./skills/tier0-sdk-v2/tier0-sdk-v2-controls"
245
- },
246
244
  {
247
245
  "name": "tier0-sdk-v2-agent",
248
246
  "path": "./skills/tier0-sdk-v2/tier0-sdk-v2-agent"
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: tier0-sdk-v2
3
- description: 使用 @tier0/sdk-v2 开发 Tier0 云端 TypeScript/JavaScript 应用:Source 数据源、Stream 处理程序、UNS 集合与记录、App 自有数据(@tier0/sdk-v2/data)、通知、文件存储、MQTT、成员、系统身份与能力、Controls 和 Agent,按任务进入对应子 Skill,并处理配置、身份和框架集成。
3
+ description: 使用 @tier0/sdk-v2 开发或迁移 Tier0 云端 TypeScript/JavaScript 应用:Source 数据源、Stream 处理程序、UNS 集合与记录、App 自有数据(@tier0/sdk-v2/data)、通知、文件存储、MQTT、成员、系统身份与能力和 Agent,按任务进入对应子 Skill,并处理配置、身份和框架集成。
4
4
  ---
5
5
 
6
6
  # Tier0 SDK v2 应用集成
@@ -20,7 +20,6 @@ description: 使用 @tier0/sdk-v2 开发 Tier0 云端 TypeScript/JavaScript 应
20
20
  | 附件、头像、导入导出、生成报告 | `client.files` | [文件](tier0-sdk-v2-files/SKILL.md) |
21
21
  | 实时主题订阅/发布 | `@tier0/sdk-v2/mq`、`client.mqttAuth` | [MQTT](tier0-sdk-v2-mq/SKILL.md) |
22
22
  | Workspace 收件人/成员检索、App 可用成员 | `client.members` | [成员](tier0-sdk-v2-members/SKILL.md) |
23
- | 调用已发布的 Control | `client.controls` | [Controls](tier0-sdk-v2-controls/SKILL.md) |
24
23
  | Agent 作业、运行、分析结果和卡片 | `client.agent` | [Agent](tier0-sdk-v2-agent/SKILL.md) |
25
24
 
26
25
  每个领域 Skill 可单独使用,无需先加载本文件。Source → Stream → UNS 是接入、处理与数据存储三个领域;不是要求每个数据读取场景都创建一套流水线。通知和系统能力独立于这条链路。
@@ -48,12 +47,23 @@ export async function inspectConnection() {
48
47
  | Source、Stream、UNS | 完整 `{ code, msg, requestId, data }` | `UnsV2Error` / `UnsV2OutcomeUnknownError` |
49
48
  | 平台 v2,如 system、members、agent、mqttAuth | 解包后的 `data` | `ApiError` / `TransportError` |
50
49
  | notifications | 直接对象,如 `messageId`、`status` | `ApiError` |
51
- | controls、capabilities | 专属响应对象 | `ControlError` / `CapabilityError` |
50
+ | capabilities | 专属响应对象 | `CapabilityError` |
52
51
 
53
52
  方法存在不等于目标环境已部署或当前 Key 有权限。整数可能为 `bigint`;UNS 任意 JSON 中还可能含 `UnsV2RawNumber`。不要将标识符强制转为 Number;给浏览器返回数据时显式定义精确数字的序列化格式。
54
53
 
55
54
  `createClient()` 的 HTTP 不自动重试。UNS 错误的 `execution` 说明执行到哪一步、`retry.safe` 说明能否用同一请求重发;带 `outcomeUnknown` 的失败应先对账,保留原意图键;取消请求不代表服务端撤销。App 数据层(`@tier0/sdk-v2/data`)会按 `retry.safe` 自动重试,见 App 数据 Skill。Source/Stream 仍使用底层 UNS v2 契约,所以错误类名保留 UNS,不代表它们仍属于数据入口。
56
55
 
56
+ ## 从首版入口迁移
57
+
58
+ - `client.uns.sourceList(body)` → `client.source.documents.list(body)`。
59
+ - `client.uns.sourceDocumentGet(body)` → `client.source.documents.get(body)`。
60
+ - `client.uns.streamProgramDeploy(body)` → `client.stream.programs.deploy(body)`。
61
+ - `client.uns.streamDocumentValidate(body)` → `client.stream.documents.validate(body)`。
62
+ - `client.uns.recordFind(body)` 保持不变。
63
+ - 低层 `createUnsV2Client` 保留完整生成契约,供契约工具使用;业务主入口不再把 Source/Stream 管理混入 `client.uns`。
64
+
57
65
  React/Vue 示例与验签见 [集成参考](references/integration.md)。完成接入时验证目标入口、真实身份、响应解包和错误分支;SDK 本地测试通过不代表环境联调完成。
58
66
 
59
- 公开 HTTP 入口统一为 `/openapi/v2`。Source 配置用 documents.*,Stream 草稿用 documents.*、发布用 programs.deploy;App 数据层提供 insertMany/findOneAnd*/distinct 等便捷方法。业务代码优先使用领域入口,底层 createUnsV2Client 供契约工具使用。
67
+ Stream 请求与消息交互由独立包 `@tier0/sdk` 的 `client.streams.requests` / `client.streams.messages` 提供;本包负责数据访问和 Source/Stream 管理,不重复实现交互 API。Scaffold 分别接入这两个包,不能将本包的 `client.stream` 管理接口当作运行时请求入口。
68
+
69
+ 本包公开 HTTP 入口统一为 `/openapi/v2`。旧的重复操作已退役,不生成兼容调用或回退。Source 配置用 documents.*,Stream 草稿用 documents.*、发布用 programs.deploy;App 数据层仍提供 insertMany/findOneAnd*/distinct 等便捷方法。历史旧写结果未知时先核对业务状态,不能转换路由后盲目重放。
@@ -9,6 +9,8 @@ description: 使用 @tier0/sdk-v2/data 在 App 服务端声明并读写本 App
9
9
 
10
10
  数据读写只在服务端代码(业务 service)里用 `@tier0/sdk-v2/data`:该入口依赖 Node 内置模块,浏览器打包会直接失败。组件和 route 的客户端代码只能从 `@tier0/sdk-v2/data/define` 取集合声明和类型(`defineCollection`、`Doc`、`NewDoc` 等),它不加载数据运行时,可以进浏览器包。需要 `zod` 4。底层调用 SDK 生成的 UNS 操作(`appCollectionsEnsure`、`recordFind`、`recordTransactionExecute` 等),凭证读取平台注入的 TIER0_API_HOST / TIER0_API_KEY。
11
11
 
12
+ 集合声明必须分别填写业务名称 `name` 与稳定程序标识 `alias`,例如 `name: '生产工单'`、`alias: 'production_orders'`。name 是唯一界面名称,没有 displayName 或 alias 回退;ensure 通过 alias 找回原集合,不覆盖用户改名。
13
+
12
14
  ## 按任务加载参考
13
15
 
14
16
  | 用户任务 | 参考 |
@@ -30,6 +32,7 @@ import { defineCollection, type Doc } from '@tier0/sdk-v2/data/define';
30
32
 
31
33
  export const workOrders = defineCollection({
32
34
  alias: 'work_orders',
35
+ name: '生产工单',
33
36
  description: 'Production work orders',
34
37
  schema: z.object({
35
38
  code: z.string(),
@@ -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', schema: z.object({ sku: z.string(), available: z.number().int().min(0).meta({ format: 'int32' }) }) });
18
- const reservations = defineCollection({ alias: 'reservations', schema: z.object({ sku: z.string(), qty: z.number().int().min(1).meta({ format: 'int32' }), orderId: z.string() }) });
19
- const counters = defineCollection({ alias: 'counters', schema: z.object({ value: z.number().int().meta({ format: 'int32' }) }) });
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' }) }) });
20
20
  const data = createDataClient({ collections: [stock, reservations, counters] });
21
21
 
22
22
  /** 扣减库存并写预留:库存不足时整体不提交。 */
@@ -58,7 +58,7 @@ export function toResponse(error: unknown): Response {
58
58
 
59
59
  | code | 含义与处理 |
60
60
  | --- | --- |
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` / `NAME_IN_USE` → 换 alias;`INVALID_INDEX` → 修正索引 |
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
62
  | `ANNOTATION_NOT_APPLIED`(`ignored` 提示) | 不是错误:ensure 保留已存储的描述,用 `schema:replace` 应用修改 |
63
63
  | `REQUIRED_NOT_ENFORCED`(警告) | 声明要求的字段在存储中仍是可选;改回可选,或回填后用 `schema:replace` 收窄 |
64
64
  | `INDEX_BUILD_FAILED`(警告) | 已有记录违反新唯一索引;修正重复数据,下次 ensure 会重试建索引 |
@@ -1,6 +1,8 @@
1
1
  # 声明集合与建模
2
2
 
3
- `defineCollection({ alias, 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, 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
+
5
+ `name` 是必填的业务名称,界面直接展示,例如“生产工单”;1–63 个 Unicode 字符,无首尾空白,不含 `/` 或空字符。`alias` 是独立、稳定的程序标识,例如 `production_orders`,改业务名称不改 alias 或集合 ID。名称应表达实际用途,语言遵循用户偏好和已有业务术语,不附加无业务含义的随机串、时间戳或 ID。没有 displayName,也不从 alias 回退生成名称。ensure 只在首次创建时写入 name;命中已有 alias 时保留用户当前名称,显式改名走 MCP 元数据更新。
4
6
 
5
7
  ```ts
6
8
  import { z } from 'zod';
@@ -8,6 +10,7 @@ import { defineCollection, type Doc } from '@tier0/sdk-v2/data/define';
8
10
 
9
11
  export const customers = defineCollection({
10
12
  alias: 'customers',
13
+ name: '客户',
11
14
  schema: z.object({ name: z.string(), tier: z.enum(['A', 'B', 'C']) }),
12
15
  indexes: [{ path: 'name' }],
13
16
  seed: [
@@ -18,6 +21,7 @@ export const customers = defineCollection({
18
21
 
19
22
  export const salesOrders = defineCollection({
20
23
  alias: 'sales_orders',
24
+ name: '销售订单',
21
25
  description: 'Customer sales orders',
22
26
  schema: z.object({
23
27
  code: z.string(),
@@ -90,6 +94,7 @@ import { defineCollection } from '@tier0/sdk-v2/data/define';
90
94
 
91
95
  export const loginCodes = defineCollection({
92
96
  alias: 'login_codes',
97
+ name: '登录验证码',
93
98
  schema: z.object({ phone: z.string(), code: z.string(), issuedAt: z.number().int().meta({ format: 'int32' }) }),
94
99
  // issuedAt 存 Date.now();签发 5 分钟后删除。
95
100
  indexes: [{ path: 'phone' }, { path: 'issuedAt', expireAfterSeconds: 300 }],
@@ -8,6 +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
12
  schema: z.object({
12
13
  code: z.string(),
13
14
  status: z.enum(['DRAFT', 'RELEASED', 'DONE']),
@@ -63,6 +64,7 @@ import { createDataClient, defineCollection } from '@tier0/sdk-v2/data';
63
64
 
64
65
  const workOrders = defineCollection({
65
66
  alias: 'work_orders',
67
+ name: '生产工单',
66
68
  schema: z.object({ code: z.string(), status: z.enum(['DRAFT', 'RELEASED', 'DONE']) }),
67
69
  indexes: [{ path: 'code', unique: true }, { path: 'status' }],
68
70
  });
@@ -161,7 +163,7 @@ export async function bumpAll(items: Collection<{ status: string; views: number
161
163
  import { z } from 'zod';
162
164
  import { createDataClient, defineCollection, runAsApp, type SubscriptionEvent } from '@tier0/sdk-v2/data';
163
165
 
164
- const tickets = defineCollection({ alias: 'tickets', schema: z.object({ title: z.string(), status: z.enum(['OPEN', 'DONE']) }) });
166
+ const tickets = defineCollection({ alias: 'tickets', name: '问题工单', schema: z.object({ title: z.string(), status: z.enum(['OPEN', 'DONE']) }) });
165
167
  const data = createDataClient({ collections: [tickets] });
166
168
 
167
169
  /** 一个进程一条 App 级订阅,在进程内扇出给浏览器。 */
@@ -5,7 +5,8 @@ Preview 与部署后的 App 共用集合,较旧的部署版本随时可能用
5
5
  | 变更 | 做法 |
6
6
  | --- | --- |
7
7
  | 新增可选字段(任意层级,含数组元素内)、新增枚举值、字段改为可选、`int` → `number`、放宽或去掉 `min`/`max`、新增索引 | 修改声明;下次访问时 ensure 生效 |
8
- | 修改 `.describe()` 文本或集合 `description` | 修改声明,然后通过 tier0 MCP 执行 `schema:replace`,请求体用 `compileCollection(declaration)` 的结果(先 `dryRun`:期望 `widened`;`narrowed` 说明存储里还有该声明之外的内容,例如其他版本的字段——合并进请求体或停止)。ensure 保留已存储的描述并报告 `ANNOTATION_NOT_APPLIED`;新字段和新集合在创建时带上描述 |
8
+ | 修改集合名称 | 通过 tier0 MCP 的 metadata:updateOne 显式修改 name,保留 alias/ID;修改声明后重复 ensure 不覆盖已有名称 |
9
+ | 修改 `.describe()` 文本或集合 `description` | 修改声明,然后通过 tier0 MCP 执行 `schema:replace`,请求体从 `compileCollection(declaration)` 中只选 `alias`、`recordSchema`、`indexes`、`description`,不传 `name`(先 `dryRun`:期望 `widened`;`narrowed` 说明存储里还有该声明之外的内容,例如其他版本的字段——合并进请求体或停止)。ensure 保留已存储的描述并报告 `ANNOTATION_NOT_APPLIED`;新字段和新集合在创建时带上描述 |
9
10
  | 新增每条记录都必须有的字段 | 先以 `.optional()` 加入,所有写入路径都写它,回填旧记录,等部署版本也写入后再用 `schema:replace` 改为必填 |
10
11
  | 删除或重命名字段 | 代码不再使用旧字段(它可以留在存储里);新名字就是新的可选字段。通过 tier0 MCP 一次性回填(`records:updateMany` 配 `$rename` / `$set`) |
11
12
  | 修改字段类型 | 换一个新字段名,回填,读取时以旧字段兜底。ensure 从不转换类型(`SCHEMA_CONFLICT`) |
@@ -15,7 +16,7 @@ Preview 与部署后的 App 共用集合,较旧的部署版本随时可能用
15
16
 
16
17
  ## 运行时代码与 worker 操作的边界
17
18
 
18
- `schema:replace`、`archive`、`restore` 由 worker 通过 tier0 MCP 显式执行(`/uns/app/collections/schema:replace`、`archive`、`restore`),App 运行时代码不调用。数据层导出的 `compileCollection(declaration)` 给出与运行时 ensure 完全一致的条目 `{ alias, recordSchema, indexes, description? }`,可作为 ensure `dryRun` 或 `schema:replace` 请求体的基础。
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 的完整结果直接作为替换请求。
19
20
 
20
21
  ```ts
21
22
  import { z } from 'zod';
@@ -23,13 +24,14 @@ import { compileCollection, defineCollection } from '@tier0/sdk-v2/data';
23
24
 
24
25
  const tickets = defineCollection({
25
26
  alias: 'tickets',
27
+ name: '问题工单',
26
28
  schema: z.object({ title: z.string().describe('Short summary'), severity: z.enum(['LOW', 'HIGH']).optional() }),
27
29
  });
28
30
 
29
31
  /** schema:replace 请求体的基础;expectedRevision 取自 ensure 结果的集合 revision。 */
30
32
  export function replaceBody(expectedRevision: number) {
31
- const { alias, recordSchema, indexes } = compileCollection(tickets);
32
- return { alias, recordSchema, indexes, expectedRevision, dryRun: true };
33
+ const { alias, recordSchema, indexes, description } = compileCollection(tickets);
34
+ return { alias, recordSchema, indexes, ...(description ? { description } : {}), expectedRevision, dryRun: true };
33
35
  }
34
36
  ```
35
37
 
@@ -69,7 +69,7 @@ export async function publishMeasurement(mq: Tier0MQClient, topic: string, value
69
69
  }
70
70
  ```
71
71
 
72
- publish 目前在建立连接并调用 MQTT 库发布后返回,不等待业务执行确认,也不保证已收到 broker PUBACK。需要闭环时使用业务 ACK 主题/关联 ID 或 Control 契约,不能把 Promise resolve 当成设备执行完成。
72
+ publish 目前在建立连接并调用 MQTT 库发布后返回,不等待业务执行确认,也不保证已收到 broker PUBACK。需要闭环时使用业务 ACK 主题/关联 ID 或独立交互 SDK 的 Stream 请求合同,不能把 Promise resolve 当成设备执行完成。
73
73
 
74
74
  订阅内部 QoS 为 1;发布默认 QoS 0、retain=false。业务消费者需考虑重复消息。对象 payload 经 JSON.stringify,不能直接包含 bigint;按协议约定编码精确值,不假设 MQTT 自动使用 UNS 的无损 codec。
75
75
 
@@ -58,7 +58,7 @@ export async function publishEvent(mq: Tier0MQClient, topic: string, eventId: st
58
58
  }
59
59
  ```
60
60
 
61
- payload 必须符合实际主题契约;对象用 JSON.stringify,不可包含 bigint。publish resolve 不证明已收 PUBACK,更不证明设备执行;业务闭环用关联 ID/ACK 或 Control。订阅 QoS 1,发布默认 QoS 0/retain=false,消费端考虑重复消息。
61
+ payload 必须符合实际主题契约;对象用 JSON.stringify,不可包含 bigint。publish resolve 不证明已收 PUBACK,更不证明设备执行;业务闭环用关联 ID/ACK 或独立交互 SDK 的 Stream 请求。订阅 QoS 1,发布默认 QoS 0/retain=false,消费端考虑重复消息。
62
62
 
63
63
  ## 事件、背压和鉴权
64
64
 
@@ -26,13 +26,13 @@ Source 维护连接配置、认证引用与源状态。脚本处理用 [Stream](
26
26
  1. Source 配置统一使用 documents.*,原生文档为 `{name,protocol,config}`,HTTP API 显式指定 `config.role`。HTTP 统一为 `protocol: http-api` + `config.role: client/server`;Client 主动请求,Server 接收入站请求,不能互换。HTTPServer 统一接收入站请求,固定前缀后的路由和响应时机属于 Stream。
27
27
  2. 已有 ID 和 revision 时优先用 documents.update 的 patch,避免额外 get;完整替换 document 前读取完整文档,安全列表摘要不能无损回写。原生 ID 是十进制字符串,revision 原样传递,不转 Number。
28
28
  3. 现有 Source 修改、启停、删除都要 expectedRevision。没有任何 Stream 引用时可直接提交;有草稿、活动、历史或不可见引用时仍需 confirmedImpactRevision。先形成精确意图,再读取全部影响分页;回执不可合成、跨意图复用或静默刷新。提供的回执始终严格校验,不自动获取并同意影响。
29
- 4. 配置中保留认证/TLS 引用,不写明文秘密;HTTP Client 新凭据走创建专用 credentials;Server 不携带 Client auth/TLS 配置。
29
+ 4. 配置中保留认证/TLS 引用,不写明文秘密;HTTP Client 新凭据走创建专用 credentials,普通创建省略 credentials.method(服务端默认 REQUEST 基址绑定),真实 method/path/query 由 Stream 定义。Basic/Bearer 显式凭据方法限制精确 URL 和方法,OAuth 旧 GET 保留既有兼容语义,不自动扩大旧授权;Server 不携带 Client auth/TLS 配置。
30
30
  5. desiredState 不代表实际健康,保存配置不代表 Stream 已切换至新 configRevision。运行状态另行读取。
31
31
 
32
32
  ## 返回值与异常
33
33
 
34
34
  所有调用返回完整 code/msg/requestId/data。documents.create/update 的预检返回 validated/sideEffects,提交返回完整 Source view,可直接使用其中的文档和 revision,无需额外 get;批量状态/删除返回 Outcome。只有 committed 且无项错误才可报告提交成功;accepted/jobId 继续查询作业。预检按需使用,不是每次提交的必经步骤。
35
35
 
36
- 需要查看历史用 source.history({id,beforeRevision,size}),按 nextBeforeRevision 继续;探测用 source.probe,其外部系统访问应属于当前任务。连接配置修改使用 documents.update,遵守同一影响确认契约。
36
+ 需要查看历史用 source.history({id,beforeRevision,size}),按 nextBeforeRevision 继续;探测用 source.probe,其外部系统访问应属于当前任务。配置校验或连接探测成功不证明某个 Stream 请求已获授权或采集成功,实际结果看对应发布的 runs。连接配置修改使用 documents.update,遵守同一影响确认契约。
37
37
 
38
38
  UnsV2Error 表示已知拒绝;UnsV2OutcomeUnknownError 表示可能已写入。保存 requestId、原请求和意图键,先查详情/历史/作业对账。取消等待不撤销服务端操作;SDK 不自动重试。权限不足不能解释为资源不存在。
@@ -41,7 +41,9 @@ HTTP Server 同样使用 `protocol: http-api`,角色为 `config.role: server`
41
41
 
42
42
  ## HTTP Client Bearer 凭据
43
43
 
44
- token 从服务端密钥提供者取得,不写入脚本、文档、日志或仓库。
44
+ Source 保存 API 基址和认证;实际请求的 method/path/query 由 Stream 设置。例如 endpoint 使用 `https://inventory.example.com/api/v1`,再由 [Stream 示例](../../tier0-sdk-v2-stream/references/create.md)请求 `GET inventory`。token 从服务端密钥提供者取得,不写入脚本、文档、日志或仓库。
45
+
46
+ 普通创建省略 `credentials.method`,服务端按 `REQUEST` 绑定 Source 基址。REQUEST 是凭据适用范围标记,不是发送给上游的 HTTP 方法;实际 GET 请求仍由执行宿主逐次授权。
45
47
 
46
48
  ```ts
47
49
  import { createClient, type Tier0Client } from '@tier0/sdk-v2';
@@ -53,7 +55,7 @@ export async function createAuthenticatedHttpSource(
53
55
  const saved = await client.source.documents.create({
54
56
  dryRun: false, idempotencyKey: intentKey,
55
57
  document: { name, protocol: 'http-api', config: { role: 'client', endpoint, options: {} } },
56
- credentials: { method: 'GET', kind: 'bearer', material: { token } },
58
+ credentials: { kind: 'bearer', material: { token } },
57
59
  });
58
60
  if (!('id' in saved.data)) throw new Error('未返回 Source ID');
59
61
  return { receipt: saved, current: saved.data };
@@ -92,12 +94,14 @@ Server 的固定前缀 `/ingest/v1/{workspaceId}/{sourceId}` 和 Bearer 要求
92
94
 
93
95
  | kind | material | method |
94
96
  | --- | --- | --- |
95
- | basic | username、password | 省略时 REQUEST;可显式绑定受限 HTTP 方法 |
96
- | bearer | token | 省略时 REQUEST;上方 GET 示例保持明确的方法限制 |
97
+ | basic | username、password | 省略时 REQUEST;显式方法绑定精确 URL 和该方法 |
98
+ | bearer | token | 省略时 REQUEST;显式方法绑定精确 URL 和该方法 |
97
99
  | apiKey | headerName、value | 省略或 REQUEST |
98
- | oauth2ClientCredentials | tokenUrl、clientId、clientSecret;可选 scope、clientAuthentication | 省略、REQUEST 或 GET |
100
+ | oauth2ClientCredentials | tokenUrl、clientId、clientSecret;可选 scope、clientAuthentication | 普通创建省略;旧 GET 保留既有兼容语义 |
101
+
102
+ Basic/Bearer 显式 `credentials.method: 'GET'` 表示凭据只用于所绑定精确 URL 的 GET,不表示该基址下所有 GET 都可使用:绑定 `/api/v1` 不授权 `/api/v1/inventory?limit=5000`。仅在任务明确需要该限制时传入;已有绑定保持原限制,不自动迁移或在拒绝后回退 REQUEST。
99
103
 
100
- OAuth 的 `clientAuthentication` 支持 `client_secret_post`(默认)和 `client_secret_basic`。REQUEST 表示绑定 Source 基址,实际路径和方法仍由执行宿主逐次授权;显式方法的已有绑定不能解释成 REQUEST 的基址范围。vendor SESSION 合同独立保留,不是此 inline 创建的 method,也不新增独立公开凭据 API。
104
+ OAuth 的 `clientAuthentication` 支持 `client_secret_post`(默认)和 `client_secret_basic`;method 接受省略、REQUEST 或旧 GET,旧 GET 保留现有兼容语义,不能把 Basic/Bearer 的精确 URL 规则跨认证类型套用。普通创建仍省略 method。vendor SESSION 合同独立保留,不是此 inline 创建的 method,也不新增独立公开凭据 API。
101
105
 
102
106
  ```ts
103
107
  import { createClient, type Tier0Client, type CloudSourceCreateCredentials } from '@tier0/sdk-v2';
@@ -39,4 +39,4 @@ HTTP Client 示例不能用于 Server。HTTPServer 通过 `source.documents.*`
39
39
 
40
40
  Cloud 的角色位于 `config.role`;Enterprise 投影的顶层 `role` 不能直接抄入 Cloud 管理文档。Server 配置见[创建](create.md),以 `http://managed` 和空 options 保存,不从 Client 模板推导。
41
41
 
42
- HTTP Client 的单条 inline 认证支持 basic / bearer / apiKey / oauth2ClientCredentials,method 省略时由服务端按 REQUEST 绑定 Source 基址;显式方法限制保持不变。APIKey 材料为 headerName/value,OAuth 可提供 clientAuthentication。见[创建](create.md)。
42
+ HTTP Client 的单条 inline 认证支持 basic / bearer / apiKey / oauth2ClientCredentials。普通创建省略 credentials.method,由服务端按 REQUEST 绑定 Source 基址;实际 method/path/query 在 Stream 设置。Basic/Bearer 显式凭据方法同时限制精确 URL,不覆盖基址下的子路径;存量绑定保持限制,不自动扩大权限。APIKey 材料为 headerName/value,OAuth 可提供 clientAuthentication,旧 OAuth GET 保留既有兼容语义。见[创建](create.md)。
@@ -26,7 +26,7 @@ description: 使用 @tier0/sdk-v2 的 client.stream 创建、查询、修改和
26
26
  | Runtime program | name、script 字符串、sourceBindings、timeoutMs | documents.create/update/validate({program}) |
27
27
  | 云端原生文档 | name、model、script 对象、sources、entries | documents.create/get/update/validate |
28
28
 
29
- 程序必须是完整 `module.exports = api => { ... }`,执行器 API 是 api.sources/api.uns/api.stream,与 npm 包的 client.source 不同。Runtime sourceBindings 为 sourceId/configRevision/acquisition;原生 sources 还使用 alias/resources。连接凭据不进脚本或 acquisition,秘密材料由 Source 执行宿主管理,不通过脚本读取、记录或返回。定时入口使用 api.stream.every;npm 的 client.controls 管理入口不作为 Goja 的 Stream 注册参数。
29
+ 程序必须是完整 `module.exports = api => { ... }`,执行器 API 是 api.sources/api.uns/api.stream,与 npm 包的 client.source 不同。Runtime sourceBindings 为 sourceId/configRevision/acquisition;原生 sources 还使用 alias/resources。连接凭据不进脚本或 acquisition,秘密材料由 Source 执行宿主管理,不通过脚本读取、记录或返回。定时入口使用 api.stream.every,不传播旧注册别名;App 请求与消息交互由独立 @tier0/sdk 的 client.streams.requests/messages 提供,不作为 Goja 注册参数。
30
30
 
31
31
  ## 核心约束
32
32
 
@@ -15,6 +15,8 @@
15
15
 
16
16
  先按[Source 查询](../../tier0-sdk-v2-source/references/read.md)选定真实 HTTP Client Source。脚本的 api.sources 属于平台执行器,与应用中的 client.source 不同。sourceId 保持字符串,configRevision 使用 Source 创建、更新回执或完整查询得到的修订。已有回执时直接传入 source;没有快照时再调用 source.documents.get({id,view:"full"}) 并检查 data.document。省略 `config.role` 时按正式协议目录默认为 client;Server 走入站语义,不能创建为主动请求程序。
17
17
 
18
+ Source 负责基址和认证,Stream 负责请求的 method/path/query。以下示例与 [Source Bearer 创建](../../tier0-sdk-v2-source/references/create.md#http-client-bearer-凭据)配合:Source endpoint 为 `https://inventory.example.com/api/v1`,凭据使用默认基址绑定;实际请求是 `GET https://inventory.example.com/api/v1/inventory?limit=5000`。已有 Source 的凭据若只绑定精确 URL `/api/v1` 的 GET,不能用于此子路径;不要通过改写请求为 REQUEST 或自动扩大凭据授权来绕过拒绝。
19
+
18
20
  ```ts
19
21
  import { createClient, type Tier0Client, type RuntimeStreamDefinition, type CloudSourceDocumentView } from '@tier0/sdk-v2';
20
22
 
@@ -34,7 +36,9 @@ export async function createHttpStream(
34
36
  'module.exports = api => {',
35
37
  ' const upstream = api.sources.use({ id: ' + JSON.stringify(sourceId) + ', resources: [] });',
36
38
  ' api.stream.every({ id: "tick", intervalMs: 60000 }, async () => {',
37
- ' const response = await upstream.request({ method: "GET", path: "orders" });',
39
+ ' const response = await upstream.request({ method: "GET", path: "inventory", query: { limit: "5000" } });',
40
+ ' if (response.status !== 200) throw new Error("Upstream inventory HTTP request failed");',
41
+ ' if (!Array.isArray(response.data)) throw new Error("Unexpected upstream inventory response format");',
38
42
  ' return response.data;',
39
43
  ' });',
40
44
  '};',
@@ -48,10 +52,16 @@ export async function createHttpStream(
48
52
  }
49
53
  ```
50
54
 
51
- 将 path="orders"、60 秒周期改为业务实际值。示例保存草稿,不部署,也不将返回值自动写入 UNS;需要写入集合时按执行器能力定义 api.uns 操作。resources=[] 是空冻结资源集,不是通配授权。源地址和凭据留在 Source,不能拷进 acquisition 或脚本。
55
+ 将 path="inventory"、query 和 60 秒周期改为业务实际值。示例保存草稿,不部署,也不将返回值自动写入 UNS;需要写入集合时按执行器能力定义 api.uns 操作。resources=[] 是空冻结资源集,不是通配授权。源地址和凭据留在 Source,不能拷进 acquisition 或脚本。
56
+
57
+ `upstream.request` 收到完整的 401/403/429/5xx 响应时仍返回 `{status,headers,data}`,不会仅因非 2xx 自动抛错;必须先检查状态,再校验业务数据,最后才缓冲 UNS 写入。此库存示例约定 200 和数组;其他接口按其合同调整,不能把错误响应当业务数据。脚本异常只使用固定文案,不拼接响应正文、认证头或 URL。
58
+
59
+ 运行记录与 SDK 管理请求异常是两层:创建/发布成功不代表上游成功。支持 HTTP 响应诊断的后端可在失败或未知运行中返回 `SOURCE_HTTP_401`、`stage:source_response`;401 应核对或更新上游拒绝的凭据,403 核对上游授权,429 核对采集频率。不要自动刷新凭据、扩大权限或立即重试。旧后端可能仍只返回 `RESULT_UNKNOWN`;仅修改 throw 文案不能让它保留 HTTP 状态。详见[运行诊断](read.md)。
52
60
 
53
61
  创建 data 为 CloudStreamDocumentView 或预检结果,实际保存返回 id、revision 和完整 document;预检不生成 ID。需要展示预检时单独调用 documents.validate({program});普通保存无需先校验再保存。当前后端创建/校验需要注册 Worker 可用。TEMPORARILY_UNAVAILABLE 不代表脚本一定错误。
54
62
 
63
+ validate 检查配置、程序和可确定的绑定问题,不执行任意业务分支,也不证明上游可达。尤其是运行时拼接的 path/query,仍需在授权范围内执行代表性采集,并检查本次发布的 runs 与业务结果;一次成功也只覆盖实际执行的请求。Source 连接探测和 isolated-sample 都不能替代真实采集验证。
64
+
55
65
  创建结局未知时先按已知 ID 或业务标识核对状态;允许重试时保留原请求与幂等键。
56
66
 
57
67
  原生编辑器形态 `documents.create({dryRun, document})` 的 document 为 name/model/script对象/sources/entries,script 为 `{code,timeoutMs}`,sources 用 alias/sourceId/configRevision/resources/acquisition。不要把上面的 script 字符串直接塞入原生文档。
@@ -84,6 +84,6 @@ export async function createHttpServerEarlyReplyStream(
84
84
  }
85
85
  ```
86
86
 
87
- `ctx.reply` 只用于 HTTP `api.stream.onRequest`,一次执行只回复一次;不要在 `every`、Control 或普通事件 handler 中调用,也不增加 npm App API 或公开 HTTP endpoint。示例先发 202 再写 Record,202 表示已回复接受,不能报告写入或提交成功;失败通过 Stream 运行记录检查,不能再发送第二份响应。
87
+ `ctx.reply` 只用于 HTTP `api.stream.onRequest`,一次执行只回复一次;不要在 `every`或普通事件 handler 中调用,也不增加 npm App API 或公开 HTTP endpoint。示例先发 202 再写 Record,202 表示已回复接受,不能报告写入或提交成功;失败通过 Stream 运行记录检查,不能再发送第二份响应。
88
88
 
89
89
  回复后的处理仍受 Stream timeout(本例 5000 ms)、并发上限和实时权限约束。客户端断线不会取消已回复后的执行,服务 shutdown 仍可取消。进程崩溃不保证重放;业务若需持久投递、去重或恢复,必须另行设计,不能把 `ctx.reply` 当作持久队列。凭据和权限均沿用现有 Source/Stream 合同。
@@ -2,6 +2,8 @@
2
2
 
3
3
  创建并发布时,将 [创建](create.md) 或 [修改](update.md) 回执中的草稿直接交给 [发布](deploy.md),再用发布回执的 activeRelease [观察运行](read.md#观察本次发布),无需在发布前后各 get 一次。只有缺少当前草稿、处理版本冲突或需要最新状态时才 [查询](read.md)。停用、回滚和 [删除](delete.md) 按任务选择,不自动执行整套生命周期。
4
4
 
5
+ HTTP 采集的常规顺序是选定或创建 Source → 用 Source 回执绑定并保存 Stream → 发布 → 观察该发布的真实采集。Source 保存基址和认证,Stream 指定 method/path/query。预检按需使用;静态校验只能发现可确定的问题,脚本动态请求仍需通过代表性实际运行验证,不能把配置通过、网络可达或样例通过等同于采集成功。
6
+
5
7
  ## 原生文档样例测试
6
8
 
7
9
  方法:stream.sampleTest;输入 document、entryId、input。document 来自 documents.get 的完整草稿,entryId 必须在该文档注册的 entries 内。
@@ -53,3 +53,9 @@ export async function readPublishedRuns(
53
53
  ```
54
54
 
55
55
  此函数只读一页。items 为空但 nextCursor 非空时,下一页仍可能有匹配记录;达到调用方的观察时限就报告“尚未观察到”。匹配记录也要检查 status、entryId 和时间,不能把存在记录当成执行成功。历史幂等回执若缺少 activeRelease,应保留为未确认,不能用当前 head 的发布号代替。
56
+
57
+ 失败或结果未知的运行可带 `error:{code,stage,message,repair,sourceRequest?}` 安全诊断。`SOURCE_TARGET_SCOPE_MISMATCH`、`stage:validate_binding` 表示请求超出 Source 凭据范围,应检查绑定基址与实际 method/path/query,保留原授权限制。`sourceRequest:notDispatched` 只证明失败的这一次 Source 请求未发送,不能推断整个 Stream 未开始或此前没有写入。
58
+
59
+ `EXECUTION_FAILED` 和 `RESULT_UNKNOWN` 是安全通用诊断,分别保留失败和结果未知的区别。按 code/stage 判断,展示服务端 message/repair;没有 error 的旧记录仍按 status 解释,不补造原因,也不因可重试猜测而自动重新执行。运行诊断不包含 token、凭据引用或完整请求 URL。
60
+
61
+ 支持 HTTP 响应诊断的后端还会保留 `SOURCE_HTTP_<三位状态码>`(300–599)、`stage:source_response`,例如 `SOURCE_HTTP_401`。这是可信宿主在本轮观察到的非 2xx 响应;不是脚本错误文案,也不证明它是所有后续失败的唯一原因。`status:unknown` 可以同时带此诊断:已知上游返回 401,不等于整轮外部副作用已确定或可以安全重试。只读查询 runs 的 HTTP 200 也不等于采集成功。运行最终成功时不会附加此前已被脚本处理的 HTTP 错误。
@@ -43,13 +43,18 @@ info 返回 name/version/mqttBroker/capabilities,字段可能省略。MQTT bro
43
43
  | 字段/状态 | 判断 |
44
44
  | --- | --- |
45
45
  | releaseEnabled | 平台/Workspace 发布开关,不代表运行时已准备好 |
46
- | allowed | 当前身份的授权摘要,不是所有具体 Control 的执行许可 |
46
+ | allowed | 当前身份的授权摘要,不是某个具体操作的执行许可 |
47
47
  | enabled、state=ready | 服务端评估就绪 |
48
48
  | availability=available | 能力可用性,不是设备在线、业务执行或 ACK |
49
49
  | disabled / not_supported / unavailable | 展示具体不可用原因,不强制调用 |
50
50
  | readiness_unknown | 保留未知状态,不能按成功处理 |
51
+ | operations | Source / Stream 各操作独立判断;整体不可用不等于读取或安全停用不可用 |
52
+ | reasonCode / checkedAt / validUntil | 保留服务端原因及证明时间,过期后重新发现,不把旧证明当成持续就绪 |
53
+ | requiresResourceCheck | 实际调用仍要检查具体资源的权限、配置和依赖 |
51
54
 
52
- 能力目录当前识别 namespace.v2、source、stream、control.v1、control.invoke.v1。Source/Stream 的 contractVersions 使用 openapi/v2,不要求或返回解释器版本。contractVersions、apiContractVersions、sdkContractVersions 是契约标签,不是 npm 包版本;productVersion 未配置时可缺省。
55
+ 本数据 SDK 的能力目录识别 namespace.v2、source、stream;其他 SDK 的可选交互能力按各自合同处理,不阻断 Namespace 发现。Source/Stream 的 contractVersions 使用 openapi/v2,不要求或返回解释器版本。contractVersions、apiContractVersions、sdkContractVersions 是契约标签,不是 npm 包版本;productVersion 未配置时可缺省。顶层要求受支持的核心 OpenAPI v2 合同,已识别能力也必须满足其合同;不要求全局交互 SDK 标签,不把未知能力投影为已支持。
56
+
57
+ 发现统一使用 `POST /openapi/v2/capabilities/discover`,请求为 `{}`,不接受 Workspace 参数。Cloud / Enterprise 返回同一合同;此 SDK 只消费 Namespace、Source、Stream 管理能力;App 交互域由 App SDK 提供,其版本不作为管理接口的前置条件。SDK 只投影已知操作与公开诊断字段;未知扩展不被解释为就绪,不自动重试。
53
58
 
54
59
  ## 定位问题
55
60
 
@@ -4,7 +4,7 @@
4
4
 
5
5
  集合 data 必须包含 name、recordSchema,可选 description、parentId、sampleSchema、indexes、semantics。parentId=null 表示根目录。索引创建后仍需确认状态为 ready。
6
6
 
7
- 目录、集合和主题使用现有 name 填写易读的真实业务名称,语言遵循用户偏好和已有术语;不要用程序 alias、随机串或 ID 代替名称,也不新增展示名称字段。工业目录优先复用已有 ISA-95 设备层级,只创建业务已知的层级;修改既有名称须有用户授权,不借整理名称改写 ID、alias、权限或移动数据。
7
+ 目录、集合和主题使用现有 name 填写易读的真实业务名称,语言遵循用户偏好和已有术语;不要用程序 alias、随机串或 ID 代替名称,不提供 displayName 或名称回退。name 为 1–63 个 Unicode 字符,无首尾空白,不含 `/` 或空字符;同目录重名时选择明确的业务名称,不自动拼接随机后缀。工业目录按[命名空间](namespace.md)指导复用已有 ISA-95 设备层级,只创建业务已知的层级,非工业业务不强套工厂结构;修改既有名称须有用户授权,不借整理名称改写 ID、alias、权限或移动数据。
8
8
 
9
9
  ```ts
10
10
  import { createClient, type Tier0Client, type UnsV2Request } from '@tier0/sdk-v2';
@@ -2,6 +2,10 @@
2
2
 
3
3
  目录、Topic、Collection 使用同一套 metadata* 方法,通过 kind 指定类型。Source/Stream 的启停、连接和发布仍使用各自专用生命周期接口;不允许把系统任务、凭据或发布历史当作普通元数据修改。
4
4
 
5
+ 目录、Collection 和 Topic 直接以 name 展示业务名称,不提供 displayName。系统引用用 ID,App 程序标识用独立 alias;名称改动不影响它们。不要用随机串、时间戳或 ID 拼接名称,也不自动用无意义后缀解决重名。
6
+
7
+ 工业目录优先按实际 ISA-95 设备层级组织:企业 → 工厂/站点 → 区域/车间 → 工作中心(产线等)→ 工作单元。先查询并复用已有目录,只建立业务已知且有意义的层级;通过 parentId 挂接,跨区域共享数据放到共同祖先。不要编造工厂设备,也不用 Level 0–4 控制功能层级替代设备层级;非工业业务按真实业务结构组织。整理时不擅自移动已有数据或更改权限。
8
+
5
9
  | 场景 | 方法 |
6
10
  | --- | --- |
7
11
  | 单条/批量新增 | metadataInsertOne / metadataBulkWrite 的 insertOne 项 |
@@ -1,105 +0,0 @@
1
- import { type HttpClient, type ClientConfig } from './openapi/client.js';
2
- import { type CloudTransportConfig } from './cloud-transport.js';
3
- import { type UnsV2Execution, type UnsV2Retry } from './uns-v2/client.js';
4
- import type { UnsV2JSONValue } from './uns-v2/generated.js';
5
- /** Control convenience API over the canonical UNS v2 routes. Exact numbers are preserved. */
6
- export type ControlJSON = UnsV2JSONValue;
7
- export interface PublicControl {
8
- code: string;
9
- name: string;
10
- description: string;
11
- contractVersion: string;
12
- inputSchema: Record<string, ControlJSON>;
13
- outputSchema: Record<string, ControlJSON>;
14
- sideEffect: 'read' | 'write';
15
- idempotency: 'none' | 'optional' | 'required';
16
- timeoutMs: number;
17
- status: string;
18
- }
19
- export interface ControlDiscoverOptions {
20
- page?: number;
21
- size?: number;
22
- search?: string;
23
- }
24
- export interface ControlDiscoverResponse {
25
- total: number;
26
- list: PublicControl[];
27
- }
28
- /** Shared HTTP settings. The server still resolves the Control binding and authorization. */
29
- export interface ControlTransportConfig extends CloudTransportConfig {
30
- }
31
- export interface CloudClientConfig extends ClientConfig, ControlTransportConfig {
32
- }
33
- export declare const DEFAULT_CONTROL_TRANSPORT_TIMEOUT_MS = 60000;
34
- export interface ControlInvokeOptions {
35
- /** Stable caller-owned key for one business intent, at most 128 UTF-8 bytes. Never generated or retried by the SDK. */
36
- idempotencyKey?: string;
37
- }
38
- export interface ControlInvokeResponse<Result = ControlJSON> {
39
- code: string;
40
- contractVersion: string;
41
- result: Result;
42
- runID: string;
43
- status: string;
44
- /** True only when the server explicitly reports an idempotent replay. */
45
- replayed: boolean;
46
- }
47
- export interface ControlErrorInfo {
48
- /** HTTP status, absent when no HTTP response was received. */
49
- status?: number;
50
- /** Canonical UNS error.code, or an SDK-local CONTROL_* transport/protocol code. */
51
- errorCode: string;
52
- runID?: string;
53
- contractVersion?: string;
54
- runStatus?: string;
55
- replayed?: boolean;
56
- requestId?: string;
57
- execution?: UnsV2Execution;
58
- retry?: UnsV2Retry;
59
- reason?: string;
60
- details?: Record<string, unknown>[];
61
- data?: unknown;
62
- }
63
- /** No Control error is automatically retried, including authentication failures. */
64
- export declare class ControlError extends Error {
65
- readonly status?: number;
66
- readonly errorCode: string;
67
- readonly runID?: string;
68
- readonly contractVersion?: string;
69
- readonly runStatus?: string;
70
- readonly replayed?: boolean;
71
- readonly requestId?: string;
72
- readonly execution?: UnsV2Execution;
73
- readonly retry?: UnsV2Retry;
74
- readonly reason?: string;
75
- readonly details?: Record<string, unknown>[];
76
- readonly data?: unknown;
77
- readonly retryable = false;
78
- constructor(message: string, info: ControlErrorInfo);
79
- }
80
- /** Execution may have happened. Reconcile run information; any deliberate replay must reuse the same key. */
81
- export declare class ControlResultUnknownError extends ControlError {
82
- readonly outcomeUnknown = true;
83
- constructor(info: ControlErrorInfo);
84
- }
85
- export interface ControlHandle<Input = ControlJSON, Result = ControlJSON> {
86
- describe(): Promise<PublicControl>;
87
- /**
88
- * One POST only. Input is opaque business JSON, including fields named streamId,
89
- * entryId, deploymentVersion or timeoutMs. Only the server chooses binding/version/timeout.
90
- * Generic types are caller assertions; the server validates the contract schemas.
91
- */
92
- invoke(input: Input, options?: ControlInvokeOptions): Promise<ControlInvokeResponse<Result>>;
93
- }
94
- export declare class ControlsClient {
95
- private readonly uns;
96
- constructor(resolveClient?: () => HttpClient, config?: ControlTransportConfig);
97
- private post;
98
- /** Discover visible contracts; defaults and authorization are resolved by the server. */
99
- discover(options?: ControlDiscoverOptions): Promise<ControlDiscoverResponse>;
100
- use<Input = ControlJSON, Result = ControlJSON>(selector: {
101
- code: string;
102
- }): ControlHandle<Input, Result>;
103
- }
104
- /** Uses configureClient()/TIER0_API_HOST/TIER0_API_KEY, resolved for every call. */
105
- export declare const controls: ControlsClient;