# 语义 SDK

把企业数据变成「有意思的对象」：工单、线体、订单、客户……每个对象类型有属性（显示名、单位、同义词）、有链接（工单 → 线体）、有 **Action**（「登记异常」「指定负责人」这类受控的写操作）。应用和智能体读的是语义层，改的也是语义层——ERP / MES 这些数据源永远只读，永远不会被改。

参照的是 Palantir 的 Ontology：

```
ERP / MES / OA ──(连接 SDK：发布端，只读)──▶ 数据层（AIDC 中枢，数据 SDK）──(语义 SDK：类型 / 链接 / Action)──▶ 应用 · 智能体
```

```js
import { semantic } from "/developer/sdk/v1/aidc.js";
```

## 三个概念

| 概念 | 是什么 | 例子（吉兴天津 8100） |
| --- | --- | --- |
| **对象类型** | 一类业务对象：主键 + 属性。属性要么来自数据源（同步进来，只读），要么只在语义层维护（`writeback`：备注、负责人、异常状态…） | `production.order_line`（工单线体进度）：工单、线体、计划、累计合格 ← SAP；异常状态、异常说明、登记人 ← 语义层 |
| **链接** | 对象之间的关系。最常见的是一个属性引用另一个类型的主键（`references`） | 工单线体 → 线体（`line_code`） |
| **Action** | 受控的写操作：参数（类型 / 必填 / 长度 / 枚举）+ 改哪些属性 + 谁能做 + 日志里的一句话 | `production.flag_issue` 登记异常：参数「异常说明」「级别」；写 `issue`、`status`、`owner = 执行人` |

改数据只有两条路：**成员用 Action**（校验、权限、留痕）；**开发者用数据 SDK 直接改**（管理用）。两条路的改动都落在语义层（对象的 edits），数据源下一次同步只更新它自己那一层，不会冲掉人改过的值。

## 读：说明书与对象

```js
const space = await semantic.describe();
// space.types：看得见的对象类型（属性、单位、同义词、链接、对象数）
// space.actions：Action 与「我能不能执行」（allowed）
// space.markdown：给大模型读的整份说明书——智能体先读它再干活
// space.release：当前语义版本（v3…）

const hit = semantic.search(space, "合格数");        // 按业务说法定位：[{ kind: "property", type: "production.order_line", name: "ok" }]

const issues = await semantic.objects("production.order_line")
  .where({ status: { in: ["关注", "异常"] }, day: "2026-09-26" })
  .sort("-gap")
  .list();                                           // { objects, total, seq }

const order = await semantic.objects("production.order_line").get("100001955718|TJ01-05");
const line = await semantic.objects("production.order_line").linked(order.pk, "production.order_line.line_code");
const byLine = await semantic.objects("production.order_line").aggregate({ groupBy: ["line_code"], metrics: { ok: ["ok", "sum"] } });
```

每个对象：`props`（当前值 = 数据源的值被语义层改动覆盖后）、`rev`（每改一次 +1）、`edited`（语义层改过的属性）、`overridden`（被改动盖住、数据源现在是另一个值的属性 → 数据源的值）、`sourceGone`（数据源里已经没有这一行）。

条件写法：`{ 属性: 值 }` 相等；`{ 属性: { gt, gte, lt, lte, ne, in, nin, contains, startsWith, null } }`；多个属性之间是「且」；`$or: [{…}, {…}]` 任意一组成立。

## 实时

```js
const view = semantic.objects("production.order_line").where({ day: today }).sort("-ok").live({
  onUpdate(objects, change) { render(objects); },   // 先全量，之后数据一变就回调（同步、Action、别人的修改都算）
  onStatus({ connection }) { badge(connection); },
});
// view.close()
```

底层是数据 SDK 的 `watch`（SSE，断线按序号续传，不丢不重）。

## 写：执行 Action

```js
const r = await semantic.action("production.flag_issue").apply(
  { issue: "缺料：顶蓬面料未到", severity: "异常" },
  { object: "100001955718|TJ01-05", ifRev: order.rev },
);
// r.object：改后的对象；r.event.summary：「TJ01-05 登记异常：缺料…」（进日志 SDK 的操作记录）
```

- 参数按定义校验（不合法 422，信息里写明哪一项）；`semantic.checkParams(actionInfo, params)` 可以在表单提交前本地预检。
- 谁能执行：应用清单 `semantic.actions` 登记了它，且访客角色在 Action 的 `roles` 里（缺省 developer / member / editor）。只读访客（公开链接、只读分享）与匿名访客永远不能写。
- `ifRev`：对象当前 rev 不等于它就 409 `rev_mismatch`（别人刚改过，刷新再改）。
- `dryRun: true`：只校验、返回计划，不落库。

## 定义与发布（开发者：CLI / 智能体）

定义就是 JSON。四种词条：`enum`、`object`、`link`、`action`。

```json
{
  "kind": "object",
  "apiName": "production.order_line",
  "title": "工单线体进度",
  "description": "每个工单在每条线体上的当日进度（SAP ZPP015）",
  "schema": {
    "titleColumn": "line_code",
    "synonyms": ["工单行", "生产任务"],
    "columns": [
      { "name": "order_no", "type": "text", "primaryKey": true, "title": "工单" },
      { "name": "line_code", "type": "text", "primaryKey": true, "title": "线体", "references": { "entity": "production.line", "column": "line_code" } },
      { "name": "ok", "type": "integer", "title": "累计合格", "unit": "件", "synonyms": ["合格数", "良品数"] },
      { "name": "status", "type": "text", "enumRef": "production.issue_status", "writeback": true, "title": "异常状态" },
      { "name": "issue", "type": "text", "writeback": true, "title": "异常说明" }
    ]
  }
}
```

```json
{
  "kind": "action",
  "apiName": "production.flag_issue",
  "title": "登记异常",
  "schema": {
    "objectType": "production.order_line",
    "operation": "modify",
    "parameters": [
      { "name": "issue", "type": "text", "title": "异常说明", "required": true, "maxLength": 200 },
      { "name": "severity", "type": "text", "title": "级别", "enumRef": "production.issue_status" }
    ],
    "edits": { "issue": "$issue", "status": "$severity", "owner": "$userName", "flagged_at": "$now" },
    "roles": ["developer", "member", "editor"],
    "summary": "{line_code} 登记异常：{issue}"
  }
}
```

取值表达式：`$参数名`、`$now`（当前时间）、`$user`（执行人账号）、`$userName`（执行人名字）、`$uuid`（新主键，create 用）；其他是字面量（以 `$` 开头的字面量写成 `$$…`）。`operation`：`modify` 改一个对象、`create` 新建（edits 要给出主键）、`delete` 删除（语义层墓碑）。

```bash
aidc semantic define ontology/            # 目录里的 *.json 整批提交：一起校验、按 枚举 → 对象 → 链接 → Action 落（有则改）
aidc semantic define ontology/ --dry-run  # 只检查：整批引用是否闭合（批内互相引用算数）、相对上一个语义版本有哪些破坏性变更
aidc semantic publish --notes "加了异常登记"   # 发一个语义版本 vN（只增；定义没变不占号）
aidc semantic describe --markdown         # 给智能体读的说明书
aidc semantic act production.flag_issue --object "100001955718|TJ01-05" --param issue=缺料 --param severity=异常
```

- **引用闭合**：Action 改的属性、引用的参数、外键目标、枚举都必须存在，否则定义直接 422。一个目录是一批（`semantic.defineAll(定义数组)` / `POST …/semantic/{命名空间}/types`）：在「整批改完之后」的全体定义上校验，批内互相引用算数，不闭合一条都不写。
- **破坏性变更**（删类型 / 删属性 / 改类型 / 改主键 / Action 删参数或新增必填参数…）会在定义时返回、在发布时记档（`changeKind: breaking`）。发布前先确认调用它的应用都改好了。
- **语义版本**就是这家公司 Ontology 的版本号，与应用版本号一起出现在日志 SDK 里；自提升 SDK 采纳的语义改进会自动发新版本。

## 权限一览

| 谁 | 读 | 执行 Action | 直接写（数据 SDK） | 定义 / 发布 |
| --- | --- | --- | --- | --- |
| 开发者 Key（CLI / 智能体） | 本公司全部 | 全部 | 全部 | ✓ |
| 应用里的 developer 访客 | 清单 `semantic.types` 登记的（含部门级） | 清单登记 + roles 允许 | 清单 `semantic.write` 登记的 | — |
| 应用里的 member / editor | 清单登记、且对公司全员可见的 | 清单登记 + roles 允许 | — | — |
| viewer（只读分享 / 公开链接）、匿名 | 同上 | — | — | — |

应用清单里这样登记：

```json
{
  "sdk": ["semantic", "data", "auth", "log"],
  "semantic": {
    "types": ["production.order_line", "production.line"],
    "actions": ["production.flag_issue", "production.resolve_issue"],
    "write": []
  }
}
```

`"*"` 表示看得见的全部（数据浏览器这类通用应用）。

## API

| 方法 | 路径 | 说明 |
| --- | --- | --- |
| GET | `/api/v1/developer/semantic/{命名空间}` | 说明书（按凭证裁剪） |
| GET / POST | `/api/v1/developer/semantic/{命名空间}/types` | 全部定义（开发者）/ 一次定义一批（`{definitions: […]}`，整批校验） |
| GET / PUT / DELETE | `/api/v1/developer/semantic/{命名空间}/types/{apiName}` | 读 / 定义 / 归档 |
| POST | `/api/v1/developer/semantic/{命名空间}/actions/{apiName}` | 执行 Action |
| GET / POST | `/api/v1/developer/semantic/{命名空间}/releases` | 语义版本列表 / 发布 |

对象的查询、聚合、订阅在数据 SDK（`/api/v1/developer/data/…`）。命名空间 = 公司 cellId（`cell-…`）；应用里自动取应用所在公司。
